简体   繁体   中英

Unit Testing issue with Moq and Include (EF6)

I have done fair bit research and tried all sorts of different ways of getting the Test to pass but now i need some help.

I am trying to test the following repository method:

public class PortalsRepository : BaseRepository<PortalDomainRole>, IPortalsRepository
{
    public PortalsRepository(IAuthDbContext context) : base(context)
    {

    }

    public IEnumerable<PortalRole> GetRoles(string domainName)
    {
        return Set.Include(x => x.PortalDomain)
            .Include(x => x.PortalRole)
            .Where(x => x.PortalDomain.Name.ToLower() == domainName)
            .Select(x => x.PortalRole)
            .ToList();
    }
}

The Context looks like:

public interface IAuthDbContext : IDbContextBase
{

}

public interface IDbContextBase
{
    IDbSet<T> Set<T>() where T : class;
    IEnumerable<DbValidationError> GetEntityValidationErrors();
    int SaveChanges();
    Task<int> SaveChangesAsync();
    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}

My Unit Test Set-up Looks like:

    protected override void GivenThat()
    {
        var mockRolesSet = GetMockDbSet(PortalRoles().AsQueryable());
        mockRolesSet.Setup(x => x.Include("PortalRole")).Returns(mockRolesSet.Object);

        var mockDomainsSet = GetMockDbSet(PortalDomains().AsQueryable());
        mockDomainsSet.Setup(x => x.Include("PortalDomain")).Returns(mockDomainsSet.Object);

        var mockPortalDomanRolesSet = GetMockDbSet(PortalDomainRoles().AsQueryable());

        mockPortalDomanRolesSet.Setup(x => x.Include("PortalRole")).Returns(mockPortalDomanRolesSet.Object);
        mockPortalDomanRolesSet.Setup(x => x.Include("PortalDomain")).Returns(mockPortalDomanRolesSet.Object);

        var customDbContextMock = new Mock<IAuthDbContext>();
        customDbContextMock.Setup(x => x.Set<PortalRole>()).Returns(mockRolesSet.Object);
        customDbContextMock.Setup(x => x.Set<PortalDomain>()).Returns(mockDomainsSet.Object);
        customDbContextMock.Setup(x => x.Set<PortalDomainRole>()).Returns(mockPortalDomanRolesSet.Object);

        ClassUnderTest = new PortalsRepository(customDbContextMock.Object);
    }

My Unit Test Supporting Methods:

public List<PortalDomainRole> PortalDomainRoles()
        {
            var data = new List<PortalDomainRole>
            {
                new PortalDomainRole { PortalRoleId = 2, PortalDomainId = 1},
                new PortalDomainRole { PortalRoleId = 1, PortalDomainId = 2},
                new PortalDomainRole { PortalRoleId = 2, PortalDomainId = 2}
            };
            return data;
        }

    public List<PortalDomain> PortalDomains()
    {
        var data = new List<PortalDomain>
        {
            new PortalDomain { Name = "google.co.uk", PortalDomainId = 1 },
            new PortalDomain { Name = "bbc.com", PortalDomainId = 2 }
        };
        return data;
    }

    public List<PortalRole> PortalRoles()
    {
        var data = new List<PortalRole>
        {
            new PortalRole {Name = "New Products", PortalRoleId = 1},
            new PortalRole {Name = "Classic Products", PortalRoleId = 2}
        };
        return data;
    }

When the unit test executes the method in question, i get:

System.NullReferenceException : Object reference not set to an instance of an object.

Most likely it does not know how to handle the nested include statements - i have followed many online questions and tutorials and now i am stuck.

My answer is probably a bit controversial, but in my experience, the best way to test your repository layer (or whatever you call the code that does the actual data access), is by having it actually call the database during testing.

When you are writing your unit test, you are assuming that Entity Framework works in a specific way. But sometimes it works in different ways than you expect, and thus a test may pass, even though the code doesn't work correctly.

Take this example, that illustrates the problem (the last EF version I worked with was version 4, but I assume that my statement is still true for EF6)

public class Foo {
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public bool Active {
        get { return StartDate < DateTime.Now && EndDate > DateTime.Now }
    }
}

public class FooRepository {
    public IEnumerable<Foo> ActiveFoos { get { return DataContext.Foos.Where(x => x.Active) } }
}

Testing this FooRepository against a mocked data access will pass, but executing against a real database will throw an exception. That is because EF will try to create an SQL clause for the Where(x => x.Active) , but because Active is not a field in the database, EF will have no idea how to translate the query to SQL.

So your unit test provides a false positive. Executing the tests against the database will make it fail, as it should.

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