7

I need to mock EF's DbContext. I use the approach here and it works well.

// mock a DbSet
var mockDbSet = Substitute.For<DbSet<Foo>, IQueryable<Foo>>();
var data = new List<Foo>().AsQueryable();
((IQueryable<Foo>)mockDbSet).Provider.Returns(data.Provider);
((IQueryable<Foo>)mockDbSet).Expression.Returns(data.Expression);
((IQueryable<Foo>)mockDbSet).ElementType.Returns(data.ElementType);
((IQueryable<Foo>)mockDbSet).GetEnumerator().Returns(data.GetEnumerator());
// now add it to a mock DbContext
var mockContext = Substitute.For<MyDbContextClass>();
mockContext.Set<Foo>().Returns(mockDbSet);

However in some tests I need to be able to call mockContext.Set<Foo>().Add(someFoo) and mockContext.Set<Foo>().Remove(otherFoo), and for the underlying add/remove logic to work.

I tried this:

mockDbSet.When(x => x.Add(Arg.Any<Foo>())).Do(x => data.Add(x.Arg<Foo>()));

but it throws with Collection was modified; enumeration operation may not execute.

So how do I implement add/remove functionality?

h bob
  • 3,610
  • 3
  • 35
  • 51
  • I need to add/remove to the context, not verify that add/remove were called (I know how to do that). – h bob Sep 14 '16 at 11:07
  • You should include that last comment in your original question as an edit, not as a comment as not everyone reads the comments. – Igor Sep 14 '16 at 11:10
  • 2
    I recently started using this helper library and it seems that it supports add/remove. I've been using it for a like 10 tests currently so I don't know more https://github.com/scott-xu/EntityFramework.Testing – Stilgar Sep 14 '16 at 11:14
  • There is the EntityFrameworkCore.Testing.NSubstitute nuget package that lets you mock dbContext. Its simple to use read more here: https://github.com/rgvlee/EntityFrameworkCore.Testing – Mohammad Apr 21 '20 at 11:19

2 Answers2

5

You don't want to add it to the collection. What you want to do is check if it (add/remove/etc) was called and possibly what it was called with.

// arrange - check what it was called with. You place asserts in the body of the `Do` expression. Place this before your call that actually executes the code
mockDbSet.Add(Arg.Do<Foo>(foo =>
{
    Assert.IsNotNull(foo);
    // Assert other properties of foo
}));

// act


// assert. Make sure that it was actually called
mockDbSet.Received(1).Add(Arg.Any<Foo>());

If you want to add a Foo at a later point in your test you can keep the reference to the List of Foo's.

// mock a DbSet
var mockDbSet = Substitute.For<DbSet<Foo>, IQueryable<Foo>>();
var fooList = new List<Foo>();
var data = fooList.AsQueryable();
// rest of your code unchanged

// add it from the code being tested through the mock dbset
mockDbSet.Add(Arg.Do<Foo>(foo =>
{
    fooList.Add(foo);
    // at this point you have to recreate the added IQueryable
    data = fooList.AsQueryable();
    // rest of code you had to add this to the mockDbSet
}));


// act
Igor
  • 60,821
  • 10
  • 100
  • 175
  • No I *want to add to the context*. I can do that during initialization, but sometimes I must do so later. In some tests I don't care whether they were called, but I need the data in there, so I can use it for some other purpose. – h bob Sep 14 '16 at 10:53
  • @hbob - That was not clear to me. I have updated the answer. – Igor Sep 14 '16 at 11:02
  • I tried that too, but when accessing the mock DbSet it throws the same exception - `Collection was modified; enumeration operation may not execute.` (Which kinda makes sense when you think about it.) – h bob Sep 14 '16 at 11:23
  • @hbob - ah, ok. You also did not mention that the exception was thrown when you tried to access to the collection after you modified it (*all little helpful details*). I updated the answer. What you want to do is essentially rebuild the mocked DbSet and reassign it to the DbContext's property when an add/remove occurs. I also have written a library for nsubstitute and Ef about 1.5 years ago, I added [my 2 cents](http://stackoverflow.com/a/39489385/1260204) to the question you referenced, this makes creating and using a mocked dbset much easier, you can do it with 2 lines of code. – Igor Sep 14 '16 at 11:45
  • 1
    Ah yes that makes sense to rebuild it. Thanks. – h bob Sep 14 '16 at 11:49
0

The comment by @Stilgar made me look into the EntityFramework.Testing library, which solves this very problem.

So I replaced my stuff with this library (one less thing to worry about).

h bob
  • 3,610
  • 3
  • 35
  • 51