RSS

Unit test WebAPI Controllers with Model Validation

17 Oct

I updated the Employees Sample (ASP.Net Web API + Knockout) and added unit tests for the controller, and let me till you it’s not easy as it seems. the journey is filled with tricks. I used moq to create mocks for repository and httpcontext and MS Test as test framework.

What to test?

If we think a little about Web API, its main purpose is to provide an easy to use REST service that can be used from apps or web pages. All of the code that matters is in the Controllers folder, and in in our case the EmployeesController class, which is the main API for the project. so we don’t need to test classes like “BundleConfig,FilterConfig,…”. These classes are just configuration inside code. Lets review the EmployeesController class

    public class EmployeesController : ApiController
    {
        private IRepository _empRepo;
        public EmployeesController(IRepository empRepo)
        {
            _empRepo = empRepo;
        }
        public IEnumerable GetEmployees()
        {...}

        public HttpResponseMessage PutEmployee(int id, EmployeeForm employee)
        {...}

        public HttpResponseMessage PostEmployee(EmployeeForm employee)
        {...}

        public HttpResponseMessage DeleteEmployee(int id)
        {...}
    }

In our sample we have four main methods each one is for an HTTP verb Get, Post, Put and Delete. So we need to unit test each one of them. But we’ll have to watch out for:

  1. We’ll have to mock the repository using Moq. We’ve developed the api controller repository using interface and this will come in handy in unit testing “Thank you IRepository”.
  2. We’ll have to configure the MVC environment in order to make Employees controller work properly.
  3. We won’t be able to test validation unless we enforce it to happen. “We’ll know how and why”
  4. Making sure a method in the repository has been called. We will do this using Setup() and Verify methods of Moq framework

Preparation

Before we start we have to initialize the controller and I did this by a helper method

public static EmployeesController SetupControllerForTests(Mock<IRepository<Employee>> employeesRepo)
{
   var config = new HttpConfiguration();
   var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/employees");
   request.RequestUri = new Uri("http://localhost/api/employees");
   var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
   var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "employees" } });
   var employeesController = new EmployeesController(employeesRepo.Object)
                {
                    ControllerContext = new HttpControllerContext(config, routeData, request),
                    Request = request
                };
   employeesController.Request.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
   employeesController.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
   return employeesController;
}

Unit testing GetEmployees()

This is the simplest one. just mock the repository and controller and you’re ready to call it and assert for the return. Here is the code I wrote.

[TestMethod]
public void GetEmployees_Action_Returns_Valid_IEnumerable_Of_Employees()
{
   var mockRepo = new Mock<IRepository<Employee>>();
   var expectedRet = TestHelpers.ValidEmployeesList();
   mockRepo.Setup(x => x.Entities).Returns(expectedRet);
   var employeesController = TestHelpers.SetupControllerForTests(mockRepo);
   var result = employeesController.GetEmployees();
   Assert.IsInstanceOfType(result, typeof (IEnumerable<EmployeeForm>),
              "The employeesController.GetEmployees() return is not of type IEnumerable<Employee>");
   Assert.IsNotNull(result, "The employeesController.GetEmployees() return is null");
   var res = result.ToList();
   Assert.AreEqual(expectedRet.Count(), res.Count(),string.Format(
       "The employeesController.GetEmployees() return count is {0} it's supposed to be {1} (not correct)",
       res.Count(), expectedRet.Count()));
}

Unit testing PostEmployee(EmployeeForm employee)

This method will be hard. because if you call the method directly using the controller class it won’t run the validation. This means If you use ModelState.isValid in your controller classes during unit testing it will always return true. The validation occurs when MVC maps the request parameters to objects. So we’ll have to enforce running the validation. Thanks to by rhernandez for his post on that subject I tweaked the code for WebAPI. and here it is

public static HttpResponseMessage CallWithModelValidation<C, R, T>(this C controller
, Func<C, R> action, T model)
where C : ApiController
where R : HttpResponseMessage
where T : class
{
   DataAnnotationsModelValidatorProvider provider = new DataAnnotationsModelValidatorProvider();
   IEnumerable<ModelMetadata> metadata = ModelMetadataProviders.Current.GetMetadataForProperties(model, typeof(T));
   foreach (ModelMetadata modelMetadata in metadata)
   {
     IEnumerable<ModelValidator> validators = provider.GetValidators(modelMetadata, new ControllerContext());
     foreach (ModelValidator validator in validators)
     {
        IEnumerable<ModelValidationResult> results = validator.Validate(model);
        foreach (ModelValidationResult result in results)
           controller.ModelState.AddModelError(modelMetadata.PropertyName, result.Message);
     }
   }
   return action(controller);
}

Now we can use this method instead of calling the post directly and it will ensure validating the model. here is the unit test for post. “for short I’ll include only two cases”

        [TestMethod]
        public void The_PostEmployee_Action_returns_created_statuscode_When_The_Employee_Model_Is_Valid()
        {
            // Arrange
            var employeesRepo = new Mock<IRepository<Employee>>();
            var employeesController = TestHelpers.SetupControllerForTests(employeesRepo);
            var employee = Mapper.Map<Employee, EmployeeForm>(TestHelpers.ValidEmployeesList().ToList()[0]);

            // Act
            var result = employeesController.CallWithModelValidation(m => m.PostEmployee(employee), employee);

            // Assert
            Assert.IsNotNull(result, "Should have returned a HttpResponseMessage");
            Assert.IsTrue(result.IsSuccessStatusCode, "Status Code Should be success");
            Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code");
        }

        [TestMethod]
        public void The_PostEmployee_Action_Calls_Save_When_The_Employee_Model_Is_Valid()
        {
            // Arrange
            var employeesRepo = new Mock<IRepository<Employee>>();
            var employee = Mapper.Map<Employee, EmployeeForm>(TestHelpers.ValidEmployeesList().ToList()[0]);

            employeesRepo.Setup(m => m.Save(It.IsAny<Employee>()));

            var employeesController = TestHelpers.SetupControllerForTests(employeesRepo);

            // Act
            var result = employeesController.CallWithModelValidation(m => m.PostEmployee(employee), employee);

            // Assert
            employeesRepo.Verify(x => x.Save(It.IsAny<Employee>()), Times.Once(),
                                 "EmployeesController should have called save on repository");
        }

Unit testing PutEmployee(int id, EmployeeForm employee)

Just the same as post testing except for logic. The employee has to exist before we call save. Here is part of the unit testing code

        [TestMethod]
        public void The_PutEmployee_Action_returns_Ok_statuscode_When_The_Employee_Exists()
        {
            // Arrange
            var employeesRepo = new Mock<IRepository<Employee>>();
            var expectedRet = TestHelpers.ValidEmployeesList();
            employeesRepo.Setup(x => x.Entities).Returns(expectedRet);
            var employeesController = TestHelpers.SetupControllerForTests(employeesRepo);
            var employee = Mapper.Map<Employee, EmployeeForm>(TestHelpers.ValidEmployeesList().ToList()[2]);

            // Act
            var result = employeesController.CallWithModelValidation(m => m.PutEmployee(3,employee), employee);

            // Assert
            Assert.IsNotNull(result, "Should have returned a HttpResponseMessage");
            Assert.IsTrue(result.IsSuccessStatusCode, "Status Code Should be success");
            Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code");
        }

        [TestMethod]
        public void The_PutEmployee_Action_returns_NotFound_statuscode_When_The_Employee_Does_NOT_Exists()
        {
            // Arrange
            var employeesRepo = new Mock<IRepository<Employee>>();
            var expectedRet = TestHelpers.ValidEmployeesList();
            employeesRepo.Setup(x => x.Entities).Returns(expectedRet);
            var employeesController = TestHelpers.SetupControllerForTests(employeesRepo);
            var employee = Mapper.Map<Employee, EmployeeForm>(TestHelpers.ValidEmployeesList().ToList()[2]);
            int notExistId = 876;
            // Act
            var result = employeesController.CallWithModelValidation(m => m.PutEmployee(notExistId,employee), employee);

            // Assert
            Assert.IsNotNull(result, "Should have returned a HttpResponseMessage");
            Assert.IsFalse(result.IsSuccessStatusCode, "Status Code Should be success");
            Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode, "Invalid status code");
        }

Unit testing DeleteEmployee(int id)

Just the same as post testing except for logic. The employee has to exist before we call delete and returning HttpStatusCode.Accepted in case of success. Here is part of the unit testing code

        [TestMethod]
        public void DeleteEmployee_Calls_Repository_Delete()
        {
            var employeesRepo = new Mock<IRepository<Employee>>();
            int itemToDelete = 3;
            var expectedRet = TestHelpers.ValidEmployeesList();
            employeesRepo.Setup(x => x.Entities).Returns(expectedRet);
            employeesRepo.Setup(x => x.Delete(itemToDelete));
            var employeesController = TestHelpers.SetupControllerForTests(employeesRepo);

            employeesController.DeleteEmployee(itemToDelete);

            employeesRepo.Verify(x => x.Delete(itemToDelete), Times.Once(),
                                 "EmployeesController should have called Delete on repository");
        }

        [TestMethod]
        public void DeleteEmployee_Returns_Response_Message_With_NotFound_StatusCode()
        {
            var employeesRepo = new Mock<IRepository<Employee>>();
            int itemToDelete = 355;
            var expectedRet = TestHelpers.ValidEmployeesList();
            employeesRepo.Setup(x => x.Entities).Returns(expectedRet);
            var employeesController = TestHelpers.SetupControllerForTests(employeesRepo);

            var result = employeesController.DeleteEmployee(itemToDelete);

            Assert.IsInstanceOfType(result, typeof (HttpResponseMessage), "Should return HttpResponseMessage");
            Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode, "Should return StatusCode == NotFound");
        }

Advertisements
 
Leave a comment

Posted by on October 17, 2012 in ASP.Net Web API, C#, Unit Test

 

Tags: , , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: