diff --git a/src/Microsoft.AspNetCore.TestHost/HostBuilderTestServerExtensions.cs b/src/Microsoft.AspNetCore.TestHost/HostBuilderTestServerExtensions.cs new file mode 100644 index 0000000000..e52f3d6ab0 --- /dev/null +++ b/src/Microsoft.AspNetCore.TestHost/HostBuilderTestServerExtensions.cs @@ -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 + { + /// + /// Retrieves the TestServer from the host services. + /// + /// + /// + public static TestServer GetTestServer(this IHost host) + { + return (TestServer)host.Services.GetRequiredService(); + } + + /// + /// Retrieves the test client from the TestServer in the host services. + /// + /// + /// + public static HttpClient GetTestClient(this IHost host) + { + return host.GetTestServer().CreateClient(); + } + } +} diff --git a/src/Microsoft.AspNetCore.TestHost/TestServer.cs b/src/Microsoft.AspNetCore.TestHost/TestServer.cs index 398a575d9d..40a3e63f05 100644 --- a/src/Microsoft.AspNetCore.TestHost/TestServer.cs +++ b/src/Microsoft.AspNetCore.TestHost/TestServer.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -15,28 +16,48 @@ namespace Microsoft.AspNetCore.TestHost { public class TestServer : IServer { - private const string ServerName = nameof(TestServer); private IWebHost _hostInstance; private bool _disposed = false; private IHttpApplication _application; + /// + /// For use with IHostBuilder or IWebHostBuilder. + /// + public TestServer() + : this(new FeatureCollection()) + { + } + + /// + /// For use with IHostBuilder or IWebHostBuilder. + /// + /// + public TestServer(IFeatureCollection featureCollection) + { + Features = featureCollection ?? throw new ArgumentNullException(nameof(featureCollection)); + } + + /// + /// For use with IWebHostBuilder. + /// + /// public TestServer(IWebHostBuilder builder) : this(builder, new FeatureCollection()) { } + /// + /// For use with IWebHostBuilder. + /// + /// + /// public TestServer(IWebHostBuilder builder, IFeatureCollection featureCollection) + : this(featureCollection) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - if (featureCollection == null) - { - throw new ArgumentNullException(nameof(featureCollection)); - } - - Features = featureCollection; var host = builder.UseServer(this).Build(); host.StartAsync().GetAwaiter().GetResult(); @@ -49,16 +70,22 @@ namespace Microsoft.AspNetCore.TestHost { 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; } + private IHttpApplication Application + { + get => _application ?? throw new InvalidOperationException("The server has not been started or no web application was configured."); + } + public HttpMessageHandler CreateHandler() { var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); - return new ClientHandler(pathBase, _application); + return new ClientHandler(pathBase, Application); } public HttpClient CreateClient() @@ -69,7 +96,7 @@ namespace Microsoft.AspNetCore.TestHost public WebSocketClient CreateWebSocketClient() { var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress); - return new WebSocketClient(pathBase, _application); + return new WebSocketClient(pathBase, Application); } /// @@ -93,7 +120,7 @@ namespace Microsoft.AspNetCore.TestHost throw new ArgumentNullException(nameof(configureContext)); } - var builder = new HttpContextBuilder(_application); + var builder = new HttpContextBuilder(Application); builder.Configure(context => { var request = context.Request; diff --git a/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs index cccf19cdd5..308ac86cd4 100644 --- a/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs @@ -6,12 +6,21 @@ using System.IO; using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.TestHost { public static class WebHostBuilderExtensions { + public static IWebHostBuilder UseTestServer(this IWebHostBuilder builder) + { + return builder.ConfigureServices(services => + { + services.AddSingleton(); + }); + } + public static IWebHostBuilder ConfigureTestServices(this IWebHostBuilder webHostBuilder, Action servicesConfiguration) { if (webHostBuilder == null) diff --git a/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs b/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs index 8b1c4d3494..b4753d7201 100644 --- a/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs +++ b/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs @@ -2,20 +2,32 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading; +using System.Threading.Tasks; namespace Microsoft.Extensions.Hosting { public static class HostingAbstractionsHostBuilderExtensions { /// - /// Start the host and listen on the specified urls. + /// Builds and starts the host. /// /// The to start. - /// The . + /// The started . public static IHost Start(this IHostBuilder hostBuilder) + { + return hostBuilder.StartAsync().GetAwaiter().GetResult(); + } + + /// + /// Builds and starts the host. + /// + /// The to start. + /// + /// The started . + public static async Task StartAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default) { var host = hostBuilder.Build(); - host.StartAsync(CancellationToken.None).GetAwaiter().GetResult(); + await host.StartAsync(cancellationToken); return host; } } diff --git a/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj b/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj index 25b877467c..f62aae9cec 100644 --- a/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj +++ b/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj @@ -6,6 +6,7 @@ + diff --git a/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs b/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs index a4175a75cb..cc789962c7 100644 --- a/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs +++ b/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DiagnosticAdapter; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using Xunit; @@ -23,6 +24,56 @@ namespace Microsoft.AspNetCore.TestHost { 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] public void CreateWithDelegate() { @@ -31,6 +82,17 @@ namespace Microsoft.AspNetCore.TestHost 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] public void DoesNotCaptureStartupErrorsByDefault() {