Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new UseKestel API to the WebApplicationFactory #60635

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.AspNetCore.Mvc.Core" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.DependencyModel" />
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.Extensions.HostFactoryResolver.Sources" />
Expand Down
4 changes: 4 additions & 0 deletions src/Mvc/Mvc.Testing/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
#nullable enable
*REMOVED*Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.Server.get -> Microsoft.AspNetCore.TestHost.TestServer!
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.Server.get -> Microsoft.AspNetCore.TestHost.TestServer?
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.UseKestrel() -> void
virtual Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.CreateHandler() -> System.Net.Http.HttpMessageHandler!
5 changes: 4 additions & 1 deletion src/Mvc/Mvc.Testing/src/Resources.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Expand Down Expand Up @@ -126,4 +126,7 @@
<data name="MissingDepsFile" xml:space="preserve">
<value>Can't find '{0}'. This file is required for functional tests to run properly. There should be a copy of the file on your source project bin folder. If that is not the case, make sure that the property PreserveCompilationContext is set to true on your project file. E.g '&lt;PreserveCompilationContext&gt;true&lt;/PreserveCompilationContext&gt;'. For functional tests to work they need to either run from the build output folder or the {1} file from your application's output directory must be copied to the folder where the tests are running on. A common cause for this error is having shadow copying enabled when the tests run.</value>
</data>
<data name="ServerNotInitialized" xml:space="preserve">
<value>Server hasn't been initialized yet. Plase intialized the server first before trying to create a client.</value>
</data>
</root>
109 changes: 97 additions & 12 deletions src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -24,11 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.Testing;
/// Typically the Startup or Program classes can be used.</typeparam>
public partial class WebApplicationFactory<TEntryPoint> : IDisposable, IAsyncDisposable where TEntryPoint : class
{
private bool _useKestrel;
private bool _disposed;
private bool _disposedAsync;
private TestServer? _server;
private IHost? _host;
private Action<IWebHostBuilder> _configuration;
private IWebHost? _webHost;
private readonly List<HttpClient> _clients = new();
private readonly List<WebApplicationFactory<TEntryPoint>> _derivedFactories = new();

Expand Down Expand Up @@ -71,7 +73,7 @@ public WebApplicationFactory()
/// <summary>
/// Gets the <see cref="TestServer"/> created by this <see cref="WebApplicationFactory{TEntryPoint}"/>.
/// </summary>
public TestServer Server
public TestServer? Server
{
get
{
Expand All @@ -88,7 +90,12 @@ public virtual IServiceProvider Services
get
{
EnsureServer();
return _host?.Services ?? _server.Host.Services;
if (_useKestrel)
{
return _webHost!.Services;
}

return _host?.Services ?? _server!.Host.Services;
}
}

Expand Down Expand Up @@ -136,10 +143,24 @@ internal virtual WebApplicationFactory<TEntryPoint> WithWebHostBuilderCore(Actio
return factory;
}

[MemberNotNull(nameof(_server))]
/// <summary>
/// Configures the factory to use Kestrel as the server.
/// </summary>
public void UseKestrel()
{
_useKestrel = true;
}

private static IWebHost CreateKestrelServer(IWebHostBuilder builder)
{
var host = builder.UseKestrel().Build();
host.Start();
return host;
}

private void EnsureServer()
{
if (_server != null)
if (_server != null || _webHost != null)
{
return;
}
Expand Down Expand Up @@ -197,21 +218,37 @@ private void EnsureServer()
{
SetContentRoot(builder);
_configuration(builder);
_server = CreateServer(builder);
if (_useKestrel)
{
_webHost = CreateKestrelServer(builder);
}
else
{
_server = CreateServer(builder);
}
}
}

[MemberNotNull(nameof(_server))]
private void ConfigureHostBuilder(IHostBuilder hostBuilder)
{
hostBuilder.ConfigureWebHost(webHostBuilder =>
{
SetContentRoot(webHostBuilder);
_configuration(webHostBuilder);
webHostBuilder.UseTestServer();
if (!_useKestrel)
{
webHostBuilder.UseTestServer();
}
else
{
webHostBuilder.UseKestrel();
}
});
_host = CreateHost(hostBuilder);
_server = (TestServer)_host.Services.GetRequiredService<IServer>();
if (!_useKestrel)
{
_server = (TestServer)_host.Services.GetRequiredService<IServer>();
}
}

private void SetContentRoot(IWebHostBuilder builder)
Expand Down Expand Up @@ -481,7 +518,19 @@ public HttpClient CreateDefaultClient(params DelegatingHandler[] handlers)
HttpClient client;
if (handlers == null || handlers.Length == 0)
{
client = _server.CreateClient();
if (_useKestrel)
{
client = new HttpClient();
}
else
{
if (_server is null)
{
throw new InvalidOperationException(Resources.ServerNotInitialized);
}

client = _server.CreateClient();
}
}
else
{
Expand All @@ -490,7 +539,7 @@ public HttpClient CreateDefaultClient(params DelegatingHandler[] handlers)
handlers[i - 1].InnerHandler = handlers[i];
}

var serverHandler = _server.CreateHandler();
var serverHandler = CreateHandler();
handlers[^1].InnerHandler = serverHandler;

client = new HttpClient(handlers[0]);
Expand All @@ -503,6 +552,25 @@ public HttpClient CreateDefaultClient(params DelegatingHandler[] handlers)
return client;
}

/// <summary>
/// Creates a custom <see cref="HttpMessageHandler" /> for processing HTTP requests/responses with the test server.
/// </summary>

protected virtual HttpMessageHandler CreateHandler()
{
if (_useKestrel)
{
return new HttpClientHandler();
}

if (_server is null)
{
throw new InvalidOperationException(Resources.ServerNotInitialized);
}

return _server.CreateHandler();
}

/// <summary>
/// Configures <see cref="HttpClient"/> instances created by this <see cref="WebApplicationFactory{TEntryPoint}"/>.
/// </summary>
Expand All @@ -511,7 +579,24 @@ protected virtual void ConfigureClient(HttpClient client)
{
ArgumentNullException.ThrowIfNull(client);

client.BaseAddress = new Uri("http://localhost");
if (_useKestrel)
{
if (_webHost is null)
{
throw new InvalidOperationException(Resources.ServerNotInitialized);
}

// The server should be started at this point already.
var serverAddressFeature = _webHost.ServerFeatures.Get<IServerAddressesFeature>();
if (serverAddressFeature?.Addresses.Count > 0)
{
client.BaseAddress = new Uri(serverAddressFeature.Addresses.Last());
}
}
else
{
client.BaseAddress = new Uri("http://localhost");
}
}

/// <summary>
Expand Down
Loading