How to parametrize a xunit class fixture?
Asked Answered
C

2

9

xUnit offers the concept of (shared) class fixtures as explained in Shared Context between Tests. What I didn't figure out so far is if there is a way of parametrizing such class fixtures. For example, what if the DatabaseFixture should be enriched with some test data which depends on the test it's being run against? A test class might want to insert test data but only once and then run all its tests against that database (fixture).

In other words, what if the // ... initialize data in the test database ... from the documentation (referenced above) also depends on the test? Because not all tests might want to have the same test data. Actually, I even think that many times it's good practice that tests define their own test data to not couple tests on the level of test data.

What I'm doing so far as workaround is to offer a ConfiguredWith method that takes in a callback that is only being executed once. And in order to do so, I need to lazily postpone the initialization of the test database so that I'm sure that the configuration options are set. Something like:

public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
    DatabaseFixture fixture;

    public MyDatabaseTests(DatabaseFixture fixture)
    {
         this.fixture = fixture;
         this.fixture.ConfigureWith(new DatabaseFixtureOptions
         {
             InitTestData = db => db.Insert(...);
         };
    }

    // ... 
}

And this looks rather contrived for something that feels like a standard requirement when writing tests against a database.

And if xUnit doesn't offer this out-of-the-box, maybe someone has a better pattern on how to solve this.

This question seems to go in a similar direction but I'm not necessarily fixed on a solution that has that structure.

Corneliacornelian answered 12/11, 2020 at 8:15 Comment(4)
Hi Dejan, how are you? Why using Theory along MemberData or ClassData won't suffice your needs? it seems to me that what you are looking for is a set of data that is test dependent (MemberData/ClassData) instead of test class-dependent (Shared context: ClassFixture/CollectionFixture)Kerley
@RodRamírez people use class/collection fixtures when the creation of the test data is expensive (for example: preparing a physical DB), so that this fixture can be shared across the tests. And on top of this, I'm trying to see if there's a convenient way to initialize the test data in the DB in a parametrized way.Corneliacornelian
If you want all tests to have the same set of data before they even start, you should look at my answer below. Now, regarding the set of data related to each of the tests inside a given test class, you can then check out the memberData/ClassData approach. @CorneliacornelianKerley
Hey Dejan, wondering if you found a good way of achieving this? ThanksRadiotransparent
W
0

I learn the hard way that trying to share the entity framework database context over IClassFixture or CollectionFixtures would eventually end up in tests being polluted with another test data or deadlock/race conditions due to the parallel execution of xUnit, entity framework throwing exceptions because it already tracked that object with a given Id and more headaches like that. Personally, I would kindly recommend that for your specific use cause, stick the database context creation/cleanup within the constructor/dispose alternative such as:

    public class TestClass : IDisposable
    {
        DatabaseContext DatabaseContext;

        public TestClass()
        {
            var options = new DbContextOptionsBuilder<DatabaseContext>()
              .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
              .Options;

            DatabaseContext = new DatabaseContext(options);

            //insert the data that you want to be seeded for each test method:
            DatabaseContext.Set<Product>().Add(new Product() { Id = 1, Name = Guid.NewGuid().ToString() });
            DatabaseContext.SaveChanges();
        }

        [Fact]
        public void FirstTest()
        {
            var product = DatabaseContext.Set<Product>().FirstOrDefault(x => x.Id == 1).Name;
            //product evaluates to => 0f25a10b-1dfd-4b4b-a69d-4ec587fb465b
        }

        [Fact]
        public void SecondTest()
        {
            var product = DatabaseContext.Set<Product>().FirstOrDefault(x => x.Id == 1).Name;
            //product evaluates to => eb43d382-40a5-45d2-8da9-236d49b68c7a
            //It's different from firstTest because is another object
        }

        public void Dispose()
        {
            DatabaseContext.Dispose();
        }
    }

Of course you can always do some refinement, but the idea is there

Weatherford answered 15/11, 2020 at 14:53 Comment(2)
Thanks for your effort. Your suggestion is simple and thus powerful and in many cases should be the default of how to do smoke testing against a DB context. However, this is nothing new to me and it still does not my answer. In some cases we have a more complex setup (= higher level system tests) and thus reasons why we want to have a shared parametrized setup.Corneliacornelian
I understand. I couldn't find something that suits your needs anywhere on the web either. However, I remember that I once had some issues with xUnit framework and the supporters have a slack channel where you can find a programmer that is more into this kind of scenario than me, you might find that helpfulKerley
H
0

I don't have the reputation to reply to Rod, but I believe you can easily correct this issue by adding your test classes to a collection:

[Collection("Sequential")]

public class TestClass : IDisposable {...}

This would force the tests to be completed across many classes, one at a time so long as they have to same collection name. I am unsure how to implement a fixture to the collection though. I did find this article though: https://www.programmingwithwolfgang.com/xunit-getting-started/ Hopefully that will help.

I would personally suggest implementing fixtures as long as you use the Collection() annotation for integration tests.

Hillis answered 12/10, 2022 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.