Skip to content

xUnit Best Practices

The basics

  • Fact are tests which are always true. They test invariant conditions. runner
  • Theory Theories are tests which are only true for a particular set of data.

Unit test structure using Fact attribute:

C#
1
2
3
4
5
6
7
8
9
[Fact]
public void SomeMethod_WhenSomethingHappens_ShouldBehaveSomehow()
{
    // arrange code ...

    // act code ...

    // assert code ...
}

In addition to the Fact attribute, you can also use the Theory attribute on test methods. Theory runs a test method multiple times, passing different data values each time. You have a variety of tools for setting the data values to be passed to your test method.

Unit test structure using Theory attribute:

C#
[Theory]
[InlineData("some Value")]
[InlineData("another Value")]
[InlineData("yet another value")]
public void SomeMethod_WithSomeInput_ShouldBehaveSomehow(string inputValue)
{
    // arrange code ...

    // act code ...

    // assert code ...
}

Using InlineData is easy and straightforward, by there are some limitations, because being an attribute we can't create object instances. We may use other sources in such scenarios. Source data for Theory unit tests can be provided in may ways:

  • Using InlineData: it's clear and useful, but limited.
  • Using MemberData: points to an static property, field or method to feed Theory's data source.
  • Using ClassData: uses a class to feed Theory's data source. It can be an static class or extend TheoryData provided by xUnit.

Bear in mind that each Theory with its data set is considered to be a separate test. In the test results outcome, the runner tells you exactly which set of data failed, because it includes the parameter values in the name of the test.

Note

Prefer InlineData when possible over the other alternatives because it's more comprehensible for the developer, and it offers a bonus point: allow us to differentiate each execution instance provided by individuals InlineData entry in the Test explorer window.

Put tests in assemblies apart from production code

The test must be placed in assemblies apart from code. Tests must never be shipped to production environments. Sometimes they requires dependencies that we don't want in final deployments. By separating test assemblies from production code we will be able to manage the dependencies as we need without make footprints in the production code.

Each component being tested should be placed in its own file, keep those tests next each other to facilitate its localization.

Use Skip attribute for temporarily disabled tests

Sometimes you need to disable some tests for some reason, the best way to make the tests don't run for a while is to mark them as skipped using the Skip attribute. Don't comment out the failing test because that way you won't realize which of them needs to be worked out to bring them alive again, use Skip attribute instead.

By using Skip attribute you'll be able to clearly identify those skipped tests among failed and succeeded ones, and due to the fact that the attribute requires a reason as mandatory, you will get enough information to act on.

C#
1
2
3
4
5
6
7
8
9
[Fact(Skip ="This message must give enough info regarding why this test was disabled")]
public void MyMethod_WhenSomeCondition_ShouldReturnSomeValue()
{
    // arrange code ...

    // act code ...

    // assert code ...
}

Parallelism in xUnit

How does xUnit.net decide which tests can run against each other in parallel? It uses a concept called test collections to make that decision.

By default, each test class is a unique test collection. Tests within the same test class will not run in parallel against each other. It means that, by default, xUnit runs tests in different test classes in parallel, which can significantly shorten the time to run all your tests. It also means that xUnit effectively ignores the Run Tests in Parallel setting at the top of the Test Explorer window.

You can, however, override this default behavior where you need tests in different classes to run sequentially by assigning test classes to the same collection. You assign tests to a collection using the Collection attribute, passing a name for the collection.

C#
[Collection("My-TestCollection-1")]
public class SomeTestClass
{
   ... //test methods ...
}

[Collection("My-TestCollection-1")]
public class AnotherTestClass
{
 ... //test methods ...
}

By using the same test Collection attribute we can tell the TestRunner to run methods in different classes to run sequentially.

There are more advanced options that can be used to particularize your test fixtures, that can be seen here.

Sharing context

Test context can be shared by three main mechanisms:

  • Constructor and Dispose (shared setup/cleanup code without sharing object instances)
  • Class Fixtures (shared object instance across tests in a single class)
  • Collection Fixtures (shared object instances across multiple test classes)

Constructor and Dispose

xUnit.net creates a new instance of the test class for every test that is run, so any code which is placed into the constructor of the test class will be run for every single test.

When to use: when you want a clean test context for every test (sharing the setup and cleanup code, without sharing the object instance).

Remember to implement IDisposable pattern to free up any resource allocated.

Class fixtures

When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished.

When using a class fixture, xUnit will ensure that the fixture instance will be created before any of the tests have run, and once all the tests have finished, it will clean up the fixture object by calling Dispose, if present.

Collection Fixtures

Sometimes you will want to share a fixture object among multiple test classes. You can use the collection fixture feature of xUnit to share a single object instance among tests in several test class.

When to use: when you want to create a single test context and share it among tests in several test classes, and have it cleaned up after all the tests in the test classes have finished.

Please follow up this link for further reading.

Comparing xUnit with NUnit

These are some major changes made during the evolution from NUnit to xUnit:

  • Single Object Instance per Test Method.
  • No SetUp or TearDown methods available,
  • No ExpectedException, use Assert.Throws instead.
  • TestFixture was removed entirely; tests can be in any public class.
  • Ignore is expressed using the Skip= parameter on Fact or Theory.
  • SetUp and TearDown are removed in favor of Constructor and IDisposable.
  • TestFixtureSetup and TestFixtureTearDown are removed in favor of implementing reusable fixture data classes, which are attached to test classes by having them implement IUseFixture<T>.

A more detailed comparison of xUnit to other frameworks can be seen here.