How to Update the HttpConfiguration AFTER the Owin TestServer creation - c#

The http configuration is setup in te Startup class usually which is bound to the Create method.
But what if I want to start an owin server ONE time for ALL tests but update its http configuration depending on each test needs?
This is not possible. The server object has nothing useful.
using (var server = TestServer.Create<Startup>())
{
var data = server.HttpClient.GetAsync("/api/data);
}
What I want to do for CRUD integration tests is stub the service methods
// Do it ONE time fall ALL tests
WebApiConfig.Register(config);
WebServicesConfig.Register(config);
// Do it individually for each test, Update DI registerations with Fake components per test method
var builder = new ContainerBuilder();
var mockContext = new Mock<TGBContext>();
var mockService = new Mock<SchoolyearService>(mockContext.Object);
mockService.Setup<Task<IEnumerable<SchoolyearDTO>>>(c => c.GetSchoolyearsAsync()).Returns(Task.FromResult(Enumerable.Empty<SchoolyearDTO>()));
// builder.RegisterInstance<TGBContext>(); ****** NO NEED for this it works without registering the ctor parameter dependency
builder.RegisterInstance<SchoolyearService>(mockService.Object);
builder.Update(((AutofacWebApiDependencyResolver)config.DependencyResolver).Container as IContainer);
At the moment I am forced to create a TestServer per Test method.
Thats a total overhead in time.
SOLUTION
Make the HttpConfiguration static and this code should work:
var builder = new ContainerBuilder();
var mockContext = new Mock<TGBContext>();
var mockService = new Mock<SchoolyearService>(mockContext.Object);
mockService.Setup<Task<IEnumerable<SchoolyearDTO>>>(c => c.GetSchoolyearsAsync()).Returns(Task.FromResult(Enumerable.Empty<SchoolyearDTO>()));
builder.RegisterInstance<SchoolyearService>(mockService.Object);
builder.Update(((AutofacWebApiDependencyResolver)Configuration.DependencyResolver).Container as IContainer);

if you want to start the OWIN server once for all tests.
declare a base class for all your test classes
declare a private static readonly variable in your base class for the OWIN Server.
private static readonly TestServer _webServer = TestServer.Create<Startup>();
declare a protected static variable to expose this server to all the tests.
protected static TestServer WebServer { get { return _webServer; } }
this should solve your problem of instantiating the web server only once for all the test runs. and if you don't want to initialize the web server so early, you can use lazy instantiation etc. but the point is to make it static and initialize it at definition time itself, so that it is once per app domain.
as for getting access to the HttpConfiguration in your unit tests.. here is one possible way..
in your Startup.cs, declare a public property...
public static HttpConfiguration HttpConfiguration { get; private set; }
initialize this variable in the configure method of the Startup.cs class.
HttpConfiguration = new HttpConfiguration();
HttpConfiguration.MapHttpAttributeRoutes();
// do more stuff.. setting resolver etc.
// and then finally
app.UseWebApi(HttpConfiguration);
This HttpConfiguration property is your web api's configuration and because we made it a public property, it should be accessible in your test project and to all tests.
Startup.HttpConfiguration
you can access it to modify the dependency resolver definitions etc.
Startup.HttpConfiguration.DependencyResolver
please note that you can update the DependencyResolver definitions even after you initialize the web server... the updated definitions will still have their effect.
if you're concerned about your HttpConfiguration just publicly available, then make the Startup class internal and make it visible only to your unit test project.

Related

ASP.NET Core Integration tests with dotnet-testcontainers

I am trying to add some integration tests for a aspnetcore v6 webapi following the docs - https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0#aspnet-core-integration-tests.
My webapi database is SQLServer. I want the tests to be run against an actual SQLServer db and not in-memory database. I came across dotnet-testcontainers - https://github.com/HofmeisterAn/dotnet-testcontainers and thinking of using this so I do not need to worry about the resetting the db as the container is removed once test is run.
So this is what I plan to do:
Start-up a SQLServer testcontainer before the test web host is started. In this case, the test web host is started using WebApplicationFactory. So the started wen host has a db to connect with. Otherwise the service start will fail.
Run the test. The test would add some test data before its run.
Then remove the SQLServer test container along with the Disposing of test web host.
This way the I can start the test web host that connects to a clean db running in a container, run the tests.
Does this approach sound right? OR Has someone used dotnet-testcontainers to spin up a container for their application tests and what approach worked.
I wrote about this approach here.
You basically need to create a custom WebApplicationFactory and replace the connection string in your database context with the one pointing to your test container.
Here is an example, that only requires slight adjustments to match the MSSQL docker image.
public class IntegrationTestFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>, IAsyncLifetime
where TProgram : class where TDbContext : DbContext
{
private readonly TestcontainerDatabase _container;
public IntegrationTestFactory()
{
_container = new TestcontainersBuilder<PostgreSqlTestcontainer>()
.WithDatabase(new PostgreSqlTestcontainerConfiguration
{
Database = "test_db",
Username = "postgres",
Password = "postgres",
})
.WithImage("postgres:11")
.WithCleanUp(true)
.Build();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveProdAppDbContext<TDbContext>();
services.AddDbContext<TDbContext>(options => { options.UseNpgsql(_container.ConnectionString); });
services.EnsureDbCreated<TDbContext>();
});
}
public async Task InitializeAsync() => await _container.StartAsync();
public new async Task DisposeAsync() => await _container.DisposeAsync();
}
And here are the extension methods to replace and initialize your database context.
public static class ServiceCollectionExtensions
{
public static void RemoveDbContext<T>(this IServiceCollection services) where T : DbContext
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<T>));
if (descriptor != null) services.Remove(descriptor);
}
public static void EnsureDbCreated<T>(this IServiceCollection services) where T : DbContext
{
var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var scopedServices = scope.ServiceProvider;
var context = scopedServices.GetRequiredService<T>();
context.Database.EnsureCreated();
}
}
There are another two ways to leverage Testcontainers for .NET in-process into your ASP.NET application and even a third way out-of-process without any dependencies to the application.
1. Using .NET's configuration providers
A very simple in-process setup passes the database connection string using the environment variable configuration provider to the application. You do not need to mess around with the WebApplicationFactory. All you need to do is set the configuration before creating the WebApplicationFactory instance in your tests.
The example below passes the HTTPS configuration incl. the database connection string of a Microsoft SQL Server instance spun up by Testcontainers to the application.
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "https://+");
Environment.SetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Path", "certificate.crt");
Environment.SetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Password", "password");
Environment.SetEnvironmentVariable("ConnectionStrings__DefaultConnection", _mssqlContainer.ConnectionString);
_webApplicationFactory = new WebApplicationFactory<Program>();
_serviceScope = _webApplicationFactory.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
_httpClient = _webApplicationFactory.CreateClient();
This example follows the mentioned approach above.
2. Using .NET's hosted service
A more advanced approach spins up the dependent database and seeds it during the application start. It not just helps writing better integration tests, it integrates well into daily development and significantly improves the development experience and productivity.
Spin up the dependent container by implementing IHostedService:
public sealed class DatabaseContainer : IHostedService
{
private readonly TestcontainerDatabase _container = new TestcontainersBuilder<MsSqlTestcontainer>()
.WithDatabase(new DatabaseContainerConfiguration())
.Build();
public Task StartAsync(CancellationToken cancellationToken)
{
return _container.StartAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _container.StopAsync(cancellationToken);
}
public string GetConnectionString()
{
return _container.ConnectionString;
}
}
Add the hosted service to your application builder configuration:
builder.Services.AddSingleton<DatabaseContainer>();
builder.Services.AddHostedService(services => services.GetRequiredService<DatabaseContainer>());
Resolve the hosted service and pass the connection string to your database context:
builder.Services.AddDbContext<MyDbContext>((services, options) =>
{
var databaseContainer = services.GetRequiredService<DatabaseContainer>();
options.UseSqlServer(databaseContainer.GetConnectionString());
});
This example uses .NET's hosted service to leverage Testcontainers into the application start. By overriding the database context's OnModelCreating(ModelBuilder), this approach even takes care of creating the database schema and seeding data via Entity Framework while developing and testing.
3. Running inside a container
In some use cases, it might be necessary or a good approach to run the application out-of-process and inside a container. This increases the level of abstractions and removes the direct dependencies to the application. The services are only available through their public API (e.g. HTTP(S) endpoint).
The configuration follows the same approach as 1. Use environment variables to configure the application running inside a container. Testcontainers builds the necessary container image and takes care of the container lifecycle.
_container = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage(Image)
.WithNetwork(_network)
.WithPortBinding(HttpsPort, true)
.WithEnvironment("ASPNETCORE_URLS", "https://+")
.WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Path", _certificateFilePath)
.WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Password", _certificatePassword)
.WithEnvironment("ConnectionStrings__DefaultConnection", _connectionString)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(HttpsPort))
.Build();
This example sets up all necessary Docker resources to spin up a throwaway out-of-process test environment.

Changing RedisCacheOptions after registering the StackExchangeRedisCache middleware

I'm trying to use Redis in my Asp.Net Core project. I added the packages StackExchange.Redis and Microsoft.Extensions.Caching.StackExchange.Redis to my project and the following code to register the middleware:
services.AddStackExchangeRedisCache(options =>
{
// do config
});
So far that works fine. But in this project Configuration settings are provided by a custom ConfigurationService which is pulling them from an external source. If changes in any of the configuration settings concerning Redis are detected I need to be able to reconfigure the StackExchangeRedisCache middleware.
How would I do that?
If I get the question right, does the case is... The project configuration setting was customize to fetch from another source by some interval ? Therefore, the setting on redis could change by time to time ?
If that's the case, I think we got to drop usage of AddStackExchangeRedisCache extensions. If you want to keep using this, here is some problem that got to solve:
Remove RedisCache singleton instance that previously register in ServiceCollection.
Remove config for RedisCacheOptions previously register in ServiceCollection
Register new RedisCacheOptions and RedisCache in ServiceCollection
Re-initialize ServiceProvider somehow
These step should be done atomicity, otherwise, exceptions likely to raise.
Quite cumbersome... does it ?
Another approach was to create a placeholder for IDistributedCache instance, register it as singleton and update the IDistributedCache instance if change was detected from the Configuration.
// Place holder, and register it as singleton
public class CacheAccessor : IDisposable
{
private RedisCache RedisCache { get; set; }
public void SetRedisCache(IOptions<RedisCacheOptions> options)
{
RedisCache?.Dispose();
RedisCache = new RedisCache(options);
}
public IDistributedCache LatestRedisCache => RedisCache;
public void Dispose()
{
RedisCache?.Dispose();
}
}
// Register it
services.AddSingleton<CacheAccessor>();
// Initialize it for the first time (write some extension or put it somewhere in program.cs would be nice.
// Run after the webhost instance got built and before it run)
// host was an instance of IHost
using var scope = host.Services.CreateScope();
var serviceProvider = scope.ServiceProvider;
serviceProvider.GetRequiredService<CacheAccessor>().SetRedisCache(Pass the option here).
// By time to time, if the config changed, extract the option and SetRedisCache again.
// And of course, get access to the cache would be difference
public IActionResult TestMethod([FromServices] CacheAccessor accessor)
{
var cache = accessor.LatestRedisCache;
}

How to use settings from integration testing project in the Startup class of api project?

I'm trying to write integration tests for web api, I have two appsettings.json files one for api project and other for integration tests. I want to use the values(azure storage connection strings) from integration testing project in Startup class of web api.
I have tried creating CustomWebApplicatonFactory it didn't work since the Startup class of web api gets settings like shown below.
//Configure services method in Startup class
public virtual void ConfigureServices(IServiceCollection services)
{
var settings = ConfigurationProvider.GetConfiguration();
services.TryAddSingleton(settings);
services.AddHttpClient();
var azureTableStorageConnectionString =
settings["AzureMainStoreConnectionStringSecretName"];
//Other Startup related code
}
I want to change the value of "azureTableStorageConnectionString" from my integration test project. Help and suggestions are much appreciated.
First of all, instead of using the static function ConfigurationProvider.GetConfiguration() inject IConfiguration into your Startup class. The host that is usually defined in the Program class builds that for you so you can inject it.
Then instead of using WebApplicationFactory<TStartup> you can build a test-host yourself like this.
[Fact]
public async Task LoadWeatherForecastAsync()
{
var webHostBuilder = new WebHostBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
builder.SetBasePath(AppContext.BaseDirectory);
// this is an appsettings file located in the test-project!
builder.AddJsonFile("appsettings.Testing.json", false);
})
.UseStartup<Startup>();
var host = new TestServer(webHostBuilder);
var response = await host.CreateClient().GetAsync("weatherforecast");
Assert.True(response.IsSuccessStatusCode);
}
I've created a sample on github. You can clone it and try it out.

How to get all the Services from Startup.cs

How do I obtain all the services after StartupClass has run?
We have a Startup Configure Services class which
runs multiple dependency injection.
serviceCollection.AddScoped<IDepartmentRepository, DepartmentRepository>();
serviceCollection.AddScoped<IDepartmentAppService, DepartmentAppService>();
Team is using Xunit Test Project and wants to grab all the Service collection list for Xunit.
Need services from this below:
new WebHostBuilder()
.UseContentRoot("C:\\Test\\Test.WebAPI")
.UseEnvironment("Development")
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath("C:\\Test\\Test.WebAPI")
.AddJsonFile("appsettings.json")
.Build()).UseStartup<Startup>());
Services in ServiceCollection are represented by the ServiceDescriptor class. You can get a list of all service descriptors by chaining a ConfigureTestServices method to your WebHostBuilder (you have to install Microsoft.AspNetCore.TestHost package)
using Microsoft.AspNetCore.TestHost;
....
IWebHost CreateWebHost(out ServiceDescriptor[] allServices)
{
ServiceDescriptor[] grabbedServices = null;
var host = new WebHostBuilder()
// ... other stuff ...
.UseStartup<Startup>()
// add the following call:
.ConfigureTestServices(services => {
// copy all service descriptors
grabbedServices = services.ToArray();
})
// build the host now to trigger the above chain
.Build();
allServices = grabbedServices;
return host;
}
It is then convenient to use like this:
[Fact]
public void TestSomething()
{
// arrange
var host = CreateWebHost(out var services);
// ... do something with the services
// act
// assert
}
The reason we have to use ConfigureTestServices (and not ConfigureServices) is that ConfigureServices is always invoked before the methods in the Startup class, whereas ConfigureTestServices is invoked afterward.
UPDATE
Of course, it's also possible to just store the reference to ServiceCollection in a variable/field, and enumerate the services whenever necessary.
So I understood your question that you want to test you services and have problems with loading them in test project.
I'd say you don't need to. In unit tests every time you test a single service. This service you instantiate directly:
var target= new ServiceToTest();
So you reference your testED project from your test project. In case your implementation is internal, add to your testED project an AssemblyInfo.cs file and put the following inside:
[assembly: InternalsVisibleTo("MyTestProject")]
Whenever you test your MyServiceToTest1, in case it has a dependecy to MyServiceTest2, you don't have to load or instantiate MyServiceTest2. Instead you mock it, e.g. with moq.

Routes in AspNetCore.TestHost depend on Startup.cs location?

I am trying to test my ASP.NET Core Web Application with Microsoft.AspNetCore.TestHost. It works fine this way (result has status 200):
var server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
var client = server.CreateClient();
var result = await client.GetAsync(someRequestUrl);
In this case the real Startup class from the API project is used.
However, I don't want to use the real Startup class in my integration test. The main reason is the need to mock some stuff that gets wired during application startup. For example, the database server to be used. It can be done in a very elegant way by defining a virtual method in Startup.cs:
public virtual void SetupDbContext(IServiceCollection services)
{
services.AddDbContext<TbsDb>(options =>
options.UseSqlServer("someConnectionString"));
}
Then I create a new class, which inherits from the original Startup class and overrides this method to use Sqlite, in-memory database or whatever:
public override void SetupDbContext(IServiceCollection services)
{
services.AddDbContext<TbsDb>(
options => options.UseSqlite("someConnectionString"));
}
This also works well with TestHost if the new class is in the same API project.
Obviously, I don't want this class which is used for testing to be there. But if I move it to integration tests project and create a TestServer there, the same test fails because the result has status 404. Why is it happening? It still inherits from the Startup class, which is in the API project. Thus I expect all the routes to work the same no matter where the TestStartup class is. Can it be solved somehow?

Categories