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
- 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<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 Acme.Web.Controllers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject.MockingKernel;
using Ninject.MockingKernel.FakeItEasy;
public class ProductControllerTests
private readonly FakeItEasyMockingKernel _kernel;
public ProductControllerTests()
_kernel = new FakeItEasyMockingKernel();
_kernel.Bind<IProductService>().ToMock();
[TestCategory("Product Controller Tests")]
public void Index_OrderedByProductName()
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);
var results = (ViewResult) controller.Index();
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.