XUnit Test Fixture & Context Lifetime
Introduction
XUnit.net (https://xunit.net/) is a wonderful tool, which allows developers to write concise, flexible and dynamic test cases, to ensure that the code they write, works.
It is an invaluable tool in dotnet. It is a corner-stone of Test Driven Development (TDD), along with its cousins, NUnit and MSTest, and is widely accepted as the defacto-standard of testing dotnet applications. Don't just take my word for it, 278 Million all-time downloads. 12 Million people on the current version and over 60,000 downloads per-day (Source Nuget). NUnit creeps just behind, but if you're testing dotnet applications, its more likely than not that you're using XUnit.
We're going to look at the lifecycle of an XUnit test case, which methods and contexts are called.When, and how things are linked together that aren't necessarily described by their documentation.
XUnit Lifetime / Lifecycle
When we talk about the lifetime or lifecycle of a program, we are usually discussing the order of events or, the order in which things happen. This is especially important when working with code, to ensure that as a developer, you know what has already happened, and what will happen after you call some other code.
This can be challenging with XUnit, because it has some interesting behaviours when you run a test. Publishing test output is different depending on where you are trying to output from. So I've unlocked the mysteries here.
Why do I need to know this?
Well, its important to know in what order things happen, its even more important with XUnit because some of the things described below, cannot take constructor arguments themselves. They must be constructed with 0 dependencies, but you may as a developer think you can place dependencies upon the contexts, but that just isn't the case.
So make sure you know, in what order things will run, and how you can work with this behaviour to ensure that all the necessary services and dependencies for your (integration) tests are working before you actually try and test anything.
Shared Context
XUnit follows a concept called Shared Context. This means that a test case does not have to run in isolation. Nor do a group of test cases have to run in isolation, or a suite of test cases have to run in isolation. Test cases can share the context / world they operate in between each other.
A developer can decide how to apply these shared contexts through something XUnit calls fixtures.
Collection Fixture
The first fixture is called a Collection Fixture. A Collection Fixture is instantiated by XUnit and provides the same context to all test cases that require it, it is a singleton, and so any state it holds or methods that it calls, will be run across all Test Cases that use this context. XUnit will pass the Collection Fixture to the constructor of a test case that requires it.
领英推荐
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.
It is a great place to setup Databases, Data Lakes, and/or Message Queues that are expensive to create. We only want to do this setup once, then share it amongst all test cases that subscribe to it globally.
Class Fixture
A Class Fixture, is a test context that is shared amongst the same test cases in the same class that implement IClassFixture<T> and take a Constructor argument of type T. Whilst a CollectionFixture will share context between test classes, each class that implements IClassFixture will receive its own copy of the fixture.
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.
XUnit will share the Class Fixture to each test case class and when all cases that need it are complete, it will dispose of the fixture. This can be a good place to setup data, or configure your environment for specific groups of tests.
IAsyncLifetime
This is an interface that can be implemented on all three elements of XUnit, a Collection Fixture, A Class Fixture AND a Test Class. It allows a developer to run async code, that might normally try to sit in a constructor (but can't, because constructors cannot be async).
Its InitializeAsync and DisposeAsync, allow async operations to be run as described above.
So what is the lifecycle?
You can see for yourself. Check out my XUnitLifeCycle project on github and run the only test case in the project.
Navigate to the '\bin\Debug\net7.0' folder and find 'TestOutput.txt'
Here you'll find the order in which things happen.
Senior Software Developer | .Net Core (Open for a remote position)
1 年Nice article, Thanks so much!
Software Engineering Director at Vigo Software Ltd
2 年Exactly why we use it for our back end!! Jest being the tool of choice for our front end ??