ClassInitialize, ClassCleanup, and Sharing Data Across Tests in XUnit2

So far in this series on migrating from MSTest to XUnit, we have looked at:

In this post, we will look at how we can share setup and cleanup code across tests in a test class in XUnit. MSTest allows you to define shared setup and cleanup code for an entire test class by using methods decorated with the ClassInitialize and ClassCleanup attributes. Unlike their counterparts, TestInitialize and TestCleanup, methods decorated with these class-level attributes are executed just once per class, rather than once per test in the class. Using these class-level attributes, we can execute code, generate fixture objects, and load test data that can be used across all tests in the class without having the overhead of repeating this for every test in the class. This is useful when that initialization or cleanup is expensive, such as creating a database connection, or loading several data files.

As we have seen so far, XUnit is light on decorating non-test methods with attributes, instead relying on language syntax that mirrors the purpose of the code. In the case of TestInitialize and TestCleanup, XUnit uses the test class constructor and IDisposable. It should come as no surprise that this pattern is also used when it comes to class-level initialization and cleanup.

IClassFixture<T>

There are two parts to shared initialization and cleanup in XUnit: declaring what shared items a test class uses, and referencing them within test methods.

To declare specific setup is required, a test class must be derived from IClassFixture for each shared setup/cleanup. The T in IClassFixture is the actual type responsible for the initialization and cleanup via its constructor and IDisposable implementation.

public class MyFixture : IDisposable
{
    public MyFixture()
    {
        // Setup here
    }

    public void Dispose()
    {
        // Cleanup here
    }
}
public class MyTests : IClassFixture<MyFixture>
{
    [Fact]
    public void Test()
    {
        // Do something knowing that MyFixture was instantiated
    }
}

The XUnit test runner sees that your test class is deriving from IClassFixture and ensures that an instance of MyFixture is created before your tests are run and disposed of when all the tests are completed. I really like this approach over the MSTest equivalent, as it moves the setup and initialization from being about the test class to being about the test fixture, the thing being setup. You can even have more than one fixture, so if you use two databases in your tests, you can have one fixture for each database and explicitly specify the use of each. It also means that you can set things that are supposed to be immutable for the duration of tests to be readonly and enforce that immutability. This is even clearer when referencing fixtures in tests.

public class MyTests : IClassFixture<MyFixture>
{
    private readonly MyFixture _fixture;

    public MyTests(MyFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public void Test()
    {
        // Do something with _fixture
    }
}

As shown in the preceding example, to reference a test fixture in your test class methods, you just need to add a corresponding argument to the constructor and XUnit will inject the fixture. You can then use the fixture, and assign it or something obtained from it to a member variable of your class. Not only that, but you can mark that member as readonly and be explicit about what tests can and cannot do to your test state. Personally, this approach to shared initialization and cleanup feels much more intuitive. I can easily reuse my initialization and setup code without cluttering my test classes unnecessarily, and I can be explicit about the immutability of any shared state or setup.

And that is it; now you not only know how to share repeatable setup across tests (as provided by TestInitialize and TestCleanup in MSTest), but also how to do the same for setup across the whole test class (as MSTest does with ClassIntialize and ClassSetup).

But, what of AssemblyInitialize and AssemblyCleanup? Well, that's probably a good place to start in the next post. As always, you are welcome to leave a comment letting me know how you are liking this series on migrating to XUnit, or perhaps bringing up something that you'd like me to cover.

One thought on “ClassInitialize, ClassCleanup, and Sharing Data Across Tests in XUnit2”

  1. Thats great. I want to implement Extent Reporting with Xunit, but I've not found a good ariticle on how to do it with Xunit. Any help is greatly appreciated

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.