Quick Guide to setup a MVC project for Unit Testing

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

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.



comments powered by Disqus

Please consider using Brave and adding me to your payment ledger. Then you won't have to see ads!

Support me and download Brave!

Use Brave