Reposted on my new blog. Come visit me there!
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.
- Read the MSDN Unit Testing MVC article.
- consider splitting controllers into a different project as suggested
- Don’t put your data access code inside the Controller methods, use a data layer/object. Create something you can inject and mock.
- Create an MVC project.
- Use Ninject to setup Dependency Injection
- Get the Ninject WebAPI NuGet package, if using WebApi so DI will work with WebApi.
- Create a solution folder in Visual Studio with this structure.
- Tests
- Integration
- Performance
- UI
- Unit
- Tests
- Decision: unit testing driven or outside in BDD (my benefits of BDD article)/SpecFlow testing, possibly with Selenium. (Probably both).
- Create a testing project (named {WebsiteName}.Controllers.Tests) under Unit.
- Use a mocking tool. I like FakeItEasy, but Moq is very good as well. Get it from NuGet.
- Setup the tests for DI. This is one approach I’ve used that seems pretty straight forward.
-
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; }}
-
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{ 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.Models;
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.
