Moq - Update with DbEntityEntry

.net c# entity-framework-6 moq

Question

I'm using EF6. The generated code is something like:

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

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

Then I have a generic repository like:

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

Then I have a service that uses the GenericRepository to update data:

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

So I can update a books with something like this:

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

This is working fine. Next I try to use Moq to unit test the above code with something like:

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

However, I get an exception on the Context.Entry(obj).CurrentValues.SetValues(data) line.

How do I mock the Update method properly?

1
1
12/5/2016 8:59:00 PM

Accepted Answer

The service should be dependent on the repository. Passing the context directly to the service is misleading as what the service really needs and uses is the repository.

Your classes should depend on abstractions and not on concretions. That said, all the above classes could be abstracted behind interfaces. but for now I'll focus on the service class and it's dependence on the repository. You are coupling different layers too closely. Service layer doesn't need to know about data context

Abstract the repository to allow for easier testability

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

The service would only now need to know about the repository abstraction, not its implementation details.

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

So now the service can be tested in isolation without any need to worry about any data context

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

When it's time to unit test the repository implementation in isolation, then you can follow the same structure and abstract the context for the repository implementation.

2
12/5/2016 9:51:10 PM

Popular Answer

You could implement an interface for MyDataService to be able to mock it

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

Moq:

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


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow