Testing an ASP.Net Web API : 10 Years of .Net Compressed into Weeks #15
on
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>