It is common for unit test classes to share setup and cleanup code (often called "test context"). xUnit.net offers several methods for sharing this setup and cleanup code, depending on the scope of things to be shared, as well as the expense associated with the setup and cleanup code.
When to use: when you want a clean test context for every test (sharing the setup and cleanup code, without sharing the object instance).
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. This makes the constructor a convenient place to put reusable context setup code where you want to share the code without sharing object instances (meaning, you get a clean copy of the context object(s) for every test that is run).
For context cleanup, add the IDisposable
interface to your test
class, and put the cleanup code in the Dispose()
method.
Note: you cannot call asynchronous methods in a constructor.
Also xUnit v2 does not call IAsyncDisposable
(it is planned for xUnit v3).
To perform async setup and cleanup you need to implement IAsyncLifetime
.
Here is a simple example:
This structure is sometimes called the "test class as context" pattern, since the test class itself is a self-contained definition of the context setup and cleanup code. You can even name the test classes after the setup context so that it's easier to remember what your starting point is:
At a high level, we're writing tests for the Stack
class, and each
context is a Stack
in a given state. To reflect this, we've wrapped
all the testcontext classes in a parent class named StackTests
.
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.
Sometimes test context creation and cleanup can be very expensive. If you were to run the creation and cleanup code during every test, it might make the tests slower than you want. You can use the class fixture feature of xUnit.net to share a single object instance among all tests in a test class.
We already know that xUnit.net creates a new instance of the test class for
every test. When using a class fixture, xUnit.net 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.
To use class fixtures, you need to take the following steps:
IDisposable
on the fixture class, and put the cleanup code in the Dispose()
method.
IClassFixture<>
to the test class.
Here is a simple example:
Just before the first test in MyDatabaseTests
is run, xUnit.net
will create an instance of DatabaseFixture
. For each test, it
will create a new instance of MyDatabaseTests
, and pass the shared
instance of DatabaseFixture
to the constructor.
Important note: xUnit.net uses the presence of the interface
IClassFixture<>
to know that you want a class fixture to
be created and cleaned up. It will do this whether you take the instance of
the class as a constructor argument or not. Similarly, if you add the constructor
argument but forget to add the interface, xUnit.net will let you know that it
does not know how to satisfy the constructor argument.
If you need multiple fixture objects, you can implement the interface as many times as you want, and add constructor arguments for whichever of the fixture object instances you need access to. The order of the constructor arguments is unimportant.
Note that you cannot control the order that fixture objects are created, and fixtures cannot take dependencies on other fixtures. If you have need to control creation order and/or have dependencies between fixtures, you should create a class which encapsulates the other two fixtures, so that it can do the object creation itself.
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.
Sometimes you will want to share a fixture object among multiple test classes. The database example used for class fixtures is a great example: you may want to initialize a database with a set of test data, and then leave that test data in place for use by multiple test classes. You can use the collection fixture feature of xUnit.net to share a single object instance among tests in several test classes.
To use collection fixtures, you need to take the following steps:
IDisposable
on the fixture class, and put the cleanup code in the Dispose()
method.
[CollectionDefinition]
attribute, giving it a unique name
that will identify the test collection.
ICollectionFixture<>
to the collection definition
class.
[Collection]
attribute to all the test classes that
will be part of the collection, using the unique name you provided to the
test collection definition class's [CollectionDefinition]
attribute.
Here is a simple example:
xUnit.net treats collection fixtures in much the same way as class fixtures, except that the lifetime of a collection fixture object is longer: it is created before any tests are run in any of the test classes in the collection, and will not be cleaned up until all test classes in the collection have finished running.
Test collections can also be decorated with IClassFixture<>
.
xUnit.net treats this as though each individual test class in the test collection
were decorated with the class fixture.
Test collections also influence the way xUnit.net runs tests when running them in parallel. For more information, see Running Tests in Parallel.
Important note: Fixtures can be shared across assemblies, but collection definitions must be in the same assembly as the test that uses them.
When to use: when you want to create a single test context and share it among all the tests in your test assembly.
Newly introduced in v3, you can now share a single instance of a fixture among all the test classes in your test assembly.
Here is the example from collection fixtures, but adapted to be used as an assembly fixture:
Instance of assembly fixtures are created once before any test in your assembly is run, and cleaned up after all tests have finished running. Any test class may gain access to the assembly fixture simply by adding it as a constructor argument.
Note that unlike collection fixtures, there is no change in parallelization when using an
assembly fixture. This means fixtures used as assembly fixtures may be used from multiple tests
simultaneously, and must be designed for with this parallelism requirement in mind. Alternatively,
you could disable all parallelism in your test assembly by setting the
parallelizeTestCollections
configuration setting to false
.