With ASP.NET Core 1.0 applications, <inlinecode>*.csproj<inlinecode> and <inlinecode>*.vbproj<inlinecode> file extensions no longer exist — instead there is the <inlinecode>*.xproj<inlinecode> file extension to take their place. Previously the “cs” and “vb” in the extension names were indicators of the programming language in use for that **Visual Studio** project (C# and Visual Basic respectively). This could be helpful I suppose, although I never really paid too much attention to the file extension of the project. You are probably wondering, “what does ‘x’ mean”? It is intended to signify that the project is a DNX project, and the language doesn’t really matter.
From within Visual Studio, let’s create a new class library project <inlinecode>File › New › Project<inlinecode>. This will open the “New File Project” dialog. Select <inlinecode>Installed › Visual C# › Web › Class Library (Package)<inlinecode>. I named mine “IEvangelist.CSharper.Calculator”. As a side note, the <inlinecode>Class Library<inlinecode> project type is the same as the <inlinecode>Web Site<inlinecode> in that you can still perform [xUnit] testing against either. For demonstration purposes, I’m simply using a class library — the main point is that the project is a <inlinecode>*.xproj<inlinecode> type.
You may have noticed that the <inlinecode>global.json<inlinecode> file calls out a “projects” property which takes a string array of directories in which to look for projects. So let’s create the “test” directory and add another class library project under this newly created folder. I named mine “IEvangelist.CSharper.CalculatorTests”, and now your structure should be as follows:
This is the convention, having the <inlinecode>src<inlinecode> separated from the <inlinecode>test<inlinecode> directories. This is how the <inlinecode>global.json<inlinecode> looks:
The next important part to note, is that the calculator test project must reference the calculator proper project (obviously). This is done the same way in which it has always been done for projects that live side-by-side, simply add it as a project reference — Right click on <inlinecode>References › Add References...<inlinecode> From the <inlinecode>Reference Manager<inlinecode> dialog, select <inlinecode>Projects › Solution<inlinecode> and check the “IEvangelist.CSharper.Calculator” project and click <inlinecode>Ok<inlinecode>.
With DNX projects, I have found that they are incompatible with MSTest. However, there is a specific DNX compatible version of xUnit available — that’s what we will be using.
xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages.
Our <inlinecode>project.json<inlinecode> file will need to take on xUnit as a <inlinecode>dependency<inlinecode> as depicted here:
For we can focus on testing. I have created a simple interface namely, <inlinecode>ICalculator<inlinecode>. It exposes four simple calculation based methods.
An example implementation is the <inlinecode>Int32Calculator<inlinecode> defined like so. Please note that these calculator’s in all practicality are useless, these are simply for demonstration purposes only.
I love some of the new C# 6 language features, like shown above — we can have members that are defined via a lambda expression instead of a method body. In my opinion this makes certain code easier to read. Now that we have defined an implementation of the <inlinecode>ICalculator<inlinecode> interface — we can author a unit test against it.
By convention, I usually try creating a single class that corresponds to the system unit test — in this case, let’s create a new class in the testing project named “Int32CalculatorTests.cs”. xUnit uses two primary test attributes that are provided at the method level, <inlinecode>Fact<inlinecode> and <inlinecode>Theory<inlinecode>. These attributes are what tellxUnit to treat these methods as tests. There are several other key attributes that are available, I’ll touch on those later.
Expects no parameters or method definition
Expects either <inlinecode>InlineData<inlinecode> or <inlinecode>ClassData<inlinecode> attributes for parameter resolution.
Here is an example of a <inlinecode>Fact<inlinecode> attributed test method that tests the <inlinecode>Int32Calculator's .Subtract<inlinecode> method.
It is very simple and straight forward, but imagine that you wanted to test several different inputs and expected outputs — this is where <inlinecode>Theory<inlinecode> comes into play. The <inlinecode>Theory<inlinecode> attribute lets you define <inlinecode>InlineData<inlinecode> for example:
The <inlinecode>InlineData<inlinecode> becomes a little too repetitive if you want to have more tests executing against a function. You can then use the <inlinecode>ClassData<inlinecode> attribute. This attribute takes a <inlinecode>System.Type<inlinecode> as an argument to its constructor. The type must implement the <inlinecode>IEnumerable<inlinecode> interface of <inlinecode>object<inlinecode>. I decided that if I was going to have lots of tests that would require this implementation for custom test data, I should create an <inlinecode>abstract class<inlinecode> that requires implementations to define a TestData. Here is the <inlinecode>ClassTestData<inlinecode> class.
Here is an example unit test, it is Theory based and it takes on the <inlinecode>Int32CalculatorTestData<inlinecode> class — which inherits the <inlinecode>ClassTestData<inlinecode> class. This is the type that is passed into the <inlinecode>Xunit.ClassDataAttribute<inlinecode> class as an argument to its constructor.
The amazing thing about all of this is that Visual Studio’s test explorer window resolves these at compile time and shows a parameterized test for each InlineData or <inlinecode>ClassData<inlinecode> test set. Based on the three examples, here’s the </inlinecode></inlinecode>Test Explorer window.
For those of you who are still with me, you made it through my bantering and you are now rewarded with the pointer to the source — I have posted the project up on my github here. I appreciate any and all feedback, and I hope that this was at the very least beneficial to someone!