Bradley Braithwaite
1 import {
2 Learning,
3 JavaScript,
4 AngularJS
5 } from 'Brad on Code.com'
6 |
Check out my online course: AngularJS Unit Testing in-depth with ngMock.

Testing an ASP.Net Web API : 10 Years of .Net Compressed into Weeks #15

on
on methodology, asp.net 10 years on, c#, MSTest, Web API, TDD

This post looks at testing an ASP.Net Web API. This post is part of a blog series ASP.Net 10 Years On. Even though this is part of a series I have tried to make each post standalone. In a previous post we looked at creating an ASP.Net Web API. In this post we cover options of how we can test the API.

To set up some context, consider the following API Controller with a Get (by id) action:

public class UsersController : ApiController
{
 private readonly IUserRepository _userRepository;
 private readonly IMapper _mapper;

 public UsersController(IUserRepository userRepository, IMapper mapper)
 { 
     _userRepository = userRepository;
   _mapper = mapper;
 }

 // GET users/B5608F8E-F449-E211-BB40-1040F3A7A3B1
 public GetUser Get(Guid id)
 {
     var user = _userRepository.FindByID(id);
     if (user == null) 
  {
   throw new NotFoundException();
     }
  return _mapper.Map<User, GetUser>(user);
 }
}

Pretty simple right? And I should probably write tests for this or if I were a real TDD rockstar I would have already written the tests before the controller method!

Unit Tests

If we were using test driven development we would end up with unit tests that resemble something like the following:

/// <summary>
/// Ger valid user by id returns a result
/// </summary>
[TestMethod]
public void Get_User_By_Id_Returns_User()
{
    // Arrange
    var repo = new Mock<IUserRepository>();
    repo.Setup(s => s.FindByID(It.IsAny<Guid>())).Returns(GetUserResponse());

    var mapper = new Mock<IMapper>();
    mapper.Setup(m => m.Map<User, GetUser>(It.IsAny<User>())).Returns(new GetUser());

    var controller = new UsersController(repo.Object, mapper.Object);

    // Act
    var response = controller.Get(Guid.NewGuid());

    // Assert
    Assert.IsNotNull(response);
}

/// <summary>
/// Ger valid user by id returns a result
/// </summary>
[TestMethod]
public void Get_User_With_Invalid_Id_Throws_Not_Found_Exception()
{
    // Arrange
    var controller = new UsersController(new Mock<IUserRepository>().Object, null);

    // Assert
    Assert.Throws<NotFoundException>(() => controller.Get(Guid.NewGuid()));
}

I love to write unit tests, but I also love to be practical. A thin API method with a single IF condition requires a lot of test code for very gain. But, I want tests! What I'm really looking for in this context is a way of testing the entire stack via a method that yields rapid feedback.

Integration Tests

Whenever I find myself questioning the pragmatism of writing a unit test it usually indicates that I need integration tests. We've looked at integration tests previously for the data access layer but this time I want to test the API. It is possible to write integration tests using a tool such as SOAP UI but I'm testing as I'm developing and I want the tight feedback loop within Visual Studio rather than having to context switch between applications.

Here's how the integration test unfolded as I was coding. I started with this:

[TestMethod]
public void UsersControllerTests()
{
    GetUser user = Post();
    Get();
    Get(user);
    Put(user);
    Delete(user);
}

This was the first block of code I wrote. I then proceeded to create each method until I ended up with the following:

[TestClass]
public class UserTests
{
    private HttpClient _client;

    [TestInitialize]
    public void TestInitialize()
    {
        _client = ApiClient.GetAuthenticatedClient();
    }

    [TestMethod]
    public void UsersControllerTests()
    {
        GetUser user = Post();
        Get();
        Get(user);
        Put(user);
        Delete(user);
    }

    [TestCleanup]
    public void CleanUp()
    {
        _client.Dispose();
    }

    private GetUser Post()
    {
        // Arrange
        var user = new PostUser()
        {
            Username = RandomGenerator.GetRandomKey(10),
            Password = RandomGenerator.GetRandomKey(10)
        };

        // Act
        var response = _client.PostAsJsonAsync("users", user).Result;

        // Assert
        Assert.AreEqual(HttpStatusCode.Created, response.StatusCode, "POST user not ok.");

        return response.Content.ReadAsAsync<GetUser>().Result;
    }

    private void Get()
    {
        var list = _client
         .GetAsync("users")
         .Result.Content.ReadAsAsync<IEnumerable<GetUser>>()
         .Result;

        Assert.IsTrue(list.Any());
    }

    private GetUser Get(GetUser user)
    {
        // Act
        var response = _client.GetAsync(GetUrRequestUri(user)).Result;

        // Assert
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Get user by ID not OK.");

        return response.Content.ReadAsAsync<GetUser>().Result;
    }

    private void Put(GetUser user)
    {
        // Arrange
        user.Username = RandomGenerator.GetRandomKey(10);

        // Act
        var response = _client.PutAsJsonAsync(GetUrRequestUri(user), user).Result;

        // Assert
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "PUT User not OK.");
        Assert.AreEqual(user.Username, Get(user).Username, "PUT User not updated.");
    }

    private void Delete(GetUser user)
    {
        // Act
        var response = _client.DeleteAsync(GetUrRequestUri(user)).Result;
        var deletedStatus = _client.GetAsync(GetUrRequestUri(user)).Result.StatusCode;

        // Assert
        Assert.AreEqual(HttpStatusCode.NoContent, response.StatusCode, "DELETE User not No Content.");
        Assert.AreEqual(HttpStatusCode.NotFound, deletedStatus, "DELETE User not deleted.");
    }

    private static string GetUrRequestUri(GetUser user)
    {
        return "users/" + user.ID;
    }
}

It's slightly unconventional in the way the individual methods construct a test sequence but it took me about 20 minutes to put together (along with writing this) and now I know my stack works and I also have an entry point for any future changes. Worth it? Definitely! Next step is to apply this template to all the controllers in the API slaying bugs as I go.

Conclusion: unit tests are a match made in heaven when it comes to business logic in the domain model but as we flow out of the core logic into Service, MVC Controller and API Controller layers (with a very big assumption that these are generally thin layers) the value of unit testing begins to diminish even if you are applying test first.

The source code for this (work in progress) project can be found at: github.com/bbraithwaite/smsquiz.

Key code elements discussed in this post:

In the next post we look at Designing an MVC UI Layer for use with Web API.</p></blockquote>

SHARE
Don't miss out on the free technical content:

Subscribe to Updates

CONNECT WITH BRADLEY

Bradley Braithwaite Software Blog Bradley Braithwaite is a software engineer who works for search engine start-ups. He is a published author at pluralsight.com. He writes about software development practices, JavaScript, AngularJS and Node.js via his website . Find out more about Brad. Find him via:
You might also like:
mean stack tutorial AngularJS Testing - Unit Testing Tutorials