How can I detect changes IDbSet

302 Views Asked by At

I am creating a mock IDbSet to allow unit testing of entity framework classes, amongst other things.

However I am really having trouble detecting changes, or even figuring out how to do it at all. Here are my classes so far...

public interface IReportContext
{
    IDbSet<Report> Reports {get;}
    public int SaveChanges();
}

public class MockReportContext : IReportContext
{
    IDbSet<Report> Reports {get;}

    public int SaveChanges()
    {
        //Need to detect changes here???
    }

    public MockReportContext()
    {
       Reports = new MockDbSet<Report>();
    }
}

public class MockDbSet<T> : IDbSet<T>
{
    readonly ObservableCollection<T> _data;
    readonly IQueryable _query;

    public FakeDbSet()
    {
        _data = new ObservableCollection<T>();
        _query = _data.AsQueryable();
    }

    public FakeDbSet(ObservableCollection<T> data)
    {
        _data = data;
        _query = _data.AsQueryable();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException();
    }

    public T Add(T item)
    {
        _data.Add(item);
        return item;
    }

    public T Remove(T item)
    {
        _data.Remove(item);
        return item;
    }

    public T Attach(T item)
    {
        _data.Add(item);
        return item;
    }

    public T Detach(T item)
    {
        _data.Remove(item);
        return item;
    }

    public T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public ObservableCollection<T> Local
    {
        get { return _data; }
    }

    Type IQueryable.ElementType
    {
        get { return _query.ElementType; }
    }

    System.Linq.Expressions.Expression IQueryable.Expression
    {
        get { return _query.Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return _query.Provider; }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }
}

This works fine for adding, deleting and retrieving entities. However when I try the following:

IReportContext context = new MockReportContext();
context.Reports.Add(new Report()); //Works
Report report = context.Reports.First(); //Works
report.Message = "Hello World!";
context.SaveChanges(); //Does nothing

How is it the MockReportContext could know that the report object it returned has changed?? I know using entity framework does this so it must be possible, but I haven't got a clue how...

1

There are 1 best solutions below

0
On BEST ANSWER

I think you're mostly there, but I'd suggesting using a mocking framework like Moq (my personal preference) or Rhino Mocks to mock the IReportContext for your unit tests, instead of going to the trouble of creating a Fake class like MockReportContext. (Alright, learning a mocking framework does involve some trouble, but it'll save a lot of Fake class drudgery down the road.)

I would presume that you're unit testing the code that depends on the IReportContext, so you don't need to do anything inside SaveChanges(), you just need to assert that your code did call SaveChanges() internally if it was supposed to.

Here's a good overview of using Moq with Entity Framework's derived DbContext / DbSet classes.

In the above linked overview, if the unit test at the end was testing a method that internally calls SaveChanges(), it could additionally verify that SaveChanges() was indeed called by your method with the line:

dc.Verify(db => db.SaveChanges());

You could also do this with your MockReportContext class by setting a side property to True inside the SaveChanges() class and checking it at the end of your unit test, but a mocking framework is much more flexible and will save the need to write additional classes for unit tests.

If you'd like to avoid using a mocking framework, then here's how to do it with fakes.