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

Merged
merged 17 commits into from
Mar 28, 2025
Merged
Changes from 10 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
@@ -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" />
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
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.StartServer() -> void
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.UseKestrel() -> void
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.UseKestrel(int port) -> void
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.UseKestrel(System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions!>! configureKestrelOptions) -> void
8 changes: 7 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
@@ -126,4 +126,10 @@
<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>
<data name="UseKestrelCanBeCalledBeforeInitialization" xml:space="preserve">
<value>UseKestrel should be called before server initialization. Calling UseKestrel after the server was initialized will have no effect.</value>
</data>
</root>
225 changes: 207 additions & 18 deletions src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// 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.Http.Features;

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl x64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: macOS x64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl ARM)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: macOS arm64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: Linux x64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: Linux ARM64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl ARM64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Build: Linux ARM)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-quarantined-pr (Tests: Ubuntu x64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Test: Ubuntu x64)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-quarantined-pr (Tests: macOS)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci (Build Test: macOS)

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-quarantined-pr

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-quarantined-pr

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)

Check failure on line 12 in src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Azure Pipelines / aspnetcore-ci

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs#L12

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs(12,1): error IDE0005: (NETCORE_ENGINEERING_TELEMETRY=Build) Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005)
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -26,9 +28,16 @@
{
private bool _disposed;
private bool _disposedAsync;

private bool _useKestrel;
private int? _kestrelPort;
private Action<KestrelServerOptions>? _configureKestrelOptions;

private TestServer? _server;
private IHost? _host;
private Action<IWebHostBuilder> _configuration;
private IWebHost? _webHost;
private Uri? _webHostAddress;
private readonly List<HttpClient> _clients = new();
private readonly List<WebApplicationFactory<TEntryPoint>> _derivedFactories = new();

@@ -75,8 +84,13 @@
{
get
{
EnsureServer();
return _server;
if (_useKestrel)
{
throw new NotSupportedException();
}

StartServer();
return _server!;
}
}

@@ -87,8 +101,13 @@
{
get
{
EnsureServer();
return _host?.Services ?? _server.Host.Services;
StartServer();
if (_useKestrel)
{
return _webHost!.Services;
}

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

@@ -136,10 +155,92 @@
return factory;
}

[MemberNotNull(nameof(_server))]
private void EnsureServer()
/// <summary>
/// Configures the factory to use Kestrel as the server.
/// </summary>
public void UseKestrel()
{
if (_server != null || _webHost != null)
{
throw new InvalidOperationException(Resources.UseKestrelCanBeCalledBeforeInitialization);
}

_useKestrel = true;
}

/// <summary>
/// Configures the factory to use Kestrel as the server.
/// </summary>
/// <param name="port">The port to listen to when the server starts. Use `0` to allow dynamic port selection.</param>
/// <exception cref="InvalidOperationException">Thrown, if this method is called after the WebHostFactory has been initialized.</exception>
/// <remarks>This method should be called before the factory is initialized either via one of the <see cref="CreateClient()"/> methods
/// or via the <see cref="StartServer"/> method.</remarks>
public void UseKestrel(int port)
{
UseKestrel();

this._kestrelPort = port;
}

/// <summary>
/// Configures the factory to use Kestrel as the server.
/// </summary>
/// <param name="configureKestrelOptions">A callback handler that will be used for configuring the server when it starts.</param>
/// <exception cref="InvalidOperationException">Thrown, if this method is called after the WebHostFactory has been initialized.</exception>
/// <remarks>This method should be called before the factory is initialized either via one of the <see cref="CreateClient()"/> methods
/// or via the <see cref="StartServer"/> method.</remarks>
public void UseKestrel(Action<KestrelServerOptions> configureKestrelOptions)
{
UseKestrel();

this._configureKestrelOptions = configureKestrelOptions;
}

private IWebHost CreateKestrelServer(IWebHostBuilder builder)
{
ConfigureBuilderToUseKestrel(builder);

var host = builder.Build();

TryConfigureServerPort(() => GetServerAddressFeature(host));

host.Start();
return host;
}

private void TryConfigureServerPort(Func<IServerAddressesFeature?> serverAddressFeatureAccessor)
{
if (_kestrelPort.HasValue)
{
var saf = serverAddressFeatureAccessor();
if (saf is not null)
{
saf.Addresses.Clear();
saf.Addresses.Add($"http://127.0.0.1:{_kestrelPort}");
saf.PreferHostingUrls = true;
}
}
}

private void ConfigureBuilderToUseKestrel(IWebHostBuilder builder)
{
if (_configureKestrelOptions is not null)
{
builder.UseKestrel(_configureKestrelOptions);
}
else
{
builder.UseKestrel();
}
}

/// <summary>
/// Initializes the instance by configurating the host builder.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the provided <typeparamref name="TEntryPoint"/> type has no factory method.</exception>
public void StartServer()
{
if (_server != null)
if (_webHost != null || _host != null)
{
return;
}
@@ -197,21 +298,54 @@
{
SetContentRoot(builder);
_configuration(builder);
_server = CreateServer(builder);
if (_useKestrel)
{
_webHost = CreateKestrelServer(builder);

TryExtractHostAddress(() => GetServerAddressFeature(_webHost));
}
else
{
_server = CreateServer(builder);
}
}
}

private void TryExtractHostAddress(Func<IServerAddressesFeature?> serverAddressFeatureAccessor)
{
var serverAddressFeature = serverAddressFeatureAccessor();
if (serverAddressFeature?.Addresses.Count > 0)
{
// Store the web host address as it's going to be used every time a client is created to communicate to the server
_webHostAddress = new Uri(serverAddressFeature.Addresses.Last());
ClientOptions.BaseAddress = _webHostAddress;
}
}

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

private void SetContentRoot(IWebHostBuilder builder)
@@ -438,10 +572,15 @@
protected virtual IHost CreateHost(IHostBuilder builder)
{
var host = builder.Build();
TryConfigureServerPort(() => GetServerAddressFeature(host));
host.Start();
return host;
}

private static IServerAddressesFeature? GetServerAddressFeature(IHost host) => host.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>();

private static IServerAddressesFeature? GetServerAddressFeature(IWebHost webHost) => webHost.ServerFeatures.Get<IServerAddressesFeature>();

/// <summary>
/// Gives a fixture an opportunity to configure the application before it gets built.
/// </summary>
@@ -455,8 +594,19 @@
/// redirects and handles cookies.
/// </summary>
/// <returns>The <see cref="HttpClient"/>.</returns>
public HttpClient CreateClient() =>
CreateClient(ClientOptions);
public HttpClient CreateClient()
{
var client = CreateClient(ClientOptions);

if (_useKestrel)
{
// Have to do this, as the ClientOptions.BaseAddress will be set to point to the kestrel server,
// and it may not match the original base address value.
client.BaseAddress = ClientOptions.BaseAddress;
}

return client;
}

/// <summary>
/// Creates an instance of <see cref="HttpClient"/> that automatically follows
@@ -476,12 +626,24 @@
/// <returns>The <see cref="HttpClient"/>.</returns>
public HttpClient CreateDefaultClient(params DelegatingHandler[] handlers)
{
EnsureServer();
StartServer();

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
{
@@ -490,7 +652,7 @@
handlers[i - 1].InnerHandler = handlers[i];
}

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

client = new HttpClient(handlers[0]);
@@ -503,6 +665,21 @@
return client;
}

private 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>
@@ -511,7 +688,19 @@
{
ArgumentNullException.ThrowIfNull(client);

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

client.BaseAddress = _webHostAddress;
}
else
{
client.BaseAddress = new Uri("http://localhost");
}
}

/// <summary>
14 changes: 14 additions & 0 deletions src/Mvc/test/Mvc.FunctionalTests/KestrelBasedWapFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +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 Microsoft.AspNetCore.Mvc.Testing;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests;

public class KestrelBasedWapFactory : WebApplicationFactory<SimpleWebSite.Startup>
{
public KestrelBasedWapFactory() : base()
{
this.UseKestrel();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests;

public class RealServerBackedIntegrationTests : IClassFixture<KestrelBasedWapFactory>
{
public KestrelBasedWapFactory Factory { get; }

public RealServerBackedIntegrationTests(KestrelBasedWapFactory factory)
{
Factory = factory;
}

[Fact]
public async Task RetrievesDataFromRealServer()
{
// Arrange
var expectedMediaType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");

// Act
var client = Factory.CreateClient();
var response = await client.GetAsync("/");
var responseContent = await response.Content.ReadAsStringAsync();

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);

Assert.Equal(5000, client.BaseAddress.Port);

Assert.Contains("first", responseContent);
Assert.Contains("second", responseContent);
Assert.Contains("wall", responseContent);
Assert.Contains("floor", responseContent);
}

[Fact]
public async Task ServerReachableViaGenericHttpClient()
{
// Arrange
var baseAddress = new Uri("http://localhost:5000");

// Act
using var factoryClient = Factory.CreateClient();
using var client = new HttpClient() { BaseAddress = factoryClient.BaseAddress };

using var response = await client.GetAsync("/");

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests;

public class RealServerWithCustomConfigurationIntegrationTests : IClassFixture<KestrelBasedWapFactory>
{
private int _optionsConfiguredCounter = 0;

public KestrelBasedWapFactory Factory { get; }

public RealServerWithCustomConfigurationIntegrationTests(KestrelBasedWapFactory factory)
{
Factory = factory;
Factory.UseKestrel(options => { _optionsConfiguredCounter++; });
}

[Fact]
public async Task ServerReachableViaGenericHttpClient()
{
// Arrange

// Act
Assert.Equal(0, _optionsConfiguredCounter);
Factory.StartServer();

using var client = Factory.CreateClient();

using var response = await client.GetAsync("/");

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(1, _optionsConfiguredCounter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests;

public class RealServerWithDynamicPortIntegrationTests : IClassFixture<KestrelBasedWapFactory>
{
public KestrelBasedWapFactory Factory { get; }

public RealServerWithDynamicPortIntegrationTests(KestrelBasedWapFactory factory)
{
Factory = factory;

// Use dynamic port
Factory.UseKestrel(0);
}

[Fact]
public async Task ServerReachableViaGenericHttpClient()
{
// Arrange
using var client = new HttpClient();
using var factoryClient = Factory.CreateClient();
client.BaseAddress = factoryClient.BaseAddress;

// Act
using var response = await client.GetAsync("/");

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Microsoft.AspNetCore.Mvc.FunctionalTests;

public class RealServerWithSpecifiedPortBackedIntegrationTests : IClassFixture<KestrelBasedWapFactory>
{
private const int ServerPort = 7777;

public KestrelBasedWapFactory Factory { get; }

public RealServerWithSpecifiedPortBackedIntegrationTests(KestrelBasedWapFactory factory)
{
Factory = factory;
Factory.UseKestrel(ServerPort);
}

[Fact]
public async Task ServerReachableViaGenericHttpClient_OnSpecificPort()
{
// Arrange
var baseAddress = new Uri($"http://localhost:{ServerPort}");

// Act
Factory.StartServer();

using var client = new HttpClient() { BaseAddress = baseAddress };

using var response = await client.GetAsync("/");

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}