Moq - 使用DbEntityEntry更新

时间:2016-12-05 20:59:00

标签: c# .net entity-framework-6 moq

我正在使用EF6。生成的代码类似于:

PÁGINA: 1
NIF --------- NOME -------------------- DATA DE NASCIMENTO ---------- SEXO
=========================================================================================

111111111 | Rui Filipe Monteiro Almeida | 12/06/1998 | masculino | 
333333333 | Amadeus Antunes Roberoso Tuberculo | 17/03/2000 | masculino | 
123485748 | Romeu Julieta Runescape World | 18/06/2004 | masculino | 
678378589 | Antonieta Julia Mosquito | 20/15/1990 | feminino | 
889589488 | LEEROY JENKINS CHUMPS UP LETS DO THIS | 27/06/1994 | feminino | 

Para continuar digite ENTER



PÁGINA: 2
NIF --------- NOME -------------------- DATA DE NASCIMENTO ---------- SEXO
=========================================================================================

null | null | null | null | 

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

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

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

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

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

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

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

这很好用。接下来我尝试使用Moq对上面的代码进行单元测试,例如:

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); 行上遇到了例外情况。

如何正确模拟Update方法?

3 个答案:

答案 0 :(得分: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);

答案 1 :(得分:2)

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

你的课程应该取决于抽象而不是结核。也就是说,所有上述类都可以在接口后面进行抽象。但是现在我将专注于服务类及其对存储库的依赖。你太紧密地耦合不同的层。服务层不需要了解数据上下文

抽象存储库以便更容易测试

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
//...

如果需要单独对存储库实现进行单元测试,那么您可以遵循相同的结构并为存储库实现抽象上下文。

答案 2 :(得分: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中注入接口。这将使您的生活更轻松,然后您可以轻松模拟所有方法。存储库的单元测试应验证是否调用了上下文的正确方法。