Why I like and prefer xUnit

03/07/2023

In almost all of my projects, I only use xUnit, and here is a small love letter. Especially the one fact I do think makes it a good choice!

Test Isolation

In xUnit, each test case (Fact or Theory data) runs on a fresh test class instance. This is a deliberate design decision made by the creators of xUnit to promote more robust, less coupled tests. By contrast, other testing frameworks, such as NUnit, create only a single instance of the test class and run all test methods on that same instance.

This might seem like a slight difference, but it has profound implications for test design and execution. With xUnit's approach, tests don't share state through instance fields or properties, making them inherently less coupled and more reliable. They won't fail unpredictably due to side effects of other tests.

To illustrate this, let's compare simple xUnit and NUnit test classes that print the HashCode of the test class instance.

// xUnit
public class MyXunitTests
{
    private readonly ITestOutputHelper _testOutputHelper;

    // We can not natively use Console.WriteLine in xUnit, so we need to inject the ITestOutputHelper
    public MyXunitTests(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }

    [Fact]
    public void Test1()
    {
        _testOutputHelper.WriteLine(GetHashCode().ToString());
    }

    [Fact]
    public void Test2()
    {
        _testOutputHelper.WriteLine(GetHashCode().ToString());
    }
}

// NUnit
[TestFixture]
public class MyNunitTests
{
    [Test]
    public void Test1()
    {
        Console.WriteLine(GetHashCode());
    }

    [Test]
    public void Test2()
    {
        Console.WriteLine(GetHashCode());
    }
}

When we run the tests, we get the following output:

  TestProject1
   MyXunitTests
    Test1 > 65615241
    Test2 > 19208783
  TestProject2
   MyNunitTests
    Test1 > 42931033
    Test2 > 42931033

As you can see, the xUnit tests print different hash codes, while the NUnit tests print the same hash code. Why is this important? Ideally, every unit test can run independently of other tests. So we don't have to maintain an order so that tests will pass. Imagine the following scenario (in nUnit):

[TestFixture]
public class StackTests
{
    private Stack<int> _stack = new Stack<int>();

    [Test]
    public void Push_AddsElementToTheStack()
    {
        _stack.Push(1);
        Assert.That(_stack.Count, Is.EqualTo(1));
    }

    [Test]
    public void Pop_RemovesElementFromTheStack()
    {
        _stack.Pop();
        Assert.That(_stack.Count, Is.EqualTo(0));
    }
}

If these tests run in the order they are written, everything will be fine. But if Pop_RemovesElementFromTheStack runs before Push_AddsElementToTheStack, the Pop test will fail with an InvalidOperationException because it's trying to pop an element from an empty stack. This is a clear example of a test order dependency, which is a symptom of poor test isolation. xUnit can help us here - mainly because that test will never run on the same instance, so Pop_RemovesElementFromTheStack will always fail in the first place. Of course also xUnit knows ways to preserve state between tests, but this is a deliberate decision and not the default. So you have to make it broken on purpose.

But also then there is another cool feature of xUnit: Random test order. At the beginning of each run, a new seed is generated that determines the order of your tests inside a test class. So even if you run the same test suite without rebuilding, the order might differ. This is a great way to find hidden dependencies between your tests.

Conclusion

Should you always use xUnit now? No, of course not. I think it's a good default choice and my preferred testing framework. It's easy to use, has many features, and is well-maintained. But if you have a good reason to use another framework, go for it.

9
An error has occurred. This application may no longer respond until reloaded. Reload x