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: }
Testing the Get() method
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
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: }
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
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: }
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
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: }
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: }
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!
How are you going to test for a POST or PUT in which the object is not as expected (ModelState.IsValid = false)?
Will this work?
14: Assert.AreEqual(5, books.Count());
Oops, it will work only if Linq is used. This line must be added to code:
using System.Linq;