Quick Guide to setup a MVC project for Unit Testing

January 16, 2018    Development UnitTesting Asp.Net MVC DevOps BDD AutomatedTesting .Net

Quick Guide to setup a MVC project for Unit Testing

Originally posted on GeeksWithBlogs.net on September 25, 2014. This was written before Asp .Net Core.

A barrier to getting into writing executable tests (Unit Tests, integration tests, automated UI tests, etc) with some people I work with, is not knowing how to get started. I believe the desire  and the recognition of the value and importance of this testing is growing, but I want to help people get over that hump.

  1. Read the MSDN Unit Testing MVC article .
    1. consider splitting controllers into a different project as suggested
    2. Don’t put your data access code inside the Controller methods, use a data layer/object. Create something you can inject and mock.
  2. Create an MVC project.
  3. Use Ninject to setup Dependency Injection
  4. Get the Ninject WebAPI NuGet package , if using WebApi so DI will work with WebApi.
  5. Create a solution folder in Visual Studio with this structure.
    1. Tests
      1. Integration
      2. Performance
      3. UI
      4. Unit
  6. Decision: unit testing driven or outside in BDD ( my benefits of BDD article )/ SpecFlow testing, possibly with Selenium. (Probably both).
  7. Create a testing project (named {WebsiteName}.Controllers.Tests) under Unit.
  8. Use a mocking tool. I like FakeItEasy , but Moq is very good as well. Get it from NuGet.
  9. Setup the tests for DI. This is one approach I’ve used that seems pretty straight forward.
    1. Create a file named SetupDiFOrTests in your test project. This will setup dependencies and expose the fake services (data access) as a static property.
        public class SetupDIForTest
        {
            private static readonly Bootstrapper bootstrapper = new Bootstrapper();
            public static void Start()
            {
                bootstrapper.Initialize(CreateKernel);
            }

            /// <summary>
            /// Stops the application.
            /// </summary>
            public static void Stop()
            {
                bootstrapper.ShutDown();
            }

            private static IKernel CreateKernel()
            {
                var kernel = new StandardKernel();
                try
                {
                    RegisterServices(kernel);
                    return kernel;
                }
                catch
                {
                    kernel.Dispose();
                    throw;
                }
            }

            /// <summary>
            /// Load your modules or register your services here!
            /// </summary>
            /// <param name="kernel">The kernel.</param>
            private static void RegisterServices(IKernel kernel)
            {
                FakeLogger = A.Fake<ILogger>();
                kernel.Bind<ILogger>().ToMethod((x) => FakeLogger);
                var fakeContext = A.Fake<ITimeSheetDbContext>();
                kernel.Bind<ITimeSheetDbContext>().ToMethod((x) => fakeContext);
                FakeTimeSheetService = A.Fake<ITimeSheetService>();
                kernel.Bind<ITimeSheetService>().ToMethod((x) => FakeTimeSheetService);
            }
            public static ILogger FakeLogger { get; private set; }
            public static ITimeSheetService FakeTimeSheetService { get; private set; }
        }
1. Call the setup start method from an assembly init and add in tests. See my example test method of a Timesheet application.
        [TestClass]
        public class When_Creating_TimeSheets
        {
            [AssemblyInitialize]
            public static void AssemblyInit(TestContext testContext)
            {
                SetupDIForTest.Start();
            }
            [TestMethod]
            public void It_Should_Populate_The_Users_Select()
            {
                // Arrange
                // use FakeItEasy to set what the method will return
                A.CallTo(() => SetupDIForTest.FakeTimeSheetService.GetTimeSheetUsersAsync())
                    .Returns(Task.FromResult(new List<TimeSheetUser>{
                        new TimeSheetUser{
                            FirstName = "Kevin"
                        },
                        new TimeSheetUser{
                            FirstName = "Terry"
                        }
                    }.AsEnumerable()));

                // Act
                var controller = new TimeSheetEntriesController(SetupDIForTest.FakeTimeSheetService, SetupDIForTest.FakeLogger);
                controller.Create();

                // Assert
                var selectList = controller.ViewBag.TimeSheetUserID as SelectList;
                Assert.IsNotNull(selectList);
                Assert.AreEqual("Kevin", selectList.First().Text, "First user should be Kevin");
                Assert.AreEqual("Terry", selectList.Skip(1).First().Text);
                Assert.AreEqual(2, selectList.Count(), "It should fill the list with all users from GetTimeSheetUsers");
            }
        }

That should be enough to get your writing some unit tests against your controllers. Happy testing!

 EDIT from December 9th, 2015. There is a  Ninject.MockingKernel.FakeItEasy  Nuget library that simplifies things.

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Acme.Services;
using Acme.Web.Controllers;
using FakeItEasy;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject;
using Ninject.MockingKernel;
using Ninject.MockingKernel.FakeItEasy;

namespace Acme.Web.Tests
{
    [TestClass]
    public class ProductControllerTests
    {
        private readonly FakeItEasyMockingKernel _kernel;
        public ProductControllerTests()
        {
            _kernel = new FakeItEasyMockingKernel();
            _kernel.Bind<IProductService>().ToMock();
        }

        [TestInitialize]
        public void TestInit()
        {
            _kernel.Reset();
        }

        [TestMethod]
        [TestCategory("Product Controller Tests")]
        public void Index_OrderedByProductName()
        {
            // Arrange
             var testData = new List<Product>
            {
                new Product {ProductId = 1, Name = "Z Product 1", Description = "this is a description", Active = true},
                new Product {ProductId = 4, Name = "A Product 4", Description = "this is a description", Active = true},
            };

            var productServiceMock = _kernel.Get(typeof (IProductService)) as IProductService;
            A.CallTo(() => productServiceMock.GetActiveProducts()).Returns(testData);
            var controller = new ProductsController(productServiceMock);

            // Act
            var results = (ViewResult) controller.Index();

            // Assert
            var model = (List<Product>) results.ViewData.Model;
            Assert.AreEqual(testData[0].Name, model.Last().Name, "Should be sorted by first name");
        }
    }
}

P.S.

You may have to do more advanced mocking of the user or other HTTP objects in MVC to get good coverage.

There is a very good course on Pluralsight about Executable Specifications if you’re interested in SpecFlow, when thinking about testing.