On January 2nd, Jim and I shipped xUnit.net 1.9. We updated NuGet with the 1.9 build binaries, and for the first time, we're including the MSBuild runner inside the "xunit" NuGet package.
There are a few big new features that are worth calling out.
Async Unit Tests
Late in 2010, the C# team announced a new feature that was coming in C# 5: the "async" and "await" keywords. These keywords allow the developer to consume Task-based asynchronous APIs with code that looks linear and procedural, and mimics the code that the developer would write when calling synchronous APIs. In addition, .NET 4.5 is introducing new Task-based async APIs to supplement the existing event-based asynchronous APIs, and new async APIs will only offer Task-based versions.
Before now, unit testing these asynchronous APIs meant resorting to calls like .Wait() and .ContinueWith(), since unit testing frameworks are inherently synchronous by nature. With the release of 1.9, xUnit.net allows you to write asynchronous unit tests by marking your test method with the "async" keyword, and changing the return value from void to Task.
Prior to 1.9, a unit test around an asynchronous API might look something like this:
[Fact] public void MyAsyncUnitTest() { // ... setup code here ... Task task = CallMyAsyncApi(...) .ContinueWith(innerTask => { var result = innerTask.Result; // ... assertions here ... } task.Wait(); }
The code gets sufficiently more complex with every additional asynchronous API you add into the mix (for example, calling some async APIs during the setup phase of the unit tests). Adding try/catch logic becomes difficult and/or redundant, as the exception handling logic needs to be duplicated for both the setup code and the ContinueWith handler).
The same unit test can be simplified by using xUnit.net 1.9 (and either the Async CTP, or the pre-release version of .NET 4.5 which includes C# 5):
[Fact] public async Task MyAsyncUnitTest() { // ... setup code here ... var result = await CallMyAsyncApi(...); // ... assertions here ... }
xUnit.net does the rest of the work. It sees that you're returning a Task and waits for it to complete. Traditional sequential coding mechanics like try/catch/finally and using are much easier to reason about when using async/await, and the compiler takes care of the boilerplate code necessary to ensure that it all works properly.
Generic Theories
One of the most used features in the xUnit.net Extensions project is support for theories. In quick review: Facts are for expressing tests which are invariants; Theories are used for expressing tests that are only necessarily true for a given set of input data. As such, theories are sometimes called "data-driven unit tests", because part of testing a theory is providing sets of conforming data.
The new Generic Theories feature allows the developer to write their theories using the .NET generic method syntax. xUnit.net will attempt to determine the best signature for the generic types of the method based on the provided data, and it makes this decision individually on a data-row by data-row basis. This is most useful for writing unit tests against generic APIs, wherein you want to choose the generic API to call based on the type of the input data.
For example, here is a unit test which is testing a generic get-and-cast behavior of a container:
[Theory] [InlineData(42)] [InlineData(21.12)] [InlineData("Hello, world!")] public void CastingGetTest<T>(T value) { MyContainer container = new MyContainer(); container.SetValue("name", value); T result = container.Get<T>("name"); Assert.Equal(value, result); }
This generic theory will be called 3 times as expected, and the type of T will be implied based on the value that was provided. We've also updated the output from theories to include any generic types that were used, so if this theory fails, its output method names would be:
CastingGetTest<Int32>(value: 42) CastingGetTest<Double>(value: 21.12) CastingGetTest<String>(value: "Hello, world!")
This highlights another new feature of 1.9 as well: theory names include parameter names in addition to parameter values.
The rules for matching are fairly straightforward:
- If the generic type has no matching parameters, we use Object
- If the generic type has one matching parameter:
- If the parameter value is non-null, we use value's concrete type
- If the parameter value is null, we use Object
- If the generic type has two or more matching parameters:
- If the parameters are the exact same concrete type, we use the concrete type (note that a null value is type-compatible with any reference type, but type-incompatible with any value type)
- If the parameters are not the exact same concrete type, we use Object
We could've gone with a more complex matching algorithm, but we wanted the results to be easy to understand and reasonably predictable without stashing away dozens of matching rules in your head.
An important note: this support is limited to generic test methods only. xUnit.net does not support generic test classes.