2016-12-05 64 views
1

我使用EF6。生成的代码是一样的东西:Moq - 用DbEntityEntry更新

public partial class MyDataContext : DbContext 
{ 
    public MyDataContext() : base("name=mydata") 
    { 
    } 

    public virtual DbSet<Book> Books { get; set; } 
} 

然后,我有一个通用的存储库,如:

public class GenericRepository<TObject> where TObject : class 
{ 
    protected readonly MyDataContext Context; 

    protected GenericRepository(MyDataContext context) 
    { 
     Context = context; 
    } 

    public virtual TObject Update(TObject data, int id) 
    { 
     if (data == null) 
      return null; 

     TObject obj = Context.Set<TObject>().Find(id); 
     if (obj != null) 
     { 
      Context.Entry(obj).CurrentValues.SetValues(data); 
      Context.SaveChanges(); 
     } 

     return obj; 
    } 
} 

然后,我有一个使用GenericRepository更新数据的服务:

public class MyDataService<TObject> where TObject : class 
{ 
    private readonly MyDataContext context; 

    public MyDataService(MyDataContext ct) 
    { 
     context = ct; 
    } 

    public TObject Update(TObject obj, int id) 
    { 
     var r = new GenericRepository<TObject>(context); 
     return r.Update(obj, id); 
    } 
} 

所以我可以用这样的东西来更新书籍:

var ds = new MyDataService<Book>(new MyDataContext()); 
var data = ds.Update(new Book { Name = "New Name" }, 1); 

这工作正常。接下来,我尝试使用起订量来的单元测试上面的代码的东西,如:

var updatedBook = new Book { Name = "Update Book Name" }; 

var mockSet = new Mock<DbSet<Book>>(); 
var mockContext = new Mock<MyDataContext>(); 
mockContext.Setup(c => c.Books).Returns(mockSet.Object); 
mockContext.Setup(c => c.Set<Book>().Find(It.IsAny<object[]>())) 
      .Returns<object[]>(ids => chips.FirstOrDefault(d => d.Id == (int)ids[0])); 

var service = new MyDataService<Book>(mockContext.Object); 
var data = service.Update(updatedBook, 1); 

但是,我上Context.Entry(obj).CurrentValues.SetValues(data)线异常。

如何正确模拟Update方法?

+1

抽象所有的东西。 :),但严重的是,你的课程应该依赖于抽象而不是结核。那说。所有上面的类都应该在接口后面抽象出来。并且您还将不同的图层紧密结合。服务层不需要知道数据上下文 – Nkosi

回答

1

服务应该依赖于存储库。直接将上下文传递给服务是误导性的,因为服务真正需要和使用的是存储库。

你的课应该依赖于抽象而不是结核。也就是说,上述所有类都可以在接口后面抽象出来。但现在我将专注于服务类,它依赖于存储库。您将紧密耦合不同的图层。服务层并不需要了解数据上下文

摘要库,以便更容易的可测性

interface IGenericRepository<TObject> where TObject : class { 
    TObject Update(TObject data, int id); 
} 

public class GenericRepository<TObject> : IGenericRepository<TObject> where TObject : class { 
    protected readonly MyDataContext Context; 

    public GenericRepository(MyDataContext context) { 
     Context = context; 
    } 

    public virtual TObject Update(TObject data, int id) { 
     if (data == null) 
      return null; 

     TObject obj = Context.Set<TObject>().Find(id); 
     if (obj != null) { 
      Context.Entry(obj).CurrentValues.SetValues(data); 
      Context.SaveChanges(); 
     } 

     return obj; 
    } 
} 

这项服务现在只需要知道有关存储库的抽象,而不是它的实现细节。

public class MyDataService<TObject> where TObject : class { 
    private readonly IGenericRepository<TObject> repository; 

    public MyDataService(IGenericRepository<TObject> repository) { 
     this.repository = repository; 
    } 

    public TObject Update(TObject obj, int id) { 
     return repository.Update(obj, id); 
    } 
} 

所以,现在的服务可以单独进行测试,而无需担心任何数据上下文

//Arrange 
var updatedBook = new Book { Name = "Update Book Name" }; 
var id = 1; 

var mockRepository = new Mock<IGenericRepository<Book>>(); 
mockRepository 
    .Setup(m => m.Update(updatedBook, id)) 
    .Returns(updatedBook); 

var service = new MyDataService<Book>(mockRepository.Object); 
//Act 
var data = service.Update(updatedBook, id); 

//Assert 
//... 

当它的时间进行单元测试孤立的存储库实现,那么你就可以按照相同的结构化和抽象化存储库实现的上下文。

+0

因为我已经有其他的模拟测试工作(如添加,获取...)我不知道如何让你的设计/实现工作与这些工作。使用添加和获取我确实想测试代码库中的代码,而不是仅仅返回一个.Returns来返回一个模拟对象。你能否也展示一个这样做的例子? – notlkk

+0

因为我相信这是正确的设计和实施,所以我对此表示赞同并使其成为正确答案。我也想出了如何在添加和更新操作中替换当前的moq测试。谢谢。 – notlkk

4

您可以实现一个接口,用于MyDataService能够模拟它

public Interface IMyDataService<TObject> where TObject : class 
{ 
    TObject Update(TObject obj, int id); 
} 

public class MyDataService<TObject>:IMyDataService<TObject> 
where TObject : class 
{ 
    private readonly MyDataContext context; 

    public MyDataService(MyDataContext ct) 
    { 
     context = ct; 
    } 

    public TObject Update(TObject obj, int id) 
    { 
     var r = new GenericRepository<TObject>(context); 
     return r.Update(obj, id); 
    } 
} 

起订量:

var mockDataService = new Mock<IMyDataService<Book>>(); 
mockDataService.Setup(c=> c.Update(It.Any<Book>(),It.Any<int>()).Returns(updatedbook); 
+0

您能否通过使用此接口显示我的测试方法应该如何? – notlkk

+0

感谢您的更新示例。我在测试方法中调用mockDataService.Update来调用,因为我似乎无法做到这一点。 – notlkk

+0

@notlkk只是调用'mockDataService.Object.Update' – esiprogrammer

0

为了使测试更容易,甚至我建议小重构可能。通过这个实现,您依赖于DbContext和DbEntityEntry的实现。

在你的范围内第一次提取接口:

public inteface IMyDataContext<TObject> where TObject is class 
{ 
    TObject FindById(int id); //call FindId 
    void Update(TObject); //call DbEntityEntry SetValues 
    void SaveChanges(); 
} 

在GenericRepository再注入的接口。这会让你的生活更轻松,然后你可以轻松地嘲笑所有的方法。存储库的单元测试应该验证是否调用了正确的上下文方法。