简体   繁体   中英

How to efficiently mock Data Contexts for Unit Testing with EF6, MVC and MOQ

I'm trying to add unit testing to a new MVC application and I'm following the guide at: http://msdn.microsoft.com/en-us/data/dn314429

The guide details almost exactly what I'd like to accomplish - testing that the results returned in an Index() action of a controller are sorted correctly, but the example is too contrived for my needs. In my case, my ViewModel is comprised of numerous domain entities and I'm finding it overly tedious to mock.

The query in my controller action is as follows:

var roles = _db.Roles
            .OrderBy(r => r.Area.Application.Name)
            .ThenBy(r => r.Area.Name)
            .ThenBy(r => r.Name)
            .Select(role =>
                new RoleViewModel
                {
                    RoleName = role.Name,
                    Description = role.Description,
                    ApplicationArea = role.Area.Application.Name + "/" + role.Area.Name,
                    GroupsUsingThisRole = role.RoleGroupMappings
                        .Select(rgm => rgm.Group.Name).ToList()
                }).ToList();

From this you can see that I'm joining across numerous DBSets. I've written a lot of code to try and mock the data needed for this query, mostly populating child collections for navigation properties but it's taking a lot of time and alarm bells are starting to ring that maybe I'm doing this all wrong.

Is there a more efficient way to mock complex data sets that encompass numerous tables? It just feels wrong that I'm spending hours trying to mock data to test code that took seconds to write.

Well, you could insert some layer between your controllers and database, some repository. Then you could mock repository to return mock data. Something like this:

public interface IRoleRepository
{
   IQueriable<Role> QueryRoles();
}

And then in your test, you create just array of mock Roles and return in in mock repository:

var roles = new Role[]
{
   new Role
   {
      ...
   },
   ...
};

var mockRepository = new Mock<IRoleRepository>();
mockRepository.Setup(r => r.QueryRoles()).Returns(roles.AsQueryable());

Mocking DB sets is always hard and there is not much you can do to simplify this task. Question is, do you need to do that in your controller? Answer is - no.

Controller is an aggregation point and it should be tested as such. Unit testing controller to determine whether some database query works rings the bells on separation of concerns and single responsibility principles violation. First, I'd extract data access layer and hide it behind abstraction:

var roles = roleService
    .GetOrderedRoles()
    .Select(role =>
            new RoleViewModel
            {
                RoleName = role.Name,
                Description = role.Description,
                ApplicationArea = role.Area.Application.Name
                    + "/" + role.Area.Name,
                GroupsUsingThisRole = role.RoleGroupMappings
                    .Select(rgm => rgm.Group.Name).ToList()
            })
    .ToList();

This delegates query problem for a moment. Let's take a look at further improvement - ViewModel building. This responsibility can be again extracted away and hidden behind abstract factory pattern :

var roles = roleService
    .GetOrderedRoles()
    .Select(role => roleViewModelFactory.CreateFromRole(role))
    .ToList();

Now, mocking both roleService and roleViewModelFactory should be trivial. As a result, unit tests for controller will be small and simple (and that's good thing). Same with unit tests for roleViewModelFactory -- simple and isolated.

Finally, we need to tackle the original problem -- unit testing database layer. But do we ? Unit testing the database? We could check whether service calls appropriate methods on db context but that's again a lot of setup work. What is worse, if we isolate (mock) db layer we essentially isolate the single responsibility our service has -- to talk to the database .

That is why it is better to test roleService on real database. The article in question mentions this point in a way:

In-memory test doubles can be a good way to provide unit test level coverage of bits of your application that use EF. However, when doing this you are using LINQ to Objects to execute queries against in-memory data. This can result in different behavior than using EF's LINQ provider (LINQ to Entities) to translate queries into SQL that is run against your database. (…)

For this reason, it is recommended to always include some level of end-to-end testing (in addition to your unit tests) to ensure your application works correctly against a database.

To wrap up, I suggest following approach:

  • refactor controller to abstract any additional responsibilities it now incorportates
  • easily unit test controller and any new classes while mocking dependencies
  • integration test db services on actual databases

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM