I’ve not really seen any major purpose to use anything other than NUnit for unit testing in .NET since I switched 100% to it from a combination of mbUnit and NUnit a fair few years back. But recently I’ve had that urge we developers get, to try something different. So here’s a very brief post on using xUnit.
First off, if you need a VS2012 test runner for xUnit check out http://visualstudiogallery.msdn.microsoft.com/463c5987-f82b-46c8-a97e-b1cde42b9099.
So with NUnit we marked are test classes with [TestFixture]. xUnit doesn’t use anything to mark the class itself as a test class, it solely relies on the use of attributes on the test methods themselves (see http://xunit.codeplex.com/wikipage?title=Comparisons for a comparison of all the features between NUnit and xUnit).
The SetUp and TearDown of NUnit have been replaced with the ctor and IDisposable.Dispose on a test class and the TestFixtureSetUp and TestFixtureTearDown require you to implement IUseFixture
So a simple example of a test is
[Fact] public void Clear_MultipleBits_ShouldClearAllBits() { Binary b = new Binary(4); b.Set(2, 4); b.Clear(2, 4); Assert.Equal(0, b.ToNumber()); }
So we use the Fact attribute to declare a test and Assert to verify the test. The Fact attribute also allows us to assign a DisplayName, test Name, a way to ignore (in this case Skip) a test and assign a string to this to tell everyone why the test was skipped and a timeout. So a Fact is pretty comprehensive.
On top of Fact the xUnit extensions library (available through NuGet) also adds some useful additions such as the Theory attribute which allows us to supply data to the test, for example
[ Theory, InlineData("1", 1), InlineData("10", 2), InlineData("11", 3), InlineData("100", 4), ] public void ToNumeric_TestVariousNumbers_BinaryStringShouldMatchDecimalValue(string s, int value) { Assert.Equal(value, Binary.Parse(s).ToNumber()); }
In the above case we’ve marked the test as a Theory (which is also a Fact, hence no need for the Fact attribute). The InlineData attribute allows us to specify the arguments that will be passed into our method as arguments. So obviously you need the same number of values in the InLineData attribute as you have arguments on the method.
The Trait attribute is useful for grouping our tests (particularly if you’re using the xUnit test runner). So for example
[Fact, Trait("Bug", "1234")] public void SomeTest() { }
In the above we create a new trait named Bug. In xUnit’s test runner this will create a new group within the Traits: window named Bug and add the item 1234.
The xUnit extensions project add a whole load of more attributes for use with xUnit tests, including various methods for bringing data into the test from different sources, such as from a separate class, a property on the test class, OleDb data, Excel and more.
One of the key difference with writing tests in xUnit comes with the way it handles exceptions. In NUnit we use the ExpectedException attribute. In xUnit the Assert.Throws wraps a specific method expecting an exception. For example
[Fact] public void Divide_ExpectDivideByZeroException() { Calculator c = new Calculator(); Assert.Throws<ArgumentException>(() => c.Divide(4, 0)); }
xUnit also supports testing of async await code in the following manner
[Fact] public async Task SomeAsyncAwaitTest() { MyObject o = new MyObject(); await o.Run(); Assert.True(MyObject.Completed); }