I am integration testing a .NET API that connects to MongoDB using the connection string and database name values stored in an app.settings.json file. I am using xUnit and Test Containers to carry out the tests. When the tests are carried out, the environment variable changes and prompts my Program.cs to connect to a test database. I originally wanted to wipe and reseed this test database between each test or carry out some kind of rollback but I could not find a simple way to do this.
I am now trying to instead create a local mock of the test database, however when I build and connect to the API in the tests, it still points to the live test database instead of the local one. I want to be able to change the connection string in an app.settings.json to the connection string that is generated in the test fixture every time the test runs but I am not sure how to do this. I have looked into creating a custom WebFactory and dependency injection but am not sure how to implement this with the code I have so far.
My Program.cs file looks like this
public partial class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
bool isTestEnvironment = Helper.IsTestEnvironment();
builder.Services.Configure<ArtsieDatabaseSettings>(
builder.Configuration.GetSection(isTestEnvironment ? "ArtsieTestDatabase" : "ArtsieDatabase"));
builder.Services.AddSingleton<ArtsieService>();
builder.Services.AddControllers()
.AddJsonOptions(
options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Art API", Description = "Browse some beautiful art", Version = "v1" });
});
var app = builder.Build();
app.UseHttpsRedirection();
app.MapControllers();
//... rest of swagger documentation and endpoints
app.Run();
}
}
My test file looks like this:
namespace art_api.Tests;
using ArtsieApi.Models;
using Microsoft.AspNetCore.Mvc.Testing;
using Newtonsoft.Json;
using System.Net;
using System.Text;
using MongoDB.Driver;
using Testcontainers.MongoDb;
using Xunit;
public class DatabaseFixture : IDisposable
{
private readonly IMongoDatabase _database;
private readonly MongoDbContainer _container;
public DatabaseFixture()
{
Environment.SetEnvironmentVariable("TEST_ENVIRONMENT", "true");
_container = new MongoDbBuilder().Build();
_container.StartAsync().Wait();
var connectionString = _container.GetConnectionString();
var client = new MongoClient(connectionString);
_database = client.GetDatabase("artsie-test");
// Seed initial data
SeedTestData();
}
private void SeedTestData()
{
var artCollection = _database.GetCollection<List<Art>>("art");
var commentsCollection = _database.GetCollection<List<Comment>>("comments");
var usersCollection = _database.GetCollection<List<User>>("users");
}
public void Dispose()
{
_database.DropCollection("art");
_database.DropCollection("comments");
_database.DropCollection("users");
_container.DisposeAsync().GetAwaiter().GetResult();
Environment.SetEnvironmentVariable("TEST_ENVIRONMENT", null);
}
}
public class Endpoints : IClassFixture<DatabaseFixture>
{
private readonly DatabaseFixture _fixture;
public Endpoints(DatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact(DisplayName = "200: GET /")]
public async Task TestRootEndpoint()
{
await using var application = new WebApplicationFactory<Program>();
using var client = application.CreateClient();
var response = await client.GetAsync("/");
var content = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello World!", content);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
//... rest of tests
}
2
Answers
So after a lot of frustration and with the help of Google/YouTube/ChatGPT I found a solution!
To start with, I created a CustomWebApplicationFactory class that grabs the local connection string generated in my Fixture class and inserts it into a new instance of DatabaseSettings.
I also fixed the SeedTestData function to populate the local database with the test data stored in some .json files.
My tests now use CustomWebApplicationFactory when building the application for tests. Pretty sure there is more that can be refactored but I'm glad to have found a solution!
In case you have
IOptionsMonitor
, better to:Like explained here