Testing in .NET Core 2.0
Unit and Integration tests with xUnit
Here is our new post about .NET Core.
In the previous one, we learned how to deploy our .NET Core application using Docker. Make sure you check it out!
In this post, we will discuss how to create automated tests for our application. Automated tests are a very important part of software development in general, and this concept also applies to .NET Core. It simplifies the execution of unit and integration tests, saving a lot of time for the developers.
This post will give you an idea on how to implement tests on your .NET Core application.
At Wolox, we test our .NET Core projects using xUnit and the test command available on the .NET Core CLI. This is not the only way of testing for this specific framework, but it is fairly quick to set up and it covers the basics on how to get started on your journey towards productivity.
It is important that the source code of our application is ready to test. In this example, we will be using this project, which is available on GitHub.
To keep the structure clean, we will be separating our code into two folders:
- One for the source code (src) and,
- One for the tests (test).
We should create the new test project on the test folder.
For each of the sections of this post, we will be creating a test project using the dotnet new command that generates a new xUnit project (dotnet new xunit). Once we have the test project, we need to reference the source code of our application into the project file. We can do so using the following command:
dotnet add reference ROUTE_TO_CSPROJ.csproj
This will let us use any methods or classes defined in our main project.
Unit testing is pretty straightforward. It allows us to test the specific modules and components of our applications. The goal of these kinds of tests does not involve the functionality of the application as a whole, rather the specific work of the units that make up our system.
These units are usually specific classes or functions, evaluated to see if the expected outcome when using their methods corresponds with what we actually get.
For this example, we will be creating a Helper class (
MathHelper.cs) that will encapsulate the functions we will be testing. This will be a static class.
To test these classes, we will be creating a file on our test project, called
UnitTestMathHelper.cs. Here, we will define a class with the same name as the file, and a method, called
[Fact] tags let the test project know that we are coding test methods. For this application, we will be testing just two of the methods available on our helper. One way to do it is by using the following code:
Here, we first use the
[Fact]tag, so this method will be identified as a test.
- Then, we use a few variables to establish our test data (lines 4 and 5) and the expected result (line 6).
- The following thing we need to do is actually call the method we will be testing (in this case, the Sum method in line 7).
- Finally, we need to check if the result obtained is the same as the one we were expecting (line 8). Once our test case is implemented, we need to use the ´Assert.Equal´ function to let the application determine the result of the test.
This will return a value based on the condition that we set as a requirement for the test to be successful. The Assert class has different methods which apply to different situations.
We can also use the
[Theory] tag, along with
[InlineData]if we want to test a method with different inputs, as is shown in the following code.
To run our unit test, we just need to head into the main folder using our terminal and type in the
dotnet test command. It should give us the result of our tests. Here, we are performing 4 tests. One for the Sum method and one for each of the values tested in the Multiplication method.
Give it a shot! Try creating a test for one of the methods in the
OperationsController. We should be able to instance it directly on your application.
A test server lets us test the functionality of various components of our application working together. They are a bit more complicated than unit tests, mainly because they require us to set up a server for our application to work.
In this example, we will be testing a new class and controller:
Hero. The code for this new class, its controller and other files that require to be changed can be found here.
Once that’s set, we can start writing the test code.
The first step is to add the
Microsoft.AspNetCore.TestHost NuGet package to our test configuration file. Our project file should look something like this:
This will provide us with the necessary libraries to create a test server for our application.
Let’s create a new folder in our
test folder and call it
Integration, and a new file to initiate the server itself. The following code shows one way of doing so:
We will be using an in-memory database for this test, so we need to change how we set the database up. To do so, we need to add a few lines in the
ConfigureServices method located in the
Startup.cs file of our application. Here’s how the new code should look like:
With that set, we will be able to use the test server as if it were the regular one. The server should work the same way as our application, but it allows us to perform our tests with test data, to avoid compromising the information used in our production servers. The
Assert functions we used previously work the same way as they do when performing unit tests.
For these tests, we will first make some test data for our tests to use. In this case, we are gonna be creating some ´Heroes´ and store them into a list, as is shown in the following code:
This list will be used in both our tests, although not in the same way. Let’s take a look at the first test:
So, what are we doing here?
- First, we are using the
[Theory]tag, which indicates we will be performing the same tests several times with different test inputs. Next, we have the
[MemberData]tag. This can be used to fetch data for the
[Theory]from a static property. This attribute has a lot of uses, in this case, we are just using it for the information from the
Heroesproperty we previously defined.
- The next thing we do is give our method a name and parameters. As you might have guessed, the parameters inserted matched the parameters of the Hero class, which we will be later instanced (line 7), and added to our in-memory database (lines 9 and 10).
- The rest is pretty straightforward. We make a request to our application, using the
Hero IDnumber as a parameter (line 12) and check if the response status code is
- Next, we get the ´Hero´ from the response, as a JSON string (line 16) and convert it to a hero object (line 18).
- Finally, we can check if the properties of the inserted
Hero(ID and name) correspond to the ones of the response hero (lines 20 and 21).
This test will be run for each of the heroes defined on the Heroes property.
Let’s take a look at the next test:
Here, we are taking a different approach to our Heroes:
- As you can see, we are first use the
[Fact]tag instead of the
[MemberData]one. This is because this time, we will be using the ´Heroes´ property as a whole, instead of testing the insertion of each hero separately.
Linq, we can convert this
objectlist into a Hero list (lines 5 to 10) and insert the whole thing into our database (lines 12 to 13).
- The next step is to determine the expected value of the response (in this case, the hero with the shortest height, see line 15).
- Once again, we will make a request to our application (line 17), check the response status code (line 19), get the JSON out of the response (line 21) and convert it into a
Heroobject (line 23).
- The final comparison is performed the same way, checking if the properties of the hero we got from the response are the same as the one we expect (lines 25 and 26).
Now, we can run our test command
dotnet test and see the result of our tests. If everything is okay, we’ll get a “Test Run Successful” message. If any of our test fail, we will get a “Test Failed” message along with information on what happened.
We can combine both unit and integration tests for our project, and set them up however we want to. It is important to note that it is usually recommended to use a test database to avoid compromising information that might not be recovered if corrupted/overwritten. Here, we are setting up an in-memory database, but if needed you can set up a database to work the same way as a normal one would to avoid compromising information that might not be recovered if corrupted/overwritten.
Now, you can develop automatic tests for your .NET Core applications. Give it a go! If you want to play around, you can always try the sample application.
Stay tuned for the next post.