How to perform Unit Testing with xUnit in ASP.NET Core

How to perform Unit Testing with xUnit in ASP.NET Core

xUnit is a Free and Open Source testing package for .NET. It can be used to write Unit Testing codes for C# methods, Classes, Controller, API and others. In this tutorial I will teach how to use xUnit in writing Test Cases for an ASP.NET Core application.

The source codes of this tutorial can be downloaded from my GitHub Repository.

In testing terms, a Test Driven Development (TDD) is the approach to write a test before implementing the target code. Here, a test is written that is known to fail, the developer then writes the target code and keeps on updating it so that it passes the test. You should follow TDD approach when creating app as your codes will be up to 95% bug free in the first place itself.

Fact, Theory, InlineData

xUnit use the 3 attributes called [Fact], [Theory] and [InlineData]to create and perform unit tests. These are explained below.

  • 1. [Fact] – A test method is declared with the [Fact] attribute. The xUnit runs this method to either pass or fail it.
  • 2. [Theory] – A test method can be provided with parameters. These parameters are used to provide some values to the test methods, which may be needed to perform the testing of the particular code. So here you will have to apply [Theory] attribute on the test methods to specify that they will be provided with parameters. Remember that if there is [Theory] attribute then you will also need to add [InlineData] attribute to the test methods (see below for more info).
  • 3. [InlineData] – This attribute specifies values to the parameters.

Project Setup

Let us understand the integration procedure of xUnit in an ASP.NET Core application. Then we will understand how to write unit tests in xUnit. So open Visual Studio and create a new project, in the selection screen – select ASP.NET Core Web Application template.

ASP.NET Core Web Application

Name your project as MyAppT.

Also select the following 3 things when creating your project –

  • a) ASP.NET Core Web App (model-view-controller)
  • b) .NET Core
  • c) ASP.NET Core 5.0

I have marked them in the below image:

MVC app

The ASP.NET Core app is created and you can run it to see if everything works well.

Adding Project for Unit Testing

Next, you will need to add a new Class Library (.NET Core) project to the solution. In this project the unit test cases in xUnit will be written.

So right click the solution name in solution explorer and select Add ➤ New Project. Check below image.

adding project to solution

On the next window that appears, select Class Library (.NET Core). See below image.

class library project

Give this project name as TestingProject and choose it’s location same as the location of your MyAppT app.

This new project will be created and both of the projects will start showing in the Solution Explorer. I have shown this in the below given image.

2 Projects in Solution Explorer
Adding Project Reference

Since we want to test the main project i.e MyAppT, we have to add it’s reference to the TestingProject. So, in the TestingProject, right click the Dependencies link and select Add Project Reference. Then check the checkbox of MyAppT and click OK button.

This will add the project reference. I have shown this procedure in the below video.

Also note to target the same framework in both the projects. I have targeted .NET 5.0 for both MyAppT and TestingProject. Failing to do so will not let the TestingProject to use the referee of MyAppT. Changing the framework is a simple task which can be done by right clicking the name of the project in the solution explorer and selecting “Properties”. A new window opens, and here you need to find the “Target Framework” drop down in the “Application” section. Once you have selected the framework simply press Ctrl+S to save the settings.

Adding xUnit Packages

Now you will have to add xUnit testing package to your TestingProject. Right click the TestingProject in Solution Explorer window and select Manage NuGet Packages then install the following 3 packages to your TestingProject.

  • 1. xunit
  • 2. xunit.runner.visualstudio
  • 3. Microsoft.NET.Test.Sdk

I have shown these 3 packages in the below image:

xunit xunit.runner.visualstudio Microsoft.NET.Test.Sdk

Congrats, the xUnit package and necessary setup is completed. Now we are ready to start writing test cases in xUnit.

Testing a C# class functionality in xUnit

Let us start with a very easy example. Suppose you need to test a C# class functionality. This C# class can do sort of work like calculating the value of a complex maths function, doing some machine learning task and so on.

For this case let this class predicts the value of Tesla stocks after one year. The user provides the class with the current value of Tesla stock and the class will return it’s predicted value for the next year. To keep the things easy, I would be just be increasing the current Tesla stock value by 50%. So, if the user provides the current value of $800 then the class will return $1200 predicted value.

Let us write this class code in the MyAppT project. Create a new folder called Infrastructure and to it add a new class called TeslaStock.cs. In this class create a new method called Predict() that accepts the current stock value in it’s int type parameter. It increases the value by 50% and then returns this new value. The full code of this class is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyAppT.Infrastructure
{
    public class TeslaStock
    {
        public int Predict(int currentValue)
        {
            int newValue = Convert.ToInt32(currentValue + (.5 * currentValue));
            return newValue;
        }
    }
}

The Predict function increases the value supplied to it’s parameter by 50%. For doing this it uses a simple maths formula – currentValue + (.5 * currentValue).

Now let us write test case (test method) for performing the testing of this Predict function. So, create a new class called TestTeslaStock.cs in the TestingProject as shown below.

using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
using MyAppT.Infrastructure;

namespace TestingProject
{
    public class TestTeslaStock
    {
        [Fact]
        public void Test_Predict()
        {
            // Arrange     
            TeslaStock TS = new TeslaStock();

            // Act
            int newValue= TS.Predict(800);

	     // Assert
            Assert.Equal(1200, newValue);
        }
    }
}

In this class code, notice I have imported the namespace called MyAppT.Infrastructure so that I can play with the TeslaStock which resides in another project. As a reminder, I have already referenced the project that contains TeslaStock class in the TestingProject.

Other than this, I have also imported the xUnit namespace.

using Xunit;

The [Fact] attribute is applied to the Test_Predict method which specifies that this method is going to perform testing of some code of the app.

I am calling the Predict method of the TeslaStock class and have passed 800 as the current value of Tesla stock.

TeslaStock TS = new TeslaStock();
int newValue= TS.Predict(800);

It returns the predicted value of the stock which is assigned to the newValue variable.

Finally, I am using the Assert.Equal method of xUnit to verify that the predicted value of the stock (assigned to “newValue”) is equal to 1200 (which is 50% increase of current value of 800).

Assert.Equal(1200, newValue);

The Assert.Equal method takes 2 parameters – first is expected values and second is the actual value. Both should be equal if the test has to pass.

Arrange, Act, Assert

Arrange, Act and Assert are called test pattern or test steps. They help developers to write good tests. No matter what testing package you are using makes sure you follow this pattern always.

Arrange – here you setup the inputs and targets. You can see that I have initialized the TeslaStock class in this step.

TeslaStock TS = new TeslaStock();

Act – in this step you call the targeted function that needs to be tested. You can see that I have called the Predict() method in this step.

int newValue = TS.Predict(800); 

Assert – in this step you will ultimately determine if the test passes or fails. You can see that I have used the “Assert.Equal” method to check whether the test has passed or not.

Assert.Equal(1200, newValue);

Running the Test in Test Explorer

Now it’s time to run this test method to makes sure whether it works or not. For that purpose, we are going to use the Visual Studio’s Test Explorer, which can be opened from the View ➤ Test Explorer menu in VS.

Test Explorer Visual Studio

The Test Explorer shows all the tests in the project and group them into the test that have run successfully (shown in Green Color), which have failed (shown in Red Color) or which have not run till now (shown in Blue Color). There are also options to run all the tests, run those tests that have failed and so on. Initially, since no tests are run, therefore they are shown in blue color. You will also find your test method “Test_Predict” listed there in blue color. See the below image where I have shown this.

Blue color tests not run

Now select Run All Tests in View option in the Test Explorer, which will run the “Test_Predict” test method. The “Test_Predict” will soon be passed and will now be shown in green color. Check the below video where I am selecting the select Run All Tests in View option in the Test Explorer.

Run All Tests in Test Explorer

Congratulations, your first test case has been passed.

Test Fails

Let us now fail this test and see how Test Explorer will react. So, change 1200 to 1000 in the Assert.Equal method. Run the test once more and the test will fail this time. The Test Explorer will show the test method in Red Color with a cross sign. I have shown this in the below image.

Assert.Equal(1000, newValue);
Test fail xUnit

[Theory] and [InlineData] attributes

The [Theory] denotes a test method that is true for a subset of data. The subset of data is provided to the test method through an [InlineData] attribute. What about predicting the tesla stock value for not one but multiple values? This can be done by the help of [Theory] and [InlineData] attributes.

So, add a new test method called Test_PredictMultiple to the TestTeslaStock class. It’s code is given below:

using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
using MyAppT.Infrastructure;

namespace TestingProject
{
    public class TestTeslaStock
    {
        [Fact]
        public void Test_Predict()
        {
            // Arrange
            TeslaStock TS = new TeslaStock();

            // Act
            int newValue = TS.Predict(800);

            // Assert
            Assert.Equal(1200, newValue);
        }

        [Theory]
        [InlineData(200)]
        [InlineData(400)]
        [InlineData(800)]
        public void Test_PredictMultiple(int currentValue)
        {
            // Arrange
            TeslaStock TS = new TeslaStock();

            // Act
            int newValue = TS.Predict(currentValue);
            int calculate = Convert.ToInt32(currentValue + (.5 * currentValue));

            // Assert
            Assert.Equal(currentValue + (.5 * currentValue), newValue);
        }
    }
}

I have provided this test method with not 1 but 3 values by using “InlineData” attribute. The value supplied by the InlineData attribute is received in the parameter of the test method. I calculate the predicted value and store it in a variable called Calculate.

Finally, I am using the Assert.Equal method to perform the testing. Note that here the test will run 3 times as there are 3 subsets of data.

Now select the Run All Tests in View option in the Test Explorer. You will see the Test_PredictMultiple method is run for 3 times. I have shown this in the below image:

Theory and InlineData attributes in xUnit

Assert class Important Methods

The Assert class contains various static methods that are used to verify that conditions are met during the process of running tests. Some important methods are.

Name Description
Equal Verifies that two values are equal. There are various overloads that accepts int, string, DateTime, and other types of values.
Null Verifies that an object is null.
True Verifies that a condition is true.
False Verifies that a condition is false.
IsType Verifies that an object is of a given type.
IsAssignableFrom Verifies that an object is of the given type or a derived type.

We will be using these methods in our test cases during the course of this tutorial.

Testing Controller with xUnit

Now it’s time to test a ASP.NET Core MVC Controller with xUnit. This controller accepts a party invitation, and has action methods that allow user to add his/her name for the upcoming party. Add a new controller called PartyController to the Controllers folder of “MyAppT” project. Add the below code to it:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace MyAppT.Controllers
{
    public class PartyController : Controller
    {
        public IActionResult Entry()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Entry(string name, string membership)
        {
            if (string.IsNullOrEmpty(name))
                ModelState.AddModelError("name", "Please enter your name");
            if (string.IsNullOrEmpty(membership))
                ModelState.AddModelError("membership", "Please enter your membership");

            if (ModelState.IsValid)
            {
                string nm = name + "," + membership;
                return View((object)nm);
            }
            else
                return BadRequest(ModelState);
        }
    }
}

The user enters his/her name along with membership (like Silver, Gold, Platinum) on the form and submits it.

On submitting the form, the user’s name and membership is stored and the time of the party is shown to user. Note that I am also doing validations to force the user to enter his/her name and membership before submitting the form. The below code does this validation part.

if (string.IsNullOrEmpty(name))
    ModelState.AddModelError("name", "Please enter your name");
if (string.IsNullOrEmpty(membership))
    ModelState.AddModelError("membership", "Please enter your membership");

For any empty fields, I am adding the validation error to the ModelState like:

ModelState.AddModelError("name", "Please enter your name");

Next, I am checking for the valid ModelState, and if it is valid then returning the “name” and “membership” to the view in an object form.

if (ModelState.IsValid)
{
    string nm = name + "," + membership;
    return View((object)nm);
}

When the ModelState is invalid (it happens when use does not enter both his name and membership on the form) then I am creating Microsoft.AspNetCore.Mvc.BadRequestObjectResult for the response which produces Microsoft.AspNetCore.Http.StatusCodes.Status400BadRequest. The below code does this work.

return BadRequest(ModelState);

Next, create Entry.cshtml razor view file inside the Views ➤ Party folder of the MyAppT project. It’s code is given below:

@model string

@{
    ViewData["Title"] = "Add Party Entry";
}

<h1 class="bg-info text-white">Add Party Entry</h1>
<div asp-validation-summary="All" class="text-danger btn-light"></div>

<form class="m-1 p-1" method="post">
    <div class="form-group">
        <label>Name</label>
        <input name="Name" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label>Membership</label>
        <input name="Membership" type="text" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

@{
    if (Model != null)
    {
        <h2 class="bg-danger text-white">Welcome to the Party</h2>
        <h2 class="bg-info text-white">Tomorrow at 9:00 PM</h2>
        <table class="table table-sm table-bordered">
            <tr>
                <th>Name</th>
                <th>Membership</th>
            </tr>

            <tr>
                <td>@Model.ToString().Split(',')[0]</td>
                <td>@Model.ToString().Split(',')[1]</td>
            </tr>
        </table>
    }
}

This view presents a form for entering the name and membership for the party.

<form class="m-1 p-1" method="post">
    <div class="form-group">
        <label>Name</label>
        <input name="Name" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label>Membership</label>
        <input name="Membership" type="text" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

The same view also shows the welcome message along with the party timings when the form is submitted successfully.

@{
    if (Model != null)
    {
        <h2 class="bg-danger text-white">Welcome to the Party</h2>
        <h2 class="bg-info text-white">Tomorrow at 9:00 PM</h2>
        <table class="table table-sm table-bordered">
            <tr>
                <th>Name</th>
                <th>Membership</th>
            </tr>

            <tr>
                <td>@Model.ToString().Split(',')[0]</td>
                <td>@Model.ToString().Split(',')[1]</td>
            </tr>
        </table>
    }
}

Well, that’s all, now run and see how this controller works. I have shown the working in the below video.

party form video

Writing Test Methods for controllers in xUnit

Let I will write test cases for the Party Controller. I will be writing 3 test methods for the controller. These methods are named as:

  • 1. Test_Entry_GET_ReturnsViewResultNullModel
  • 2. Test_Entry_POST_InvalidModelState
  • 3. Test_Entry_POST_ValidModelState

Create a new class called TestParty to the TestingProject. In this class the above 3 test methods will be added.

Why so long names? The reason is to make sense. For example, read Test_Entry_GET_ReturnsViewResultNullModel without spaces, which makes it Test Entry GET ReturnsViewResultNullModel. In short it means to test Entry method of type HTTP GET to confirm that it returns ViewResult with null Model. Naming test methods so long has the advantage that they can be easily understood by other developers of your team.
1. Test_Entry_GET_ReturnsViewResultNullModel

Inside this TestParty.cs class add a new Test method called Test_Entry_GET_ReturnsViewResultNullModel whose code is given below. I have shown it’s code below and also highlighted it with a different color.

using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;

namespace TestingProject
{
    public class TestParty
    {
        [Fact]
        public void Test_Entry_GET_ReturnsViewResultNullModel()
        {
            // Arrange
            var controller = new PartyController();

            // Act
            var result = controller.Entry();

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            Assert.Null(viewResult.ViewData.Model);
        }
    }
}

Recall the “Entry” action of HTTP GET type returns IActionResult type with a null model.

public IActionResult Entry()
{
    return View();
} 

Therefore, in the test method I used Assert.IsType to verify that the return type of the action method is of ViewResult.

var viewResult = Assert.IsType<ViewResult>(result);

You may say why ViewResult instead of IActionResult ? The reason is that IActionResult is an interface and ViewResult is a class that implements IActionResult interface. You can only check a class type with IsType method and so this is the reason why I have used ViewResult here.

Next, I have checked that the action method returns a null model. This checking is done by Assert.Null method of xUnit.

Assert.Null(viewResult.ViewData.Model);
2. Test_Entry_POST_InvalidModelState

Next add the test method called Test_Entry_POST_InvalidModelState whose full code is shown below:

using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;

namespace TestingProject
{
    public class TestParty
    {
        //…

        [Fact]
        public void Test_Entry_POST_InvalidModelState()
        {
            // Arrange
            var controller = new PartyController();

            // Act
            var result = controller.Entry(null, null);

            // Assert
            var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
            Assert.IsType<SerializableError>(badRequestResult.Value);
        }
    }
}

When user submits the form without filling his name and membership then he/she get’s the bad request, and in this test method I am testing this case only.

I called the Entry action by providing null values for the name and membership parameters.

var result = controller.Entry(null, null);

Then checking if the action result is of BadRequestObjectResult.

var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);

I also tested for SerializableError for the value returned.

Assert.IsType<SerializableError>(badRequestResult.Value);
3. Test_Entry_POST_ValidModelState

Next add the test method called Test_Entry_POST_ValidModelState whose full code is shown below:

using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;

namespace TestingProject
{
    public class TestParty
    {
        //...

        [Fact]
        public void Test_Entry_POST_ValidModelState()
        {
            // Arrange
            string name = "Tom Cruise", membership = "Platinum";
            var controller = new PartyController();

            // Act
            var result = controller.Entry(name, membership);

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            var model = Assert.IsAssignableFrom<string>(viewResult.ViewData.Model);
        }
    }
}

When the user fill both his/her name and membership on the form and submits it. Then I am returning to the view with the name and membership (which is the model value) in string type.

if (ModelState.IsValid)
{
    string nm = name + "," + membership;
    return View((object)nm);
}

In this test method, I perform testing to make sure the returned value from the action is ViewResult.

var viewResult = Assert.IsType<ViewResult>(result);

Also checking if the model value is of string type.

var model = Assert.IsAssignableFrom<string>(viewResult.ViewData.Model);

Now run all the test you will see all these 3 tests passes successfully. I have shown them in the below image.

all tests passed xUnit

How to Debug Test Methods with Breakpoints

If you want to Debug any test method then right click the name of the test in the Test Explorer and select Debug. It is useful if you want to put a breakpoint on any test method and then select Debug option. See the below image which shown how to select the Debug option.

unit test debug

Once breakpoint is hit on the test method then move line by line ahead by pressing F10 key and check the values of the code lines.

Check the below video where I am debugging a test method by putting a breakpoint and selecting the Debug option.

Conclusion

In this tutorial I have explained and created tests method to perform testing for the class methods and also for Controllers of ASP.NET Core app. I hope you enjoyed learning them. Please share it on facebook and twitter.

SHARE THIS ARTICLE

  • linkedin
  • reddit

ABOUT THE AUTHOR

I am Yogi S. I write DOT NET artciles on my sites hosting.work and yogihosting.com. You can connect with me on Twitter. I hope my articles are helping you in some way or the other, if you like my articles consider buying me a coffee - Buy Me A Coffee

Leave a Reply

Your email address will not be published.

Related Posts based on your interest