How to Run Xunit in Parallel without Collision in DbContext - Primary Key?
Asked Answered
H

3

6

I am creating Xunit tests, with in-memory database. The tests execute correctly if run separately. However if run in parallel, they collide due to primary key issue in dbcontext.

What is best option to resolve this?

  1. Does Xunit have teardown capability? Heard xunit does not support this.
  2. Should I just run tests sequentially?
  3. Should go ahead and use different key Ids?

Trying to research xunit documentation, just started learning .net programming.

Error:

"System.ArgumentException : An item with the same key has already been added. Key: 2"

Code:

2 is primary key, used twice

public class ProductAppServiceTest
{
    public TestContext context;
    public IMapper mapper;
    public ProductAppServiceTest()
    {
        var options = new DbContextOptionsBuilder<TestContext>()
            .UseInMemoryDatabase(databaseName: "TestDatabase")
            .Options;
        context = new TestContext(options);

        ApplicationServicesMappingProfile applicationServicesMappingProfile = new ApplicationServicesMappingProfile();
        var config = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile(applicationServicesMappingProfile);
        });
        mapper = config.CreateMapper();
    }

    [Fact]
    public async Task Get_ProductById_Are_Equal()
    {
        context.Product.Add(new Product { ProductId = 2, ProductCode = "123", ProductName = "ABC" });
        context.SaveChanges();

        var ProductRepository = new ProductRepository(context);
        var ProductAppService = new ProductAppService(ProductRepository, mapper);
        var ProductDto = await ProductAppService.GetProductById(2);

        Assert.Equal("123", ProductDto.ProductCode);
    }

    [Fact]
    public async Task Get_ProductPrice_Are_Equal()
    {
        context.Product.Add(new Product { ProductId = 2, ProductCode = "123", ProductName = "ABC" });
        context.SaveChanges();

        var ProductRepository = new ProductRepository(context);
        var ProductAppService = new ProductAppService(ProductRepository, mapper);
        var ProductDto = await ProductAppService.GetProductById(2);
        //Goes into Enum table to validate price is 5
        Assert.Equal("5", ProductDto.Price);
    }
Homophony answered 13/8, 2019 at 19:2 Comment(2)
Teardown or create new context for each test.Purport
Instead of setting databaseName: "TestDatabase", have you tried setting databaseName: $"{Guid.NewGuid()}" so each of your parallel runs will use a different DB for the test? Using the same in-memory database might be why they're stepping on each other.Mra
C
6

The issue here is that you are using the same in-memory database for each test. While you could tear down and create the database for each test, its generally easier to use a unique database for each test.

Note: The reason each test is using the same database is because you are using a static database name.

XUnit calls the test-class constructor before each test, therefore you can create a unique in-memory database for each test by using a guid for the database name.

var options = new DbContextOptionsBuilder<TestContext>()
                      .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
                      .Options;
context = new TestContext(options);
Cruce answered 19/8, 2019 at 14:32 Comment(1)
How can we do something like this with ITestCollection and WebApplicationFactory? The samples on Microsoft official docs show this approach. If you have multiple ITestCollection<WAF> you will have 3 hosts running in memory at once. Those samples do not lend themselves well to spinning up a unique host with a unique DB config for every single test.Unhealthy
S
2

The example tests will not correctly execute in parallel as they are members of the same collection.

By default, each test class is a unique test collection. Tests within the same test class will not run in parallel against each other. - https://xunit.net/docs/running-tests-in-parallel

What is TestContext within the source code, is it a singleton of some kind? If so when running in parallel since both tests are in the same collection it will be shared between the 2 instances of the class which xUnit spins up. (Even though in the same collection a new copy of each class is created).

If you do need to share within a collection or assembly I would recommend using the TestFixture functionality built in, or implement an Assembly Fixture.

Setup and Teardown in xUnit are implemented via Constructor and IDisposable, or by implementing IAsyncLifetime on the test class. Note: when running in parallel the DisposeAsync will wait for the tests to run before starting each dispose.

public class UnitTest : IDisposable, IAsyncLifetime
{
    public UnitTest()
    {
        //First
    }

    public Task InitializeAsync()
    {
        //Second
    }

    [Fact]
    public async Task Test1()
    {
        //Third
    }

    public Task DisposeAsync()
    {
        //Forth
    }

    public void Dispose()
    {
        //Fifth
    }
}
Sandoval answered 21/8, 2019 at 14:40 Comment(0)
J
0

My simple answer to avoiding problems with multi-processing tasks changing the data that other tasks are using is to develop a data structure where the tasks are each passed an index that tells them which part of the structure to use, when the parallel jobs are all completed the data can then be recombined into the original main data structure that you normally use.

Janettajanette answered 23/8, 2019 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.