64

Following on the heels of my other question about mocking DbContext.Set I've got another question about mocking EF Code First.

I now have a method for my update that looks like:

if (entity == null)
    throw new ArgumentNullException("entity");

Context.GetIDbSet<T>().Attach(entity);
Context.Entry(entity).State = EntityState.Modified;
Context.CommitChanges();

return entity;

Context is an interface of my own DbContext.

The problem I'm running in to is, how do I handle the

Context.Entry(entity).State.

I've stepped through this code and it works when I have a real live DbContext as the implementation of my Context interface. But when I put my fake context there, I don't know how to handle it.

There is no constructor for a DbEntityEntry class, so I can't just create a new one in my fake context.

Has anyone had any success with either mocking or faking DbEntityEntry in your CodeFirst solutions?

Or is there a better way to handle the state changes?

Community
  • 1
  • 1
taylonr
  • 10,732
  • 5
  • 37
  • 66

2 Answers2

103

Just like the other case, what you need is to add an additional level of indirection:

interface ISalesContext
{
    IDbSet<T> GetIDbSet<T>();
    void SetModified(object entity)
}

class SalesContext : DbContext, ISalesContext
{
    public IDbSet<T> GetIDbSet<T>()
    {
        return Set<T>();
    }

    public void SetModified(object entity)
    {
        Entry(entity).State = EntityState.Modified;
    }
}

So, instead of calling the implementation, you just call SetModified.

Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
  • Thanks... I got stuck thinking "How do I mock Entry" when I don't need to, I just need to mock the modified functionality... I'm almost embarrassed it's so obvious now. – taylonr Feb 18 '11 at 13:57
  • 13
    Don't be - our lives as developers are filled with "duh!" moments :-) – Diego Mijelshon Feb 18 '11 at 15:09
  • 4
    +9000 I just spent an hour researching how to mock classes with internal ctors and internal classes. I was getting thwarted at every turn and the solution is so simple! Thanks to both the asker and the answerer – Darko May 20 '11 at 05:03
  • @DiegoMijelshon This is elegant! – devlord Oct 16 '13 at 22:36
  • 1
    @ErwinRooijakkers late response, but in your tests, you don't do anything. Just implement the `SetModified` in your fake of `ISalesContext` and leave it empty. Just `public void SetModified(object entity){}` – Nathan Koop Jun 19 '15 at 14:32
  • Hi Nathan, bit late indeed. The advantage I saw in having a fake context with in-memory objects is it was possible to exactly test what happened to these objects, but it was difficult to maintain and quite complex to setup and unintuitive to read. I found it easier to use Moq and just mock the whole repository and/or unit of work. You lose some deeper down checking of objects (for what it is worth since it is not te actual data store) in exchange for readability and ease of testing. – user2609980 Jun 19 '15 at 22:28
  • Oh wait there was no question. Anyway, what you said is what I did indeed. :-) – user2609980 Jun 19 '15 at 22:30
  • Works perfectly, but regenerating edmx (database first) seems like wiping out my implementations! – Robin1990 Jun 26 '20 at 10:01
  • @Robin1990, put your implementation in a partial class and it won't get wiped out when regenerating the .edmx. – DSoa Mar 24 '22 at 13:25
4

Found this question when I needed to unit test with Moq, no need for your own interface. I wanted to set specific fields to not modified but the method SetModified can be used with object as well.

DbContext:

public class AppDbContext : DbContext
{   
    ...
    public virtual void SetModified(GuidEntityBase entity)
    {
        Entry(entity).State = EntityState.Modified;
        Entry(entity).Property(x => x.CreatedDate).IsModified = false;
        Entry(entity).Property(x => x.CreatedBy).IsModified = false;
    }
    ...
}

Test:

var mockContext = new Mock<AppDbContext>();
mockContext.Setup(c => c.MyDbSet).Returns(mockMyDbSet.Object);
mockContext.Setup(c => c.SetModified(It.IsAny<GuidEntityBase>()));
Ogglas
  • 62,132
  • 37
  • 328
  • 418