I am currently learning the xUnit.net framework as part of a new project I work on. For the last years I used NUnit for my unit and integration tests. Conceptually those two libraries aren’t that different. However, the naming of attributes and what is possible in sharing setup & clean-up code makes it worth to take a deeper look.
Installation
Create a class library project, right-click on References and select “Manage NuGet Packages…” to open the NuGet packages dialog. Search for xUnit and install this package:
To integrate xUnit.net into the Visual Studio Test runner you can install the package xunit.runner.visualstudio:
Check the extensive documentation and a list of all the xUnit.net NuGet packages to see how you can customize your installation.
Fact replaces Test
Every method annotated with Fact
will be marked as a test and run by xUnit.net:
1 2 3 4 5 6 7 8 9 10 11 |
using Xunit; public class FirstSteps { [Fact] public void Add_1and2_gives3() { var result = Add(1, 2); Assert.Equal(3, result); } } |
This isn’t a big change from the Test attribute you use in NUnit.
The class containing your tests must be public
, but you don’t need any attributes on them (like TestFixture
for NUnit).
Ignoring Tests
If you don’t want to execute a unit test you can skip it by providing a reason:
1 2 3 4 5 |
[Fact(Skip = "Thats how you ignore a test")] public void ToIgnore() { Assert.False(true); } |
Again, only a small change to the Ignore attribute you know from NUnit.
Theories instead of TestCases
One of the reasons I use NUnit is the possibility to reuse tests by using the TestCase
attribute and change only the parameters to a method. xUnit.net offers the same functionality with Theory
and InlineData
:
1 2 3 4 5 6 7 8 9 |
[Theory] [InlineData(0, 0, 0)] [InlineData(1, 0, 1)] public void Add_with_values(int first, int second, int expectedResult) { var result = Add(first, second); Assert.Equal(expectedResult, result); } |
Asserts
The Assert
class offers most of the features you know from the classic approach used by NUnit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
[Fact] public void Asert_Examples() { Assert.Equal(1, 1); Assert.Equal("London", "London"); Assert.StartsWith("Lon", "London"); Assert.EndsWith("on", "London"); Assert.Contains("Lon", "London"); Assert.DoesNotContain("Bern", "London"); Assert.Empty(new List<int>()); Assert.NotEmpty(new List<int>(){1,2,3}); Assert.True(1 == 1); Assert.False(1 == 2); Assert.Null(null); Assert.NotNull("a"); Exception ex = Assert.Throws<ArgumentNullException>( () => MyMethod()); Assert.InRange(5, 1, 10); Assert.NotInRange(-1, 0, 10); } [Fact] public async Task Assert_Example_Async() { await Assert.ThrowsAsync<Exception>( () => throw new Exception("name")); } |
NUnit offers in addition the constraint model for assertions, which I find more readable. As far as I know there is no direct replacement in xUnit.net.
Setup & Clean-up
The biggest difference between xUnit.net and NUnit is in my opinion in the setup and clean-up code.
For every test: Constructor and Dispose
xUnit.net creates a new instance of the test class for every test it contains. This allows you to put the setup code you need in the constructor of your test class:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class SetupBeforeEachTest { public SetupBeforeEachTest() { // constructor for setup code } [Fact] public void MyTest1() { Assert.True(true); } } |
To clean-up after every test you use another basic functionality of .Net by implementing the IDisposable
interface and putting your code in the Dispose()
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class CleanupAfterTest : IDisposable { public CleanupAfterTest() { // constructor for setup code } public void Dispose() { // dispose for cleanup code } [Fact] public void MyTest1() { Assert.True(true); } } |
Once for a test class: ClassFixture
What can be done in NUnit with a simple OneTimeSetUp
needs a lot more work in xUnit.net. To have a setup and clean-up method per test class, you need to create a fixture class to place your code and implement the IClassFixture<>
interface on your test class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class MyDbSetupCode : IDisposable { public MyDbSetupCode() { // setup code } public void Dispose() { // clean-up code } } public class MyTestClass : IClassFixture<MyDbSetupCode> { private MyDbSetupCode classwideFixture; public MyTestClass(MyDbSetupCode fixture) { this.classwideFixture = fixture; } [Fact] public void MyTest1() { Assert.True(true); } } |
You only need to use your fixture class as a parameter in the constructor when you want to use it inside your test class. One benefit of this approach is that you can reuse your fixture class with other test classes – when a setup and clean-up after every class is working for you.
Once for multiple test classes: Collection Fixtures
Expensive setup or clean-up code may be run only once for a group of test classes. We can reuse the fixture class, create a class without code for the collection definition and then add the Collection attribute on all test classes that should use it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[CollectionDefinition("Database collection")] public class DatabaseCollection : ICollectionFixture<MyDbSetupCode> { // A class with no code, only used to define the collection } [Collection("Database collection")] public class CleanupAfterCollection { MyDbSetupCode fixture; public CleanupAfterCollection(MyDbSetupCode fixture) { this.fixture = fixture; } [Fact] public void A() { } [Fact] public void B() { } } [Collection("Database collection")] public class MoreTestsWithDatabase { [Fact] public void C() { } } |
Conclusion
xUnit.net offers more or less the same functionality I know and use in NUnit. The biggest difference is the more flexible way to reuse the same setup and clean-up code, even when this comes with an increased complexity. The traditional way of Assert.* is nearly the same and lets you quickly write tests.
So far, I don’t have a reason to switch to xUnit.net, but this may change depending on how well it works with SpecFlow on .Net Core.
Thanks for the info!