Using xUnit Unit Testing
Going back to basics: a unit test should verify a single bit of functionality (usually a method). I want tests to be isolated, so I do not call the internet or a real database. Tests are my safety net for "you just broke something".
I also like to test the unhappy paths. It is easy to only test the happy path, but the bugs always live in the corners.
Why xUnit?
It is the main testing tool used on the platform I work on, so I wanted to understand why and what the alternatives are.
Test attributes and organisation
Common testing frameworks are MSTest, NUnit, and xUnit. In xUnit we tag tests with [Fact] (single test) or [Theory] (data-driven test). MSTest and NUnit have similar attributes.
Test discovery
xUnit discovers tests via convention. It looks for those attributes on public classes inside a test project (usually ending in .Tests). One downside: discovery sometimes fails in IDEs. I have not had it fail personally, but I have seen it happen to QA.
Setup and teardown
xUnit uses the class constructor for setup and Dispose for teardown. NUnit/MSTest use [SetUp] and [TearDown]. The constructor approach feels more natural to me.
Test isolation
xUnit creates a new instance of the test class per test by default. NUnit and MSTest reuse the same instance unless you change it. The xUnit approach avoids strange side effects between tests.
Dependency injection support
xUnit plays nicely with DI because the constructor can take dependencies. You can pass in mocks and keep your tests close to how your production code is wired.
Parallel execution
By default, test collections run in parallel (tests within a class are sequential). This has been a nice speed boost for me.
Shared test context
If you have an expensive setup, you can share it using IClassFixture. I have seen this used for database-ish setup where you want to reuse a single instance.
Data-driven tests with Theory
I use Theory when I want the same test to run multiple times with different data.
[Fact]
public void Testy_Mc_Test_Face()
Using InlineData
[Theory]
[InlineData(1, 2, 3)]
public void Testy_Mc_Test_Face(int foo, int fee, int faa)
Using MemberData
public static IEnumerable<object[]> AddTestData => new List<object[]>
{
new object[] { 0, 1, 1 },
new object[] { 1, 2, 3 },
new object[] { -1, 1, 0 }
};
[Theory]
[MemberData(nameof(AddTestData))]
public void Addition_Works(int a, int b, int expected)
{
// Test logic here
}
Test naming
I still get this wrong sometimes, but the common convention is MethodName_WhenCondition_ExpectedOutcome.
So what is mocking?
Mocking is how we replace dependencies so tests stay isolated. That lets us control what happens when a method calls a database or a service. I actually enjoy mocking, but it can get tricky.
The mocking package I use at work is Moq. After installing it (dotnet add package Moq), you can create a mock like this:
var thingIMocked = new Mock<IThingIWantToMock>();
You can then control what the mock returns:
// For cases where you care about the specific value provided as the argument
thingIMocked.Setup(p => p.Add(100)).Returns(true);
// For cases where you do not care about the argument provided
thingIMocked.Setup(p => p.Add(It.IsAny<int>())).Returns(true);
This makes it easy to test different outcomes. You can also verify how many times a method was called.
Under the hood, Moq creates a fake class at runtime (using reflection and Dynamic Proxy) and intercepts method calls. It is clever, but it does have limits: it cannot mock static or sealed types and it cannot mock a class without a parameterless constructor. I have hit both of those.
Why I like using xUnit
- Fresh test class instances for each test (no more weird interactions)
- Constructor/Dispose feels like normal C#
- DI and mocking support is solid
- Parallel test execution by default
- Theory tests make data-driven testing easy
Pick the testing tool that works for you. I like xUnit because I am familiar with it and it makes sense to me most of the time.
My takeaway: name your tests clearly. You will thank yourself later.
Happy testing.