C# Integration tests with XUnit, SQL Server Database and transactions

6rqinv9w  于 2023-08-02  发布在  C#
关注(0)|答案(1)|浏览(110)

I've been working on adding integration tests with XUnit and SQL Server. I'm facing a challenge: Have a clean database state before and after each test.

This is an example of my test:

[Fact]
        public async Task GetAsync_ReturnsTaskOfRoleModelList()
        {
            using var context = _factory.CreateContext();
            _rolesRepository = () => _factory.CreateRoleRepository(context);

            await context.Database.BeginTransactionAsync();

            await _rolesRepository().CreateAsync(new RoleModel
            {
                Id = Guid.NewGuid(),
                Name = "Role A",
                Description = "Role A Description",
                CreatedDate = DateTime.Now,
                UpdatedDate = DateTime.Now,
                Deleted = false,
            });

            var response = await _client.GetFromJsonAsync<List<RoleModel>>("/api/roles");

            response.ShouldNotBeNull();
            response.ShouldBeOfType<List<RoleModel>>();
            response.Count.ShouldBeGreaterThan(0);

            context.ChangeTracker.Clear();
        }

First a context is created, and then a repository is also created with that context. Then, we start the transaction, add the data to the database, send the request and check the response, to finally clear the transaction. Unfortunately this test is failing.

Using this microsoft article as reference: https://learn.microsoft.com/en-us/ef/core/testing/testing-with-the-database .

This line does not create a new Role in the database:

await _rolesRepository().CreateAsync(new RoleModel
{
   Id = Guid.NewGuid(),
   Name = "Role A",
   Description = "Role A Description",
   CreatedDate = DateTime.Now,
   UpdatedDate = DateTime.Now,
   Deleted = false,
});

If I remove the lines related to the database transaction, the test passes and the role is added.

How can I solve this?

ldfqzlk8

ldfqzlk81#

We use the following two methods whenever we want our test DB to be reset:

myDbContext.Database.EnsureDeleted();
myDbContext.Database.EnsureCreated();

This code is in a protected method within a testing base class that all integration test classes inherit. The method looks something like this:

protected static MyDbContext GetInitializedDbContext(bool createNewDb)
{
   var builder   = new ConfigurationBuilder()
                       .SetBasePath(Directory.GetCurrentDirectory())
                       .AddJsonFile(appsettings.json);

  IConfigurationRoot config = builder.Build();

  var options = new DbContextOptionsBuilder<MyDbContext>()                 
           .UseSqlServer(config.GetConnectionString("IntegrationTestingDb")
           .Options;

  var myDbContext = new MyDbContext(options);
  
  if(createNewDb)
  {
    myDbContext.Database.EnsureDeleted();
    myDbContext.Database.EnsureCreated();
  }

  return myDbContext;
}

The appsettings.json file is attached to the integration testing assembly and, as is probably obvious, contains whatever connection string you want to use for your testing DB. You could save the DbContext constructor arguments in static fields, too.

The base class's static constructor calls this method with an argument of true.

You can call the method from your child class's constructors or from the individual methods with whatever argument is most appropriate. No need for transactions.

Your post has been around for a while, but I hope this helps.

相关问题