Simon Miller Team : Web Development

Integration testing in .NET

Simon Miller Team : Web Development

Unit Tests – in the cowboy days of web development, writing code tests was the exception and not the rule. Today, as web projects get more and more complicated requiring more and more business rules, it is definitely a case of unit tests being the rule rather than the exception.

I will admit that I am late to the party on this one. Working on a recent, large scale six-month long web project has been my first real attempt at writing test code. Here are a few things I have learned along the way.

Integration Test versus Unit Test

As this was my first project to utilize tests, we decided to go down the integration test route. The difference is that unit tests are designed to test every functional code block in your project in an isolated state, whereas integration tests test against the controller actions. This different approach is highly suitable for web projects, as the tests are effectively emulating user action on views. It also means that code further down the chain will be inherently tested via the controller actions.

There are pros and cons for this approach – and it is obviously not traditional test-driven development – however I have found it a logical starting point for my first attempt at writing tests. And once I have suitable coverage of controller actions, there is nothing stopping me from writing additional unit tests directly against business logic functions.

A test is divided into three logical sections - Arrange, Act and Assert. First you arrange your dependecies for the test, then you act upon them, finally asserting results at the end.

A sample integration test looks like so:

[TestMethod]
public async Task MyController_Index_ShouldReturn8Results()
{
    // Arrange 
    var controller = new MyControllerController();
    // Act
    var result = await controller.Index(null) as ViewResult;
    // Assert
    Assert.IsNotNull(result);
    var model = result.Model as MyControllerListViewModel;
    Assert.IsNotNull(model);
    Assert.AreEqual(8, model.Items.Count);
}

Allow 10% of your time for tests

Other guides allow as much as 50%, but I have found that I have adequate test coverage by allowing – on average – about 10% of my time towards test writing. Were I to follow standard test-driven development practices and test every block of code in isolation, I am sure this percentage would be much higher.

Visual Studio’s tools are just fine

There is a lot of discussion about different platforms or plugins to perform the tests (NUnit, xUnit) that is a little daunting for the newcomer. My advice is to stick to the built in tools for your first run. It is very simple to create a Unit Test project and corresponding tests. I follow a folder/file hierarchy that mirrors the web project, that is, a folder called Controllers with a controller test file for each web project controller I am testing e.g. AccountControllerTests.cs

Inherit all tests from a base class

All my tests inherit from TestBase. This class is responsible for the Setup and Tear Down of the framework for the tests. At its most basic, we instantiate the test database for the tests to use and we rollback all transactions at the end. The below code should give you an idea (though is not a complete working example):

protected DataContextProvider ContextProvider = new TestDataContextProvider();
protected DbContextTransaction DbTransaction;
[TestInitialize]
public void Setup()
{
    ContextProvider = new TestDataContextProvider("TestDbEntities");
    BaseRepository.SetDataContextProvider(ContextProvider);
    DbTransaction = ContextProvider.Context.Database.BeginTransaction(IsolationLevel.ReadUncommitted);
}
[TestCleanup]
public void TearDown()
{
    DbTransaction.Rollback();
    DbTransaction.Dispose();
    ContextProvider.Context.Dispose();
}

Test every case in a function

The most important part of writing tests is to ensure you test every possible outcome of a function. This will include passing in a different set of values to the controller where applicable, including those that will test your exception handling.

One way of testing an exception is to force the database to go offline. In my test framework I simply null out the connection before calling the controller action:

BaseRepository.SetDataContextProvider(null);

It is easy to see how well you have all cases covered by analyzing your code coverage, available from the Test > Analyze Code Coverage > All Tests menu. Don’t aim for 100% coverage, but if you get over 85% of your code covered you are doing well.

Use mocks to emulate users and sessions

My first main hurdle was to emulate an authenticated user required for many of my controller actions. This is where mocking comes in to play.

Mocking allows you to spin-up and write to otherwise inaccessible internal methods, such as the HttpContext. The most popular extension if Moq. Using this extension I was able to ‘fake’ a user, an OWIN context and sessions in context so that null exceptions wouldn’t be thrown in the action as it was tested.

Through some trial and error, and help from StackExchange answers, I came up with a generic controller override function that gives the test access to valid user with claims, OWIN context, and request/response/session properties. The function as added to the TestBase class:

public T CreateControllerAs<t>(T controller, string userName, Guid id) where T : Controller
{
    var mock = new Mock<controllercontext>();
    var identity = new GenericIdentity(userName);
    identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", id.ToString()));
    var principal = new GenericPrincipal(identity, new[] { "user" });
    var request = new Mock<httprequestbase>();
    mock.SetupGet(p => p.HttpContext.Request).Returns(request.Object);
    var response = new Mock<httpresponsebase>();
    mock.SetupGet(p => p.HttpContext.Response).Returns(response.Object);
    var session = new Mock<httpsessionstatebase>();
    mock.SetupGet(p => p.HttpContext.Session).Returns(session.Object);
    mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
    mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
    mock.SetupGet(s => s.HttpContext.User).Returns(principal);
    controller.ControllerContext = mock.Object;
    return controller;
}

And the controller overridden on test methods:

var authManager = new Mock<IAuthenticationManager>();
var uc = new ControllerToTestController();
uc.AuthenticationManager = authManager.Object;
var controller = CreateControllerAs< ControllerToTestController >(uc, "TestUser", Constants.PrimaryUserId);