In this post we will see how to write an Unit Test for a WebAPI method that uses an Action Filter.

We will write tests for the code we saw on this post on how to add Action Filters to WebAPI methods.

The code we want to test is this one:

using TestWebAPI_1.Models;
using TestWebAPI_1.Repository;
using TestWebAPI_1.Contracts;
using TestWebAPI_1.Helpers.Filters;

namespace TestWebAPI_1.Controllers
{

    [RoutePrefix("api/Person")]
    [MyCustomAttribute]
    public class PersonController : ApiController
    {
        public IPersonRepository repository = new PersonRepository();

        [HttpPost]
        [Route("GetAll")]
        public List GetAll(ModelData keyData)
        {
            return repository.GetAll();
        }
    }
}

Creating a project for our Unit Tests

We will start for adding a new project – a Class Library type of project – to our Visual Studio solution.

In this post we will use XUnit as our testing framework. If you are familiar with other testing frameworks like NUnit, you will find it quite easy.

We start by adding the xunit NuGet Package, and the xunit.runner.visualstudio NuGet Package to our project.

Adding XUnit:
Add Xunit NuGet package

Adding xunit.runner.visualstudio to be able to run the tests from inside Visual Studio:
Add xunit.runner.visualstudio NuGet package

We will also add another pair of libraries that will make writing tests easier: Moq (mocking framework), and FluentAssertions (a library that allows us to write easier to read assertions).

Adding Moq:
Add Moq NuGet package

Adding FluentAssertions:
Add FluentAssertions NuGet package

Creating a test class for our controller

In XUnit we use the attribute Fact to indicate that a method is a test:

namespace Tests
{
    public class Test_PersonController
    {
        [Fact]
        public void GetAll_ReturnsCorrectResult()
        {

        }
}

If we check the method GetAll we see that it has an external dependency: indeed it relies in an IPersonRepository to retrieve the data.

We could run the test agains the real repository but that it is not advisable: we are trying after all to run an unit test, we are concerned with testing the correctness of our GetAll method in PersonController. By using the real repository we are adding extra noise since we have to rely in the correctness of the implementation of the repository (that should have its own test). Therefore we are going to use the Moq library we installed before to mock the repository:

List expected = new List() { new Person
            {
                Id=new Guid("D4CD5EE6-6E79-495F-AADC-6FCAA2E865ED"),
                Name = "Person1"
            } };

var mockPersonRepository = new Mock();
mockPersonRepository.Setup(d => d.GetAll()).Returns(expected);

What we are saying there is that we are going to create a Moq of type IPersonRepository that will intercept the call to GetAll and return always the expected list.

All in all the final test looks like this:

using System;
using System.Collections.Generic;
using TestWebAPI_1.Controllers;
using TestWebAPI_1.Models;
using Xunit;
using TestWebAPI_1.Contracts;
using Moq;
using TestWebAPI_1.Helpers.Filters;

namespace Tests
{
    public class Test_PersonController
    {

        [Fact]
        public void GetAll_ReturnsCorrectResult()
        {
            List expected = new List() { new Person
            {
                Id=new Guid("D4CD5EE6-6E79-495F-AADC-6FCAA2E865ED"),
                Name = "Person1"
            } };

            var mockPersonRepository = new Mock();
            mockPersonRepository.Setup(d => d.GetAll()).Returns(expected);

            PersonController personController = new PersonController();
            personController.repository = mockPersonRepository.Object;

            ModelData modelData = new ModelData();

            var allPersonList = personController.GetAll(modelData);
            Assert.Equal(expected, allPersonList);
        }
}

Now if in Visual Studio we go to Test > Windows > Test Explorer we can run the test.

Action Filter not firing when running the Unit Test

If you run the unit test above you will notice that the test passes. That may come as unexpected since the custom filter MyCustomAttribute should be throwing an exception. Let´s review why.

Code of MyCustomAttribute:

using System;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using TestWebAPI_1.Models;

namespace TestWebAPI_1.Helpers.Filters
{
    public class MyCustomAttribute : ActionFilterAttribute
    {
        string parameter="keyData";
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ActionArguments == null || !actionContext.ActionArguments.ContainsKey(parameter))
                throw new Exception(string.Format("Parameter '{0}' not present", parameter));

            ModelData model = actionContext.ActionArguments[parameter] as ModelData;

            if (String.IsNullOrEmpty(model.id) || String.IsNullOrEmpty(model.code))
                throw new Exception("Error: could not find key data");
        }
    }
}

The custom filter is checking that we are sending the parameters code and id in the petition and that they are not empty.

In our implementation of the test, however, we are sending id and code as null. See the relevant lines in GetAll_ReturnsCorrectResult:

// ...
ModelData modelData = new ModelData();
var allPersonList = personController.GetAll(modelData);
//...

And yet no exception is thrown. How comes?

The problems is that when calling the controller directly from the test you are bypassing the normal chain of events in the MVC Framework: translated the code of the custom attribute it is not being called.

So what can we do now? The suggested approach in those cases it is to test the controller and the custom attribute separately:

  • Write a test for the custom attribute
  • In the controller test that the method/class is decorated with the custom attribute

This way we can both ensure that the attribute works correctly, and also can prevent future regression errors like inadvertently remove a necessary attribute from the controller.

Unit testing a custom a method has an ActionFilter

So I need to modify the above test to check the class is decorated with the right attribute. That can be easily done using the FluentAssertions library we installed before:

// ...
typeof(PersonController).Should().BeDecoratedWith();
//...

All put together:

 [Fact]
        public void GetAll_ReturnsCorrectResult()
        {
            List expected = new List() { new Person
            {
                Id=new Guid("D4CD5EE6-6E79-495F-AADC-6FCAA2E865ED"),
                Name = "Person1"
            } };

            var mockPersonRepository = new Mock();
            mockPersonRepository.Setup(d => d.GetAll()).Returns(expected);

            PersonController personController = new PersonController();
            personController.repository = mockPersonRepository.Object;

            ModelData modelData = new ModelData();

            var allPersonList = personController.GetAll(modelData);
            Assert.Equal(expected, allPersonList);
            typeof(PersonController).Should().BeDecoratedWith();
}
Advertisements