2017-07-06 47 views
0

我已經做了一些公正的研究,嘗試了各種不同的方式讓測試通過,但現在我需要一些幫助。Moq和Include(EF6)的單元測試問題

我想測試下方法:

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(); 
    } 
} 

語境樣子:

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); 
} 

我的單元測試建立看起來像:

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); 
    } 

我的單元測試支持方法:

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; 
    } 

在單元測試中的問題執行方法,我得到:

System.NullReferenceException:對象不設置到對象的實例。

很可能它不知道如何處理嵌套的include語句 - 我遵循許多在線問題和教程,現在我被卡住了。

回答

1

我的答案可能有點有爭議,但根據我的經驗,測試存儲庫層(或者稱爲執行實際數據訪問的代碼的任何方法)的最佳方法是在測試期間實際調用數據庫。

在編寫單元測試時,假定實體框架以特定方式工作。但是有時它會以不同的方式工作,因此即使代碼無法正常工作,測試也可能通過。

就拿這個例子中,說明了這個問題(過去的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) } } 
} 

測試這個FooRepository針對嘲笑數據訪問將會通過,但是對真實數據庫的執行會引發異常。這是因爲EF會嘗試爲Where(x => x.Active)創建SQL子句,但由於Active不是數據庫中的字段,因此EF不知道如何將查詢轉換爲SQL。

所以你的單元測試提供了一個誤報。對數據庫執行測試會使其失敗,因爲它應該如此。