Unit Testing of Web API with xUnit and Moq

Unit Testing of Web API with xUnit and Moq

In this tutorial I will show how to perform Unit Tests for Web API methods by using xUnit and Moq. I will be writing complete test method for GET, POST, PUT and DELETE methods of Web API. Make sure you read the complete tutorial so that you do not miss any part. The source codes for this tutorial can be downloaded from the link given at the bottom of this tutorial.

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

Project setup install xUnit and Moq

In this tutorial I will be using the same app that I built in my last tutorial How to perform Unit Testing with xUnit in ASP.NET Core. This app solution file has 2 projects in .NET 8.0, these are:

1. MyAppT

An ASP.NET Core MVC project.

2. TestingProject

A Class Library project that has 4 packages installed.

  1. Moq
  2. xunit
  3. xunit.runner.visualstudio
  4. Microsoft.NET.Test.Sdk

xUnit Mock Packages

Create Web API

Next create the Web API infrastructure. First create a new controller inside the Controllers folder of the “MyAppT”. Name it as ReservationController.cs. Its code is given below.

using Microsoft.AspNetCore.Mvc;
using MyAppT.Infrastructure;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ReservationController : ControllerBase
    {
        private IRepository repository;
        public ReservationController(IRepository repo) => repository = repo;

        [HttpGet]
        public IEnumerable<Reservation> Get() => repository.Reservations;

        [HttpGet("{id}")]
        public ActionResult<Reservation> Get(int id)
        {
            if (id == 0)
                return BadRequest("Value must be passed in the request body.");

	     Reservation r = repository[id];
            
            if (r is null)
                return NotFound(); 	
            
            return Ok(r);
        }

        [HttpPost]
        public Reservation Post([FromBody] Reservation res) =>
        repository.AddReservation(new Reservation
        {
            Name = res.Name,
            StartLocation = res.StartLocation,
            EndLocation = res.EndLocation
        });

        [HttpPut]
        public Reservation Put([FromBody] Reservation res) => 
                   repository.UpdateReservation(res);

        [HttpDelete("{id}")]
        public void Delete(int id) => repository.DeleteReservation(id);
    }
}

This controller has GET, POST, PUT and DELETE methods that perform creation, deletion, reading and updation of Reservation records.

I have used the Web API codes from my tutorial How to Create Web APIs in ASP.NET Core [RESTful pattern]. If you want to learn how Web APIs are made in ASP.NET Core then make sure you go through that tutorial to.

Next create an in-memory storage of Reservation records. For this add a class called Reservation.cs inside the Models folder. It’s code is given below:

namespace MyAppT.Models
{
    public class Reservation
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string StartLocation { get; set; }
        public string EndLocation { get; set; }
    }
}

Next add 2 files inside the Infrastructure folder. These files are:

1. An interface file called IRepository.cs. It’s code is given below:

using MyAppT.Models;

namespace MyAppT.Infrastructure
{
    public interface IRepository
    {
        IEnumerable<Reservation> Reservations { get; }
        Reservation this[int id] { get; }
        Reservation AddReservation(Reservation reservation);
        Reservation UpdateReservation(Reservation reservation);
        void DeleteReservation(int id);
    }
}

2. A Repository.cs class whose code is given below:

using MyAppT.Models;

namespace MyAppT.Infrastructure
{
    public class Repository : IRepository
    {
        private Dictionary<int, Reservation> items;

        public Repository()
        {
            items = new Dictionary<int, Reservation>();
            new List<Reservation> {
                new Reservation {Id=1, Name = "Ankit", StartLocation = "New York", EndLocation="Beijing" },
                new Reservation {Id=2, Name = "Bobby", StartLocation = "New Jersey", EndLocation="Boston" },
                new Reservation {Id=3, Name = "Jacky", StartLocation = "London", EndLocation="Paris" }
                }.ForEach(r => AddReservation(r));
        }

        public Reservation this[int id] => items.ContainsKey(id) ? items[id] : null;

        public IEnumerable<Reservation> Reservations => items.Values;

        public Reservation AddReservation(Reservation reservation)
        {
            if (reservation.Id == 0)
            {
                int key = items.Count;
                while (items.ContainsKey(key)) { key++; };
                reservation.Id = key;
            }
            items[reservation.Id] = reservation;
            return reservation;
        }

        public void DeleteReservation(int id) => items.Remove(id);

        public Reservation UpdateReservation(Reservation reservation) => AddReservation(reservation);
    }
}

Now add the Reservation Repository as a service inside the Program.cs class.

builder.Services.AddSingleton<IRepository, Repository>();

If you had any problem in adding the codes then simply download the source codes and navigate to the files to find the necessary codes.

This completes the setup, the API url is – https://localhost:7195/api/Reservation. Try opening this URL in your browser and you will see 3 Reservation records displayed in JSON, like shown by the image below.

reservations json

Testing Web API with xUnit and Moq

Let us now create test method in order to test the Web API we just created. Here I will write 7 test methods in total. Start by creating a new class called TestAPI.cs inside the TestingProject. In this class the test methods will be written.

So, let’s start creating them one by one.

1. Test_GET_AllReservations

Here I will test the Get method of Web API which returns a list of Reservation records. So add a method called Test_GET_AllReservations() whose code is given below:

using Moq;
using MyAppT.Controllers;
using MyAppT.Infrastructure;
using MyAppT.Models;
using Xunit;

namespace TestingProject
{
    public class TestAPI
    {
        [Fact]
        public void Test_GET_AllReservations()
        {
            // Arrange
            var mockRepo = new Mock<IRepository>();
            mockRepo.Setup(repo => repo.Reservations).Returns(Multiple());
            var controller = new ReservationController(mockRepo.Object);

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

            // Assert
            var model = Assert.IsAssignableFrom<IEnumerable<Reservation>>(result);
            Assert.Equal(3, model.Count());
        }

        private static IEnumerable<Reservation> Multiple()
        {
            var r = new List<Reservation>
            {
                new Reservation()
                {
                    Id = 1,
                    Name = "Test One",
                    StartLocation = "SL1",
                    EndLocation = "EL1"
                },
                new Reservation()
                {
                    Id = 2,
                    Name = "Test Two",
                    StartLocation = "SL2",
                    EndLocation = "EL2"
                },
                new Reservation()
                {
                    Id = 3,
                    Name = "Test Three",
                    StartLocation = "SL3",
                    EndLocation = "EL3"
                }
            };
            return r;
        }
    }
}

I created a Moq object as:

var mockRepo = new Mock<IRepository>();

Then did it’s setup by calling the Reservations property of Repository.cs class and returning an IEnumerable<Reservation> object by calling Multiple() method.

mockRepo.Setup(repo => repo.Reservations).Returns(Multiple());

The Multiple method returns 3 test Reservations, see it’s code defined above.

Next, I initialized the ReservationController as shown below.

var controller = new ReservationController(mockRepo.Object);

After that the Get method of the Web API is called.

var result = controller.Get();

Finally I did the verification for the return type to be IEnumerable<Reservation> and count of the model to be 3.

var model = Assert.IsAssignableFrom<IEnumerable<Reservation>>(result);
Assert.Equal(3, model.Count());

Run the test method and it will pass.

2. Test_GET_AReservations_BadRequest

Next, I will test the GET method and pass to it the id of the record to be returned by the API. Here I will test for the BadRequestObjectResult type.

So add the test method called Test_GET_AReservations_BadRequest () whose code is given below.

[Fact]
public void Test_GET_AReservations_BadRequest()
{
    // Arrange
    int id = 0;
    var mockRepo = new Mock<IRepository>();
    mockRepo.Setup(repo => repo[It.IsAny<int>()]).Returns<int>((a) => Single(a));
    var controller = new ReservationController(mockRepo.Object);

    // Act
    var result = controller.Get(id);

    // Assert
    var actionResult = Assert.IsType<ActionResult<Reservation>>(result);
    Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}

private static Reservation Single(int id)
{
    IEnumerable<Reservation> reservations = Multiple();
    return reservations.Where(a => a.Id == id).FirstOrDefault();
}

The mock setup is done by calling Single() method that accepts the id in the parameter and returns the corresponding reservation record. It’s code is also given above.

mockRepo.Setup(repo => repo[It.IsAny<int>()]).Returns<int>((a) => Single(a));

Also note how I called the Reservation property of Repository.cs class, and specified to accept any int type value by using It.IsAny() method of Moq.

repo => repo[It.IsAny<int>()]

Finally, In the assert section I checked if the return type of this API method is ActionResult<Reservation> which is done by the below code:

var actionResult = Assert.IsType<ActionResult<Reservation>>(result);

Since I passed the value of id to be 0 therefore the API will return BadRequestObjectResult which I have checked by the below code.

Assert.IsType<BadRequestObjectResult>(actionResult.Result);

3. Test_GET_AReservations_Ok

I now check for OkObjectResult result returned by the Get method of the API. It is similar to the above test method except that I passed id value as 1 which will make the API to return Ok result with the value of the first reservation.

The code of Test_GET_AReservations_Ok() method is given below.

[Fact]
public void Test_GET_AReservations_Ok()
{
    // Arrange
    int id = 1;
    var mockRepo = new Mock<IRepository>();
    mockRepo.Setup(repo => repo[It.IsAny<int>()]).Returns<int>((id) => Single(id));
    var controller = new ReservationController(mockRepo.Object);

    // Act
    var result = controller.Get(id);

    // Assert
    var actionResult = Assert.IsType<ActionResult<Reservation>>(result);
    var actionValue = Assert.IsType<OkObjectResult>(actionResult.Result);
    Assert.Equal(id, ((Reservation)actionValue.Value).Id);
}

The 3 tests are done which are:

a. Testing if the return type of the method is ActionResult<Reservation>.

var actionResult = Assert.IsType<ActionResult<Reservation>>(result);

b. Testing if the action retuned OkObjectResult.

var actionValue = Assert.IsType<OkObjectResult>(actionResult.Result);

c. The value of the reservation is indeed the first reservation. This check is done by the below code:

Assert.Equal(id, ((Reservation)actionValue.Value).Id);

4. Test_GET_AReservations_NotFound

Here I will check for the NotFoundResult object returned by the Get method of the API. So add the test method called Test_GET_AReservations_NotFound() whose code is given below.

[Fact]
public void Test_GET_AReservations_NotFound()
{
    // Arrange
    int id = 4;
    var mockRepo = new Mock<IRepository>();
    mockRepo.Setup(repo => repo[It.IsAny<int>()]).Returns<int>((id) => Single(id));
    var controller = new ReservationController(mockRepo.Object);

    // Act
    var result = controller.Get(id);

    // Assert
    var actionResult = Assert.IsType<ActionResult<Reservation>>(result);
    Assert.IsType<NotFoundResult>(actionResult.Result);
}

This method is very similar to the above test method except that I provided id of the reservation as 4, which is a non-existent, so the API will return NotFoundResult. This check is done by the below code:

Assert.IsType<NotFoundResult>(actionResult.Result);

5. Test_POST_AddReservation

Now let us test the Post method of the Web API. This method inserts a reservation record in the repository. Add test method called Test_POST_AddReservation() whose code is given below.

[Fact]
public void Test_POST_AddReservation()
{
    // Arrange
    Reservation r = new Reservation()
    {
        Id = 4,
        Name = "Test Four",
        StartLocation = "SL4",
        EndLocation = "EL4"
    };
    var mockRepo = new Mock<IRepository>();
    mockRepo.Setup(repo => repo.AddReservation(It.IsAny<Reservation>())).Returns(r);
    var controller = new ReservationController(mockRepo.Object);

    // Act
    var result = controller.Post(r);

    // Assert
    var reservation = Assert.IsType<Reservation>(result);
    Assert.Equal(4, reservation.Id);
    Assert.Equal("Test Four", reservation.Name);
    Assert.Equal("SL4", reservation.StartLocation);
    Assert.Equal("EL4", reservation.EndLocation);
}

Few things to note here are:

a. The mock setup is done by calling AddReservation() method faking it to return a reservation object “r”.

mockRepo.Setup(repo => repo.AddReservation(It.IsAny<Reservation>())).Returns(r);

b. Calling the Post method of the API and passing the reservation object “r”.

var result = controller.Post(r);

c. Checking for returned reservation object (which is added by the API) that is it’s Id, Name StartLocation and EndLocation values which must match with the added reservation.

Assert.Equal(4, reservation.Id);
Assert.Equal("Test Four", reservation.Name);
Assert.Equal("SL4", reservation.StartLocation);
Assert.Equal("EL4", reservation.EndLocation);

6. Test_PUT_UpdateReservation

The Put method of the API updates a reservation. This method will check for it’s working. The test method “Test_PUT_UpdateReservation()” code is given below.

[Fact]
public void Test_PUT_UpdateReservation()
{
    // Arrange
    Reservation r = new Reservation()
    {
        Id = 3,
        Name = "new name",
        StartLocation = "new start location",
        EndLocation = "new end location"
    };
    var mockRepo = new Mock<IRepository>();
    mockRepo.Setup(repo => repo.UpdateReservation(It.IsAny<Reservation>())).Returns(r);
    var controller = new ReservationController(mockRepo.Object);

    // Act
    var result = controller.Put(r);

    // Assert
    var reservation = Assert.IsType<Reservation>(result);
    Assert.Equal(3, reservation.Id);
    Assert.Equal("new name", reservation.Name);
    Assert.Equal("new start location", reservation.StartLocation);
    Assert.Equal("new end location", reservation.EndLocation);
}

I created a reservation object “r”. Then while doing mock object setup, I called “UpdateReservation” method of the Repository.cs class and returning the same object “r” from it.

mockRepo.Setup(repo => repo.UpdateReservation(It.IsAny<Reservation>())).Returns(r);

Next, I called the Put method of the Web API.

var result = controller.Put(r);

Then after that I verified if the reservation object is successfully modified or not.

Assert.Equal(3, reservation.Id);
Assert.Equal("new name", reservation.Name);
Assert.Equal("new start location", reservation.StartLocation);
Assert.Equal("new end location", reservation.EndLocation);

7. Test_DELETE_Reservation

The final test method tests the Delete method of the Web API. This test method is called Test_DELETE_Reservation and It’s code is given below.

[Fact]
public void Test_DELETE_Reservation()
{
    // Arrange
    var mockRepo = new Mock<IRepository>();
    mockRepo.Setup(repo => repo.DeleteReservation(It.IsAny<int>())).Verifiable();
    var controller = new ReservationController(mockRepo.Object);

    // Act
    controller.Delete(3);

    // Assert
    mockRepo.Verify();
}

Note that here I marked the setup as Verifiable, meaning, when the mockRepo.Verify() is called then the setup will also be verified. In the setup I called DeleteReservation() method.

mockRepo.Setup(repo => repo.DeleteReservation(It.IsAny<int>())).Verifiable();

Next, see the Delete API method is called and value of 3 for the reservation id is passed to it’s parameter.

controller.Delete(3);

The xUnit will verify that the DeleteReservation is indeed called or not.

The whole test methods are written which you can run and they will all passed. I have shown this in the below image.

web api xunit tests passes
Conclusion

In this tutorial you learned how to perform Web API tests with xUnit and Moq. I hope you enjoyed learning these concepts. Please share it in your social account so that others can also know about this tutorial.

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. Required fields are marked *

Related Posts based on your interest