Add TestServer support for generic WebHost #1583

This commit is contained in:
Chris Ross (ASP.NET) 2018-11-16 11:01:01 -08:00
parent f6cda4fab7
commit 241d2c13df
6 changed files with 160 additions and 14 deletions

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Microsoft.AspNetCore.TestHost
{
public static class HostBuilderTestServerExtensions
{
/// <summary>
/// Retrieves the TestServer from the host services.
/// </summary>
/// <param name="host"></param>
/// <returns></returns>
public static TestServer GetTestServer(this IHost host)
{
return (TestServer)host.Services.GetRequiredService<IServer>();
}
/// <summary>
/// Retrieves the test client from the TestServer in the host services.
/// </summary>
/// <param name="host"></param>
/// <returns></returns>
public static HttpClient GetTestClient(this IHost host)
{
return host.GetTestServer().CreateClient();
}
}
}

View File

@ -6,6 +6,7 @@ using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
@ -15,28 +16,48 @@ namespace Microsoft.AspNetCore.TestHost
{ {
public class TestServer : IServer public class TestServer : IServer
{ {
private const string ServerName = nameof(TestServer);
private IWebHost _hostInstance; private IWebHost _hostInstance;
private bool _disposed = false; private bool _disposed = false;
private IHttpApplication<Context> _application; private IHttpApplication<Context> _application;
/// <summary>
/// For use with IHostBuilder or IWebHostBuilder.
/// </summary>
public TestServer()
: this(new FeatureCollection())
{
}
/// <summary>
/// For use with IHostBuilder or IWebHostBuilder.
/// </summary>
/// <param name="featureCollection"></param>
public TestServer(IFeatureCollection featureCollection)
{
Features = featureCollection ?? throw new ArgumentNullException(nameof(featureCollection));
}
/// <summary>
/// For use with IWebHostBuilder.
/// </summary>
/// <param name="builder"></param>
public TestServer(IWebHostBuilder builder) public TestServer(IWebHostBuilder builder)
: this(builder, new FeatureCollection()) : this(builder, new FeatureCollection())
{ {
} }
/// <summary>
/// For use with IWebHostBuilder.
/// </summary>
/// <param name="builder"></param>
/// <param name="featureCollection"></param>
public TestServer(IWebHostBuilder builder, IFeatureCollection featureCollection) public TestServer(IWebHostBuilder builder, IFeatureCollection featureCollection)
: this(featureCollection)
{ {
if (builder == null) if (builder == null)
{ {
throw new ArgumentNullException(nameof(builder)); throw new ArgumentNullException(nameof(builder));
} }
if (featureCollection == null)
{
throw new ArgumentNullException(nameof(featureCollection));
}
Features = featureCollection;
var host = builder.UseServer(this).Build(); var host = builder.UseServer(this).Build();
host.StartAsync().GetAwaiter().GetResult(); host.StartAsync().GetAwaiter().GetResult();
@ -49,16 +70,22 @@ namespace Microsoft.AspNetCore.TestHost
{ {
get get
{ {
return _hostInstance; return _hostInstance
?? throw new InvalidOperationException("The TestServer constructor was not called with a IWebHostBuilder so IWebHost is not available.");
} }
} }
public IFeatureCollection Features { get; } public IFeatureCollection Features { get; }
private IHttpApplication<Context> Application
{
get => _application ?? throw new InvalidOperationException("The server has not been started or no web application was configured.");
}
public HttpMessageHandler CreateHandler() public HttpMessageHandler CreateHandler()
{ {
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
return new ClientHandler(pathBase, _application); return new ClientHandler(pathBase, Application);
} }
public HttpClient CreateClient() public HttpClient CreateClient()
@ -69,7 +96,7 @@ namespace Microsoft.AspNetCore.TestHost
public WebSocketClient CreateWebSocketClient() public WebSocketClient CreateWebSocketClient()
{ {
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
return new WebSocketClient(pathBase, _application); return new WebSocketClient(pathBase, Application);
} }
/// <summary> /// <summary>
@ -93,7 +120,7 @@ namespace Microsoft.AspNetCore.TestHost
throw new ArgumentNullException(nameof(configureContext)); throw new ArgumentNullException(nameof(configureContext));
} }
var builder = new HttpContextBuilder(_application); var builder = new HttpContextBuilder(Application);
builder.Configure(context => builder.Configure(context =>
{ {
var request = context.Request; var request = context.Request;

View File

@ -6,12 +6,21 @@ using System.IO;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.TestHost namespace Microsoft.AspNetCore.TestHost
{ {
public static class WebHostBuilderExtensions public static class WebHostBuilderExtensions
{ {
public static IWebHostBuilder UseTestServer(this IWebHostBuilder builder)
{
return builder.ConfigureServices(services =>
{
services.AddSingleton<IServer, TestServer>();
});
}
public static IWebHostBuilder ConfigureTestServices(this IWebHostBuilder webHostBuilder, Action<IServiceCollection> servicesConfiguration) public static IWebHostBuilder ConfigureTestServices(this IWebHostBuilder webHostBuilder, Action<IServiceCollection> servicesConfiguration)
{ {
if (webHostBuilder == null) if (webHostBuilder == null)

View File

@ -2,20 +2,32 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Hosting namespace Microsoft.Extensions.Hosting
{ {
public static class HostingAbstractionsHostBuilderExtensions public static class HostingAbstractionsHostBuilderExtensions
{ {
/// <summary> /// <summary>
/// Start the host and listen on the specified urls. /// Builds and starts the host.
/// </summary> /// </summary>
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to start.</param> /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to start.</param>
/// <returns>The <see cref="IHostBuilder"/>.</returns> /// <returns>The started <see cref="IHost"/>.</returns>
public static IHost Start(this IHostBuilder hostBuilder) public static IHost Start(this IHostBuilder hostBuilder)
{
return hostBuilder.StartAsync().GetAwaiter().GetResult();
}
/// <summary>
/// Builds and starts the host.
/// </summary>
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to start.</param>
/// <param name="cancellationToken"></param>
/// <returns>The started <see cref="IHost"/>.</returns>
public static async Task<IHost> StartAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{ {
var host = hostBuilder.Build(); var host = hostBuilder.Build();
host.StartAsync(CancellationToken.None).GetAwaiter().GetResult(); await host.StartAsync(cancellationToken);
return host; return host;
} }
} }

View File

@ -6,6 +6,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.TestHost\Microsoft.AspNetCore.TestHost.csproj" /> <ProjectReference Include="..\..\src\Microsoft.AspNetCore.TestHost\Microsoft.AspNetCore.TestHost.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Extensions.Hosting\Microsoft.Extensions.Hosting.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DiagnosticAdapter; using Microsoft.Extensions.DiagnosticAdapter;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Xunit; using Xunit;
@ -23,6 +24,56 @@ namespace Microsoft.AspNetCore.TestHost
{ {
public class TestServerTests public class TestServerTests
{ {
[Fact]
public async Task GenericRawCreate()
{
var server = new TestServer();
var host = new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseServer(server)
.Configure(app => { });
})
.Build();
await host.StartAsync();
var response = await server.CreateClient().GetAsync("/");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GenericCreateAndStartHost_GetTestServer()
{
var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.Configure(app => { });
})
.StartAsync();
var response = await host.GetTestServer().CreateClient().GetAsync("/");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GenericCreateAndStartHost_GetTestClient()
{
var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.Configure(app => { });
})
.StartAsync();
var response = await host.GetTestClient().GetAsync("/");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact] [Fact]
public void CreateWithDelegate() public void CreateWithDelegate()
{ {
@ -31,6 +82,17 @@ namespace Microsoft.AspNetCore.TestHost
new TestServer(new WebHostBuilder().Configure(app => { })); new TestServer(new WebHostBuilder().Configure(app => { }));
} }
[Fact]
public void CreateWithDelegate_DI()
{
var builder = new WebHostBuilder()
.Configure(app => { })
.UseTestServer();
var host = builder.Build();
host.Start();
}
[Fact] [Fact]
public void DoesNotCaptureStartupErrorsByDefault() public void DoesNotCaptureStartupErrorsByDefault()
{ {