简体   繁体   English

Moq和Include(EF6)的单元测试问题

[英]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. System.NullReferenceException:对象引用未设置为对象的实例。

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. 最有可能的是,它不知道如何处理嵌套的include语句-我已经关注了许多在线问题和教程,但现在我陷入了困境。

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. 在编写单元测试时,您假设Entity Framework以特定的方式工作。 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) 以这个例子为例,它说明了问题(我使用的最后一个EF版本是版本4,但是我认为我的声明对于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. 针对模拟数据访问测试此FooRepository将通过,但是针对实际数据库执行测试将引发异常。 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. 那是因为EF将尝试为Where(x => x.Active)创建一个SQL子句,但是由于Active不是数据库中的字段,因此EF将不知道如何将查询转换为SQL。

So your unit test provides a false positive. 因此,您的单元测试提供了误报。 Executing the tests against the database will make it fail, as it should. 对数据库执行测试将使其失败,这应该是正确的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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