1

I am setting up integration tests for my Asp.Net Core API, using xunit. So far, I have two tests setup:

public class BasicTests : IClassFixture<CustomWebApplicationFactory<EcommerceWebAPI.Startup>>
{
    private readonly CustomWebApplicationFactory<EcommerceWebAPI.Startup> _factory;

    public BasicTests(CustomWebApplicationFactory<EcommerceWebAPI.Startup> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("84.247.85.224")]
    public async Task AuthenticateUserTest(string ip)
    {
        // Arrange
        var client = _factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
        {
            Content = new StringContent("{\"Username\":\"test@test.com\",\"Password\":\"test\"}", Encoding.UTF8,
                "application/json")
        };
        request.Headers.Add("X-Real-IP", ip);

        var response = await client.SendAsync(request);
        responseStatusCode = (int)response.StatusCode;

        // Assert
        Assert.Equal(200, responseStatusCode);
    }

    [Theory]
    [InlineData("84.247.85.224")]
    [InlineData("84.247.85.225")]
    [InlineData("84.247.85.226:6555")]
    [InlineData("205.156.136.211, 192.168.29.47:54610")]
    public async Task SpecificIpRule(string ip)
    {
        // Arrange
        var client = _factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        for (int i = 0; i < 4; i++)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
            {
                Content = new StringContent("{\"Username\":\"Test\",\"Password\":\"Test\"}", Encoding.UTF8,
                    "application/json")
            };
            request.Headers.Add("X-Real-IP", ip);

            var response = await client.SendAsync(request);
            responseStatusCode = (int)response.StatusCode;
        }

        // Assert
        Assert.Equal(429, responseStatusCode);
    }
}

My custom application factory:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var descriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                     typeof(DbContextOptions<EntityContext>));

            services.Remove(descriptor);

            services.AddDbContext<EntityContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryDbForTesting");
            });

            var sp = services.BuildServiceProvider();

            using (var scope = sp.CreateScope())
            {
                var scopedServices = scope.ServiceProvider;
                var db = scopedServices.GetRequiredService<EntityContext>();
                var logger = scopedServices
                    .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

                db.Database.EnsureDeleted();

                db.Database.EnsureCreated();

                try
                {
                    Utilities.InitializeDbForTests(db);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "An error occurred seeding the " +
                                        "database with test messages. Error: {Message}", ex.Message);
                }
            }
        });
    }
}

I seed the database with:

static class Utilities
{
    public static void InitializeDbForTests(EntityContext db)
    {
        var customer = new Customer
        {
            Custnmbr = "AARONFIT0001"
        };

        db.Customers.Add(customer);
        
        var user = new User
        {
            Id = 1,
            Customer = customer,
            EmailAddress = "test@test.com",
            PasswordHash = BCrypt.Net.BCrypt.HashPassword("test")
        };

        db.Users.Add(user);
        db.SaveChanges();
    }
}

Both test cases pass. But, I get an entity already exists exception in the log when the second test tries to run the seed again. I thought putting the line db.Database.EnsureDeleted() in my CustomWebApplicationFactory would ensure each test starts with a brand new InMemory Database. How do I ensure a new InMemory seeded Database for each test?

Randy
  • 1,137
  • 16
  • 49
  • Check out https://github.com/v-zubritsky/Reseed for a real db state management, once you decide that in-memory isn't enough anymore. – Uladzislaŭ Nov 13 '21 at 16:11

1 Answers1

1

After some additional searching, one particular StackOverflow article Resetting In-Memory database between integration tests led me to this conclusion. I need to Initialize the factory for each test...

public class AuthenticationTests
{
    [Theory]
    [InlineData("84.247.85.224")]
    public async Task AuthenticateUserTest(string ip)
    {
        // Arrange
        using var factory = new CustomWebApplicationFactory<EcommerceWebAPI.Startup>();
        var client = factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
        {
            Content = new StringContent("{\"Username\":\"test@test.com\",\"Password\":\"test\"}", Encoding.UTF8,
                "application/json")
        };
        request.Headers.Add("X-Real-IP", ip);

        var response = await client.SendAsync(request);
        responseStatusCode = (int)response.StatusCode;

        // Assert
        Assert.Equal(200, responseStatusCode);
    }

    [Theory]
    [InlineData("84.247.85.224")]
    [InlineData("84.247.85.225")]
    [InlineData("84.247.85.226:6555")]
    [InlineData("205.156.136.211, 192.168.29.47:54610")]
    public async Task SpecificIpRule(string ip)
    {
        // Arrange
        using var factory = new CustomWebApplicationFactory<EcommerceWebAPI.Startup>();
        var client = factory.CreateClient();
        int responseStatusCode = 0;

        // Act
        for (int i = 0; i < 4; i++)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "/api/Users/AuthenticateUser")
            {
                Content = new StringContent("{\"Username\":\"Test\",\"Password\":\"Test\"}", Encoding.UTF8,
                    "application/json")
            };
            request.Headers.Add("X-Real-IP", ip);

            var response = await client.SendAsync(request);
            responseStatusCode = (int)response.StatusCode;
        }

        // Assert
        Assert.Equal(429, responseStatusCode);
    }
}

Heres hoping the SO community can come up with a more elegant solution...

Randy
  • 1,137
  • 16
  • 49