ASP.NET MVC, TDD and AutoMapper

This post is in response to a question on a recent article I wrote about mapping domain entities to presentation models with AutoMapper, an object-object mapper for .NET. Today I will give a brief example of how we can tie it all together in an ASP.NET MVC application using dependency injection and application services.

First, let’s start with the controller and the application service it talks to:

public class TasksController : Controller{    readonly ITaskService tasks;    public TasksController(ITaskService tasks)    {        if (tasks == null)            throw new ArgumentNullException("tasks");        this.tasks = tasks;    }    public ActionResult Index()    {        IEnumerable<TaskView> results = this.tasks.GetCurrentTasks();        return View(results);    }        ...}
public interface ITaskService{    IEnumerable<TaskView> GetCurrentTasks();    TaskView AddTask(TaskForm task);    TaskView SaveTask(TaskForm task);    void DeleteTask(int id);}

Note the service’s inputs and outputs are defined in terms of view models (TaskView) and edit models (TaskForm). Performing this mapping in the application services layer keeps our controllers nice and simple. Remember we want to keep them as thin as possible.

Inside the tasks service

public class TaskService : ITaskService{    readonly ITaskRepository taskRepository;    readonly IMappingEngine mapper;    public TaskService(ITaskRepository taskRepository, IMappingEngine mapper)    {        if (taskRepository == null)            throw new ArgumentNullException("taskRepository");        if (mapper == null)            throw new ArgumentNullException("mapper");        this.taskRepository = taskRepository;        this.mapper = mapper;    }    public IEnumerable<TaskView> GetCurrentTasks()    {        IEnumerable<Task> tasks = this.taskRepository.GetAll();        return tasks.Select(t => this.mapper.Map<Task, TaskView>(t));    }        ...}

The tasks service has two dependencies: the tasks repository* and AutoMapper itself. Injecting a repository into a service is simple enough, but for AutoMapper we have to inject an IMappingEngine instance to break the static dependency on AutoMapper.Mapper as discussed in this post.

* Note this is a very simple example — in a bigger app we might use CQS instead of querying the repository directly.

Testing the service

We are using Moq to isolate the tasks service from its repository and AutoMapper dependencies, which always return a known result from the Object Mother. Here are our test cases for all the different things that should occur when retrieving the current tasks:

[TestFixture]public class When_getting_all_current_tasks{    Mock<ITaskRepository> repository;    Mock<IMappingEngine> mapper;    ITaskService service;    IEnumerable<Task> tasks;    [SetUp]    public void SetUp()    {        repository = new Mock<ITaskRepository>();        mapper = new Mock<IMappingEngine>();        service = new TaskService(repository.Object, mapper.Object);        tasks = ObjectMother.GetListOfTasks();        repository.Setup(r => r.GetAll()).Returns(tasks);        mapper.Setup(m => m.Map<Task, TaskView>(It.IsAny<Task>()))            .Returns(ObjectMother.GetTaskView());    }    [Test]    public void Should_get_all_the_tasks_from_the_repository()    {        service.GetCurrentTasks();        repository.Verify(r => r.GetAll());    }    [Test]    public void Should_map_tasks_to_view_models()    {        service.GetCurrentTasks();        foreach (Task task in tasks)            mapper.Verify(m => m.Map<Task, TaskView>(task));    }    [Test]    public void Should_return_mapped_tasks()    {        IEnumerable<TaskView> results = service.GetCurrentTasks();        results.Should().Not.Be.Empty();    }}

Enter AutoMapper

As you can see, we have both the controller and service under test without needing to involve AutoMapper yet. Remember it is being tested separately as discussed in my previous post.

To wire up AutoMapper so it gets injected into the TaskService, all we have to do is register IMappingEngine in the IoC container:

container.RegisterInstance<IMappingEngine>(Mapper.Engine);

Putting the mapping step in the application service and then mocking out AutoMapper like this allows us to easily test everything in isolation, without having to set up the mapper first.

I hope this answers your question Paul!