AssemblyInitialize, AssemblyCleanup and Sharing State Between Test Classes in XUnit

We have covered quite a bit in this series on migrating from MSTest to XUnit and we have not even got to the coolest bit yet; data-driven theories. If that is what you are waiting for, you will have to wait a little longer. Before we get there, I want to cover one last piece of test initialization as provided in MSTest by the AssemblyInitialize and AssemblyCleanup attributes.

As we saw in previous posts, we can use the test class constructor and Dispose() for TestInitialize and TestCleanup, and IClassFixture and fixture classes for ClassInitialize and ClassCleanup.  For the assembly equivalents, we use collections and the ICollectionFixture interface.

A collection is defined by a set of test classes and a collection definition. A test class is designated as being part of a specific collection by decorating the class with the Collection attribute, which provides the collection name. A corresponding class decorated with the CollectionDefinition attribute should also exist as this is where any collection fixtures are defined. All classes that share the same collection name will share the collection fixtures from which the definition class derives.

public class MyFirstFixture
{
    public MyFirstFixture()
    {
        // Some initialization
    }
}

public class MySecondFixture : IDisposable
{
    public MySecondFixture()
    {
        // Some initialization
    }

    public void Dispose()
    {
        // Some cleanup
    }
}

[CollectionDefinition("MyCollection")]
public class MyCollectionDefinition : ICollectionFixture<MyFirstFixture>, ICollectionFixture<MySecondFixture>
{
    //Nothing needed here
}

[Collection("MyCollection")]
public class TestClass1
{
    [Fact]
    public void TestMethod1()
    {
        // Do some testing
    }
}

[Collection("MyCollection")]
public class TestClass2
{
    [Fact]
    public void TestMethod1()
    {
        // Do some more testing
    }
}

The example code above shows a collection definition with two fixtures and two test classes defined as part of that collection. Note how the fixtures control initialization and cleanup using constructors and IDisposable 1 . We can modify those classes to reference the collection fixtures just as we did with class-level fixtures; by referencing the fixture in the constructor arguments as shown here.

[Collection("MyCollection")]
public class TestClass1
{
    public TestClass1(MySecondFixture fixture)
    {
        // Do something with the collection fixture
    }

    [Fact]
    public void TestMethod1()
    {
        // Do some testing
    }
}

I really like this approach over the attributed static methods of MSTest. This seems to more easily support code reuse and makes intentions much clearer, separating the concerns of tests (defined in the test class) from fixtures (defined by the fixture types). The downside is that fixture types do not get access to the ITestOutputHelper interface so if you want your fixtures to output diagnostic information, you should consider a logging library like Common.Logging. Also, your fixture types must be in the same assembly as your tests. Of course, that doesn't preclude the fixtures from using types outside of the assembly, so you can always put shared implementation between test assemblies in some other class library.

And that brings our migration of shared initialization to a close. You can find more information on sharing context across tests on the xunit site. Next up, we will look at data-driven tests. Thank you for your time. If you have any questions, please leave a comment.

  1. A fixture type can used with IClassFixture<T> or ICollectionFixture<T> []