Fix E2E tests by switching to new KestrelTestServer (#360)

This commit is contained in:
Leendert de Borst
2024-11-13 16:44:44 +01:00
parent 87bb34f3ba
commit b09cdcec1e
6 changed files with 107 additions and 70 deletions

View File

@@ -58,7 +58,7 @@ public class AdminPlaywrightTest : PlaywrightTest
AppBaseUrl = "http://localhost:" + appPort + "/";
// Start Admin project in-memory.
_webAppFactory.HostUrl = "http://localhost:" + appPort;
_webAppFactory.Port = appPort;
_webAppFactory.CreateDefaultClient();
await SetupPlaywrightBrowserAndContext();

View File

@@ -85,11 +85,11 @@ public class ClientPlaywrightTest : PlaywrightTest
ApiBaseUrl = "http://localhost:" + apiPort + "/";
// Start WebAPI in-memory.
_apiFactory.HostUrl = "http://localhost:" + apiPort;
_apiFactory.Port = apiPort;
_apiFactory.CreateDefaultClient();
// Start Blazor WASM in-memory.
_clientFactory.HostUrl = "http://localhost:" + appPort;
_clientFactory.Port = appPort;
_clientFactory.CreateDefaultClient();
await SetupPlaywrightBrowserAndContext();

View File

@@ -0,0 +1,66 @@
//-----------------------------------------------------------------------
// <copyright file="KestrelTestServer.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.E2ETests.Infrastructure;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
/// <summary>
/// A <see cref="TestServer"/> that uses Kestrel as the server.
/// </summary>
public class KestrelTestServer : TestServer, IServer
{
private readonly KestrelServer _server;
/// <summary>
/// Initializes a new instance of the <see cref="KestrelTestServer"/> class.
/// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use.</param>
public KestrelTestServer(IServiceProvider serviceProvider)
: base(serviceProvider)
{
// We get all the transport factories registered, and the first one is the correct one
// Getting the IConnectionListenerFactory directly from the service provider does not work
var transportFactory = serviceProvider.GetRequiredService<IEnumerable<IConnectionListenerFactory>>().First();
var kestrelOptions = serviceProvider.GetRequiredService<IOptions<KestrelServerOptions>>();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
_server = new KestrelServer(kestrelOptions, transportFactory, loggerFactory);
}
/// <inheritdoc />
async Task IServer.StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
// We need to also invoke the TestServer's StartAsync method to ensure that the test server is started
// Because the TestServer's StartAsync method is implemented explicitly, we need to use reflection to invoke it
await InvokeExplicitInterfaceMethod(nameof(IServer.StartAsync), typeof(TContext), [application, cancellationToken]);
// We also start the Kestrel server in order for localhost to work
await _server.StartAsync(application, cancellationToken);
}
/// <inheritdoc />
async Task IServer.StopAsync(CancellationToken cancellationToken)
{
await InvokeExplicitInterfaceMethod(nameof(IServer.StopAsync), null, [cancellationToken]);
await _server.StopAsync(cancellationToken);
}
private Task InvokeExplicitInterfaceMethod(string methodName, Type? genericParameter, object[] args)
{
var baseMethod = typeof(TestServer).GetInterfaceMap(typeof(IServer)).TargetMethods.First(m => m.Name.EndsWith(methodName));
var method = genericParameter == null ? baseMethod : baseMethod.MakeGenericMethod(genericParameter);
var task = method.Invoke(this, args) as Task ?? throw new InvalidOperationException("Task not returned");
return task;
}
}

View File

@@ -11,6 +11,7 @@ using System.Data.Common;
using AliasServerDb;
using AliasVault.Admin.Services;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
@@ -49,9 +50,9 @@ public class WebApplicationAdminFactoryFixture<TEntryPoint> : WebApplicationFact
}
/// <summary>
/// Gets or sets the URL the web application host will listen on.
/// Gets or sets the port the web application kestrel host will listen on.
/// </summary>
public string HostUrl { get; set; } = "https://localhost:5003";
public int Port { get; set; } = 5003;
/// <summary>
/// Returns the DbContext instance for the test. This can be used to seed the database with test data.
@@ -82,28 +83,23 @@ public class WebApplicationAdminFactoryFixture<TEntryPoint> : WebApplicationFact
/// <inheritdoc />
protected override IHost CreateHost(IHostBuilder builder)
{
var dummyHost = builder.Build();
builder.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port));
webHostBuilder.ConfigureServices(s => s.AddSingleton<IServer, KestrelTestServer>());
});
builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());
var host = builder.Build();
host.Start();
var host = base.CreateHost(builder);
// Get the DbContextFactory instance and store it for later use during tests.
_dbContextFactory = host.Services.GetRequiredService<IDbContextFactory<AliasServerDbContext>>();
// This delay prevents "ERR_CONNECTION_REFUSED" errors
// which happened like 1 out of 10 times when running tests.
Thread.Sleep(100);
return dummyHost;
return host;
}
/// <inheritdoc />
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseUrls(HostUrl);
SetEnvironmentVariables();
builder.ConfigureServices(services =>
@@ -129,19 +125,13 @@ public class WebApplicationAdminFactoryFixture<TEntryPoint> : WebApplicationFact
/// <param name="services">The <see cref="IServiceCollection"/> to modify.</param>
private static void RemoveExistingRegistrations(IServiceCollection services)
{
var descriptorsToRemove = new[]
{
services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextFactory<AliasServerDbContext>)),
services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AliasServerDbContext>)),
services.SingleOrDefault(d => d.ServiceType == typeof(VersionedContentService)),
};
var descriptorsToRemove = services.Where(d =>
d.ServiceType.ToString().Contains("AliasServerDbContext") ||
d.ServiceType == typeof(VersionedContentService)).ToList();
foreach (var descriptor in descriptorsToRemove)
{
if (descriptor != null)
{
services.Remove(descriptor);
}
services.Remove(descriptor);
}
}

View File

@@ -11,6 +11,7 @@ using System.Data.Common;
using AliasServerDb;
using AliasVault.Shared.Providers.Time;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
@@ -49,9 +50,9 @@ public class WebApplicationApiFactoryFixture<TEntryPoint> : WebApplicationFactor
}
/// <summary>
/// Gets or sets the URL the web application host will listen on.
/// Gets or sets the port the web application kestrel host will listen on.
/// </summary>
public string HostUrl { get; set; } = "https://localhost:5001";
public int Port { get; set; } = 5001;
/// <summary>
/// Gets the time provider instance for mutating the current time in tests.
@@ -87,28 +88,23 @@ public class WebApplicationApiFactoryFixture<TEntryPoint> : WebApplicationFactor
/// <inheritdoc />
protected override IHost CreateHost(IHostBuilder builder)
{
var dummyHost = builder.Build();
builder.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port));
webHostBuilder.ConfigureServices(s => s.AddSingleton<IServer, KestrelTestServer>());
});
builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());
var host = builder.Build();
host.Start();
var host = base.CreateHost(builder);
// Get the DbContextFactory instance and store it for later use during tests.
_dbContextFactory = host.Services.GetRequiredService<IDbContextFactory<AliasServerDbContext>>();
// This delay prevents "ERR_CONNECTION_REFUSED" errors
// which happened like 1 out of 10 times when running tests.
Thread.Sleep(100);
return dummyHost;
return host;
}
/// <inheritdoc />
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseUrls(HostUrl);
SetEnvironmentVariables();
builder.ConfigureServices(services =>
@@ -133,19 +129,13 @@ public class WebApplicationApiFactoryFixture<TEntryPoint> : WebApplicationFactor
/// <param name="services">The <see cref="IServiceCollection"/> to modify.</param>
private static void RemoveExistingRegistrations(IServiceCollection services)
{
var descriptorsToRemove = new[]
{
services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextFactory<AliasServerDbContext>)),
services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AliasServerDbContext>)),
services.SingleOrDefault(d => d.ServiceType == typeof(ITimeProvider)),
};
var descriptorsToRemove = services.Where(d =>
d.ServiceType.ToString().Contains("AliasServerDbContext") ||
d.ServiceType == typeof(ITimeProvider)).ToList();
foreach (var descriptor in descriptorsToRemove)
{
if (descriptor != null)
{
services.Remove(descriptor);
}
services.Remove(descriptor);
}
}

View File

@@ -8,7 +8,9 @@
namespace AliasVault.E2ETests.Infrastructure;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
/// <summary>
@@ -19,30 +21,19 @@ public class WebApplicationClientFactoryFixture<TEntryPoint> : WebApplicationFac
where TEntryPoint : class
{
/// <summary>
/// Gets or sets the URL the web application host will listen on.
/// Gets or sets the port the web application kestrel host will listen on.
/// </summary>
public string HostUrl { get; set; } = "https://localhost:5002";
/// <inheritdoc />
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseUrls(HostUrl);
}
public int Port { get; set; } = 5002;
/// <inheritdoc />
protected override IHost CreateHost(IHostBuilder builder)
{
var dummyHost = builder.Build();
builder.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port));
webHostBuilder.ConfigureServices(s => s.AddSingleton<IServer, KestrelTestServer>());
});
builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());
var host = builder.Build();
host.Start();
// This delay prevents "ERR_CONNECTION_REFUSED" errors
// which happened like 1 out of 10 times when running tests.
Thread.Sleep(100);
return dummyHost;
return base.CreateHost(builder);
}
}