Sunday, August 30, 2015

Unit Testing (part 1) - Without using a mocking framework


In this series of posts we are not using Moq or Microsoft Fakes or any testing framework, instead we are using the test double approach and following the rule “do not test code you do not own”.

Unit Testing (part 1) - Without using a mocking framework

Unit Testing (part 2) - Faking the HttpContext and HttpContextBase

Unit Testing (part 3) - Running Unit Tests & Code Coverage

Unit Testing (part 4) - Faking Entity Framework code first DbContext & DbSet

 

When I asked myself what I needed to test the answer is “my business logic and any classes I wrote that are called by the business logic”. I don’t want to test making actual calls to the database or 3rd party web services etc.

I also took the view that what I wanted to test was not every class in isolation I don’t have the time at my employer to do it the purist way of mocking the in’s/outs around each class. Instead I came up with a compromise and call the controller and let the code run (as it would when running on the web site). I just fake out the 3rd party calls so my focus of testing is on my code.

Let’s see how this works:

  • The MVC 5 web site controllers in my site don’t really contain any code they all reference a service function which populates the MVC model. Which keep our controllers lean.

     public ProductController(IProductService productService) : base()

     {

         this.productService = productService;

     }

     public ActionResult Index()

     {

         var model = productService.PopulateModel(this.HttpContext, ...);

         return View(model.ViewTemplateToRender, model);

     }

  • We make use of Interfaces (which are essential for effective unit testing).

  • Also note that the PopulateModel(this.HttpContext, …) is expecting a HttpContextBase more on that later.

  • We use dependency injection to inject the actual concrete class at run time which matches the interface. We do this so in our unit tests we can over-ride any concrete class and replace it with a test double if we need to. The times when we would do this are when we call code that we do not own (e.g. 3rd party web services, .Net framework code etc.).

This is where the rule “do not test code you do not own” comes into its own.

By not following this rule you open yourself to a whole world of pain. For example why test a call to an external web service. All you need to do is test that your code is calling the correct method, passing the correct data, and getting back the correct response. But without making the actual call we’d fake it instead. This results in tests that run much faster. If you made the call to the web service that’s integration testing.

Why would you test code that sets a cookie, you’ve got to fake the request / response to do it, we know it works Microsoft have tested it, and it’s been in .Net for many years. It’s actually much easier to test if you think differently and use a CookieContainer class with an interface ICookieContainer then I let the site use request.Cookies etc. And have a test double FakeCookieContainer class that doesn’t use the request / response at all.

Let’s say I had an external web service which one of my service functions calls which given the clients IP number returns the country that the IP number is located in. I do not want to make that actual call from a unit test the speed will be terrible and that’s a job for an integration test using Selenium or some other UI testing tool. But I do want to call it from my website. So I create an interface:

public interface IIpFunctions

{

    Country GetCountry(HttpRequestBase request);

}

My concrete class used by the site is going to call the GetCountry method and do the actual calling of the external web service, which would look like this (some code has been removed for simpler reading):

public class IpFunctions : IIpFunctions

{

    public Country GetCountry(HttpRequestBase request)

    {

        String ip = GetClientIP(request);

        using (HttpClient client = new HttpClient())

        {

            HttpResponseMessage response = client.GetAsync(countryRequest).Result;

            response.EnsureSuccessStatusCode();

            countryResult = response.Content.ReadAsStringAsync().Result;

        }

        return countryRepository.GetByIso(countryResult);

    }

}

For unit testing I just need to create a fake concrete class, both inheriting the same interface. So it passes in the same request object and gets a Country object back. But the code internally is very different.

public class FakeIpFunctions : IIpFunctions

{

    public Country GetCountry(HttpRequestBase request)

    {

        var clientIP = request.UserHostAddress;

        if (clientIP == "1.1.1.1")

        {

            // Fake UK IP address

            return countryRepository.GetByIso("GBR");

        }

        else if (clientIP == "1.1.1.2")

        {

             // Fake USA IP address

            return countryRepository.GetByIso("USA");

        }

        return null;

    }

}

Now when I’m writing a unit test I can just pass in the IP number 1.1.1.2 as part of the request object and I know the country will be USA.

My unit test would look like this:

[TestMethod]

public void ProductService_PopulateModel_IPDetectUSA()

{

    var clientIP = "1.1.1.2";

    var productService = unityContainer.Resolve<IProductService>();

    HttpContext.Current = new FakeHttpContext().CreateFakeHttpContext();

    var httpContextWrapper = new FakeHttpContextWrapper(httpContext: HttpContext.Current, clientIP: clientIP);

    var model = productService.PopulateModel(httpContextWrapper);

}

My dependency injection is setup so:

  • It uses the same IProductService concrete class for unit tests and for the MVC web site. But within the ProductService class’s constructor it uses the IIpFunctions interface.

public ProductService(IIpFunctions ipFunctions, …)

  • For the IIpFunctions interface the dependency injection is very different, the site calls IpFunctions and the unit tests call FakeIpFunctions.

So I’ve followed the rule “don’t test what you don’t own”. I can do the same with cookies, cache managers like Redis Cache etc. I don’t want to test third party code, so I use interfaces and create a fake test double class for unit testing with.

Note: in the above unit test example I’m starting my unit test at my ProductService not the MVC controller, there is no reason this could not be the controller but because my controllers don’t really contain any code they simply call the service layer I decided that my unit tests for the most part should start at the service layer. I’d then need just a handful of unit tests to test the controller (which would really be to just check the correct view is being returned or any viewbag values are set correctly etc.).

 

Is this the purist way of doing unit testing – No.

Is this truly a unit test where the definition of a unit = a class (e.g. you test each class in isolation and any dependencies for it are mocked / faked) – No.

Isn’t this integration testing – That’s a fine line and debatable but no because it’s very fast, all in memory and we don’t call 3rd party code/services like an integration test would.

Why didn’t we do the above?

  • Because companies don’t like to invest the time in developing unit tests especially the purist way. So it’s better to have something rather than nothing.

  • Because it would take a lot longer to write and more tests are needed to get decent code coverage.

  • It gives the same benefit; the code is tested very quickly while it’s still in Visual Studio. And we’d always follow up with integration tests once the code has been deployed to a test environment.

No comments:

Post a Comment