Unit testing a ASP.NET WebAPI 2 controller

Earlier this year I wrote a blog post about how to unit test ASP.NET WebAPI controllers. It turned out that in order to do a good job of testing the public ApiController methods you would need to quite a bit of setup. Most of the time this was just about providing a HttpConfiguration object but sometimes, like in the case of responding to a post request, quite a bit more would be required.

One of the goals of the ASP.NET WebAPI 2 was to make testing of controllers easier. This has been achieved by adding a new response interface named IHttpActionResult and a number of common implementations. Additionally the ApiController class contains a number of utility methods like Ok(), BadRequest(), CreatedAtRoute() and InternalServerError() that make it easy to return the correct response.

 

So how does this change the unit testing story?

In this blog post I am going to rewrite the source code from the previous blog post using these new capabilities and compare the two.

 

Lets start with the two HTTP GET methods.

 
   1: // GET api/books


   2: public IHttpActionResult Get()


   3: {


   4:     return Ok(_repo.GetBooks().AsEnumerable());


   5: }


   6:  


   7: // GET api/books/5


   8: public IHttpActionResult Get(int id)


   9: {


  10:     var book = _repo.GetBook(id);


  11:  


  12:     if (book == null)


  13:     {


  14:         return NotFound();


  15:     }


  16:  


  17:     return Ok(book);


  18: }

 
These methods are almost the same. We are using the new interface and helper methods but other than that no big deal. So how about testing them?
 

Testing the Get() method

The Get() method as real easy to test before as we where just returning an IEnumerable<Book>. Using the new approach actually takes a bit more test code.
 
   1: [TestMethod]


   2: public void WhenGettingItShouldReturnAllBooks()


   3: {


   4:     // Arrange


   5:     var controller = new BooksController();


   6:  


   7:     // Act


   8:     var actionResult = controller.Get();


   9:  


  10:     // Assert


  11:     var response = actionResult as OkNegotiatedContentResult<IEnumerable<Book>>;


  12:     Assert.IsNotNull(response);


  13:     var books = response.Content;


  14:     Assert.AreEqual(5, books.Count());


  15: }

 
 

Testing the Get(id) method

Testing the Get(id) method required us to create a HttpConfiguration object before. With the new way of doing things this is no longer needed. This makes the arrange part of both the test real simple and definitely an improvement.
 
The happy path:
 
   1: [TestMethod]


   2: public void WhenGettingWithAKnownIdItShouldReturnThatBook()


   3: {


   4:     // Arrange


   5:     var controller = new BooksController();


   6:  


   7:     // Act


   8:     var actionResult = controller.Get(1);


   9:  


  10:     // Assert


  11:     var response = actionResult as OkNegotiatedContentResult<Book>;


  12:     Assert.IsNotNull(response);


  13:     Assert.AreEqual(1, response.Content.Id);


  14: }

 
The unhappy path:
 
   1: [TestMethod]


   2: public void WhenGettingWithAnUnknownIdItShouldReturnNotFound()


   3: {


   4:     // Arrange


   5:     var controller = new BooksController();


   6:  


   7:     // Act


   8:     var actionResult = controller.Get(999);


   9:  


  10:     // Assert


  11:     Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult));


  12: }

 
 

Testing an HTTP PUT operation

Just like Get() methods the Put() method has changed only slightly.
 
   1: // PUT api/books/5


   2: public IHttpActionResult Put(int id, Book book)


   3: {


   4:     if (!ModelState.IsValid)


   5:     {


   6:         return BadRequest(ModelState);


   7:     }


   8:  


   9:     return Ok(_repo.UpdateBook(book));


  10: }

 
 
And just like with when testing the Get() methods the test setup becomes easier. Nice :-)
 
   1: [TestMethod]


   2: public void WhenPuttingABookItShouldBeUpdated()


   3: {


   4:     // Arrange


   5:     var controller = new BooksController();


   6:  


   7:     // Act


   8:     var book = new Book { Id = 1, Title = "New Title", Author = "New Author" };


   9:     var actionResult = controller.Put(book.Id, book);


  10:  


  11:     // Assert


  12:     var response = actionResult as OkNegotiatedContentResult<Book>;


  13:  


  14:     Assert.IsNotNull(response);


  15:     var newBook = response.Content;


  16:     Assert.AreEqual(1, newBook.Id);


  17:     Assert.AreEqual("New Title", newBook.Title);


  18:     Assert.AreEqual("New Author", newBook.Author);


  19: }

 
 

Adding new data using an HTTP POST action

The last method to look at is the Post() method. This as the one that that took most setup work to test as it also required RouteData.
 
The new Post() method is also quite a but simpler as we don’t need to worry about the URL in the HTTP Location header ourselves, the framework takes care of that.
 
   1: // POST api/books


   2: public IHttpActionResult Post(Book book)


   3: {


   4:     if (!ModelState.IsValid)


   5:     {


   6:         return BadRequest(ModelState);


   7:     }


   8:  


   9:     var newBook = _repo.AddBook(book);


  10:     return CreatedAtRoute("DefaultApi", new { newBook.Id }, newBook);


  11: }

 
So does this make the unit test simpler?
 
Fortunately it does. Just like with the Get() and Put() all extra WebAPI related setup code in the arrange part of out test is gone making the test much simpler. Doubly sweet :-)
 
   1: [TestMethod]


   2: public void WhenPostingANewBookShouldBeAdded()


   3: {


   4:     // Arrange


   5:     var controller = new BooksController();


   6:  


   7:     // Act


   8:     var actionResult = controller.Post(new Book


   9:     {


  10:         Title = "A new book",


  11:         Author = "The author"


  12:     });


  13:  


  14:     // Assert


  15:     var response = actionResult as CreatedAtRouteNegotiatedContentResult<Book>;


  16:  


  17:     Assert.IsNotNull(response);


  18:     Assert.AreEqual("DefaultApi", response.RouteName);


  19:     Assert.AreEqual(response.Content.Id, response.RouteValues["Id"]);


  20: }



 


One thing that has changed here is the asserts. We are no longer checking the Location HTTP header on the response as before. In this case we are just checking the data that the WebAPI framework uses to construct the Location header. I am perfectly fine with that as I am not unit testing the WebAPI code, I am assuming that the ASP.NET team has done that for me, but instead just my own code.


 


Conclusion



The new IHttpActionResult makes creating ASP.NET WebAPI 2 controllers simpler than before and enables a much simpler unit testing story. Sweet, simple is good :-) The only exception to this improvement is doing a Get() method the new way as this takes slightly more code. But this is not an all or nothing choice so we are perfectly free to keep that method and its test the same as they where before.



All together I like the effect of these changes :-)



 



Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>