Software Programming

Kunuk Nykjaer

How to write unit tests – mvc, test framework and mock example

leave a comment »


The code examples are also avaiable at

https://github.com/kunukn/UnitTestExample-CSharp


Synopsis
You have been given a task to write tests for a methods in a class.

You are working with a MVC web app framework, a test framework and a mocking framework.
For this example, I will use Asp.net MVC, Visual Studio Unit Testing Framework and Rhino Mocks.

Your task is to write unit tests for the actions in controller class.
Your have to unit test that the action returns the correct outcome for the possible inputs.

This is the controller class you must test.


DataController

using System.Collections.Generic;
using System.Net;
using System.Web.Mvc;
using MyApp.Services;

namespace MyApp.Controllers
{
    public class DataController : Controller
    {
        public ActionResult GetData(string subject)
        {
            if (subject == "foo")
            {
                new ReportService().ReportAbuseUsage(subject);
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
            }

            IList<string> data = new DataService().GetData(subject);
                        
            return Json(data, JsonRequestBehavior.AllowGet);
        }
    }
}


Analysis

The GetData action can have two possible outcome.

  • When the subject is foo it will invoke a ReportAbuseUsage method and return a http forbidden result
  • When the subject is anything else it fetch data from a service and then return a json result

The unit testing should test those two cases, input with foo and with something else than foo.
Then inspect the result and see if the outcome was as expected.

The action is dependent on two services. DataService and ReportService.
To unit-test the action you are not supposed to test those two services.
If you do then it is no longer a unit test of the action method but an integration test.

What you have is a method, which is not unit testable. You must refactor the action and mock the depended services.
The class has high coupling with the services.
Loose coupling is often preferred for various reasons. One is for better test-ability of your code.

There are various methods for refactoring to make it more testable.
I will show following techniques: constructor injection and property injection.



Refactoring

You start with adding interfaces to your services.


IDataService

using System.Collections.Generic;
namespace MyApp.Interfaces
{
    public interface IDataService
    {
        IList<string> GetData(string subject);
    }
}

IReportService

namespace MyApp.Interfaces
{
    public interface IReportService
    {
        void ReportAbuseUsage(string subject);
    }
}

DataService

using System.Collections.Generic;
using MyApp.Interfaces;

namespace MyApp.Services
{
    public class DataService : IDataService
    {
        public IList<string> GetData(string subject)
        {
            // returns data by subject, simulate get data 
            return new List<string> { "apple", "orange", "banana" };
        }
    }
}

ReportService

using MyApp.Interfaces;

namespace MyApp.Services
{
    public class ReportService : IReportService
    {
        public void ReportAbuseUsage(string subject)
        {
            // simulate report something to a repository
        }
    }
}


Property injection example

The controller class has been refactored to use property injection

using System.Collections.Generic;
using System.Net;
using System.Web.Mvc;
using MyApp.Interfaces;
using MyApp.Services;

namespace MyApp.Controllers
{
    public class DataController : Controller
    {
        private IDataService _dataService;
        public IDataService DataService
        {
            get { return _dataService ?? new DataService(); }
            set { _dataService = _dataService ?? value; }
        }

        private IReportService _reportService;
        public IReportService ReportService
        {
            get { return _reportService ?? new ReportService(); }
            set { _reportService = _reportService ?? value; }
        }
        
        public ActionResult GetData(string subject)
        {            
            if (subject == "foo")
            {
                ReportService.ReportAbuseUsage(subject);
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
            }

            IList<string> data = DataService.GetData(subject);

            return Json(data, JsonRequestBehavior.AllowGet);
        }
    }
}

The getters only create a new instance of the service if it has not been set first.
The setters only allow to be set once if the value is not null.
The injection is done by setting the value before using the getters of the properties.


Unit testable state

Now the controller has dependencies on interfaces and not the implementation.
From here you are able to write unit test where you mock the services.

A Unit Test project has been created with reference to the project and system.web.mvc
and the nuget package has been installed: Install-Package RhinoMocks


Unit test implementation

I will use the Arrange Act Assert (AAA) Pattern.

The service methods has been mocked
(the reason I call them mocks and not stub is because I assert against them. Whether they have been called and with what arguments).

For the first unit test, I test that the action returns a json result for every input but foo.
I also verify that the DataService method was invoked and verify the ReportService method was not invoked.

For the second unit test, I test that the action returns http denied for foo input.
I also verify that the DataService method was not invoked and verify the ReportService method was invoked.

This is the unit test implementation using Visual Studio Unit Testing Framework and Rhino Mocks.

Test naming standard used is MethodName_StateUnderTest_ExpectedBehavior

using System.Collections.Generic;
using System.Net;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyApp.Controllers;
using MyApp.Interfaces;
using Rhino.Mocks;

namespace UnitTestMyApp
{
    [TestClass]
    public class DataControllerTest
    {
        // Injected services
        private IDataService dataService;
        private IReportService reportService;

        [TestInitialize]
        public void Setup()
        {
            dataService = MockRepository.GenerateStub<IDataService>();
            reportService = MockRepository.GenerateStub<IReportService>();

            dataService
                .Stub(s => s.GetData(Arg<string>.Is.Anything))
                .Return(new List<string>());
        }

        [TestMethod]
        public void 
        GetData_WhenCalledWithAnythingButFoo_InvokeGetDataAndReturnsJsonResult()
        {
            // Arrange            
            var controller = new DataController
            {
                DataService = dataService,
                ReportService = reportService
            };

            // act
            ActionResult news = controller.GetData(subject: "news");
            ActionResult fooish = controller.GetData(subject: "fooish");

            // Assert            
            Assert.IsNotNull(news as JsonResult);
            Assert.IsNotNull(fooish as JsonResult);
            
            dataService
                .AssertWasCalled(s => s.GetData(Arg<string>.Is.Anything));
            
            reportService
                .AssertWasNotCalled(s => s.ReportAbuseUsage(Arg<string>.Is.Anything));
        }


        [TestMethod]
        public void 
        GetData_WhenCalledWithFoo_InvokeReportAbuseUsageAndReturnsHttpDenied()
        {
            // Arrange            
            var controller = new DataController
            {
                DataService = dataService,
                ReportService = reportService
            };

            var forbidden = new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            // act
            ActionResult foo = controller.GetData(subject: "foo");
            var fooHttpStatusCodeResult = foo as HttpStatusCodeResult;

            // Assert            
            Assert.IsNotNull(fooHttpStatusCodeResult);
            Assert.AreEqual(fooHttpStatusCodeResult.StatusCode, forbidden.StatusCode);
            
            dataService
                .AssertWasNotCalled(s => s.GetData(Arg<string>.Is.Anything));
            
            reportService
                .AssertWasCalled(s => s.ReportAbuseUsage(Arg<string>.Is.Anything));
        }
    }
}


Constructor injection example

The unit test for the constructor injection example is very similar to the property injection.
The DataController class looks similar to the injection example.

The refactoring for the DataController class looks like this.

DataController

using System.Collections.Generic;
using System.Net;
using System.Web.Mvc;
using MyApp.Interfaces;

namespace MyApp.Controllers
{
    public class DataController : Controller
    {
        private readonly IDataService dataService;
        private readonly IReportService reportService;

        public DataConstructorController()
            : this(new DataService(), new ReportService())
        {
        }

        public DataController(IDataService dataService, IReportService reportService)
        {
            this.dataService = dataService;
            this.reportService = reportService;
        }
        
        public ActionResult GetData(string subject)
        {            
            if (subject == "foo")
            {
                reportService.ReportAbuseUsage(subject);
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
            }

            IList<string> data = dataService.GetData(subject);
            
            return Json(data, JsonRequestBehavior.AllowGet);
        }
    }
}


The refactoring for the DataControllerTest class looks like this.

DataControllerTest

using System.Collections.Generic;
using System.Net;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyApp.Controllers;
using MyApp.Interfaces;
using Rhino.Mocks;

namespace UnitTestMyApp
{
    [TestClass]
    public class DataControllerTest
    {
        // Injected services
        private IDataService dataService;
        private IReportService reportService;

        [TestInitialize]
        public void Setup()
        {
            dataService = MockRepository.GenerateStub<IDataService>();
            reportService = MockRepository.GenerateStub<IReportService>();

            dataService
                .Stub(s => s.GetData(Arg<string>.Is.Anything))
                .Return(new List<string>());
        }

        [TestMethod]
        public void 
        GetData_WhenCalledWithAnythingButFoo_InvokeGetDataAndReturnsJsonResult()
        {
            // Arrange            
            var controller = new DataController(dataService, reportService);
            
            // act
            ActionResult news = controller.GetData(subject: "news");
            ActionResult fooish = controller.GetData(subject: "fooish");

            // Assert            
            Assert.IsNotNull(news as JsonResult);
            Assert.IsNotNull(fooish as JsonResult);

            dataService
                .AssertWasCalled(s => s.GetData(Arg<string>.Is.Anything));

            reportService
                .AssertWasNotCalled(s => s.ReportAbuseUsage(Arg<string>.Is.Anything));
        }


        [TestMethod]
        public void 
        GetData_WhenCalledWithFoo_InvokeReportAbuseUsageAndReturnsHttpDenied()
        {
            // Arrange            
            var controller = new DataController(dataService, reportService);            
            var forbidden = new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            // act
            ActionResult foo = controller.GetData(subject: "foo");
            var fooHttpStatusCodeResult = foo as HttpStatusCodeResult;

            // Assert            
            Assert.IsNotNull(fooHttpStatusCodeResult);
            Assert.AreEqual(fooHttpStatusCodeResult.StatusCode, forbidden.StatusCode);

            dataService
                .AssertWasNotCalled(s => s.GetData(Arg<string>.Is.Anything));

            reportService
                .AssertWasCalled(s => s.ReportAbuseUsage(Arg<string>.Is.Anything));
        }
    }
}


Advertisements

Written by kunuk Nykjaer

September 7, 2014 at 7:31 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: