9

I'm doing a small practice project to improve my unit testing skills. I'm using Entity Framework Code First.

I'm using a FakeDBSet, which works well for simple lists of entities. When entity trees are returned things aren't so nice. In particular two way relationships aren't maintained as this is part of the Entity Framework magic.

I have two classes:

public class Book
{
    public virtual ICollection<Review> Reviews {get; set;}
}

public class Review
{
    public virtual Book Book { get; set;}
}

If I set the book for a review, the review does not get added to the book's review collection. It does when using EF, but not in my fake version.

Is there a way mock this behaviour, or should I not rely on EF to implement two way relationships? Or is mocking the data context just a waste of time?

Richard Garside
  • 87,839
  • 11
  • 80
  • 93

2 Answers2

6

This is actually a pretty common problem (and one without a really good answer). There is a process which happens inside of EF called fixups which runs inside the detect changes loop (the one that triggers on add/remove and a few other changes). This evaluates backlinks in your model. When you start mocking your context you are going to lose that detect changes loop and hence the fixups.

In the past I've gotten around this by understanding this particular limitation of my mocks and making sure i do my setup code in the correct way to make sense in the code (which lets face it is pretty un-ideal). The other option here is to go to some form of real lightweight database in your unit tests and keep using EF.

undefined
  • 33,537
  • 22
  • 129
  • 198
  • I don't see a great benefit in mocking and EF. EF behaves much different than LINQ to objects and most code in the business layer in a typical business application is about storing data. – yonexbat Dec 17 '12 at 19:55
  • 3
    @yonexbat a key reason to mock is for performance. Tests require a known state which means that ideally you should be setting the state up for each specific test. If you are using a real database the time to drop and create a database for the test is likely to be around 1s. If you scale this out over 1000 or so tests it can become pretty unmanageable. IMO border testing is really important, which means assuming EF is going to do what you expect how is your app internal logic going to work. Its these scenarios which you are testing with mocks anyway – undefined Dec 17 '12 at 20:17
  • FYI: I've got a post where I'm compiling [differences between linq to objects and linq to entities](http://stackoverflow.com/a/13352779/861716). I added this example. – Gert Arnold Dec 18 '12 at 08:19
  • @GertArnold good summary, i agree its a bit of a pain to test vs LTE, i still think however that there is some value in mocking EF in your tests but its really important to understand for completeness nothing beats an integration test here. My feeling is that even with all of the difficulties there is still value in specifically targeting bits of logic surrounding your database. – undefined Dec 18 '12 at 08:38
  • 1
    @LukeMcGregor Agree. Ordinary unit tests are good for testing whether code is hit in the first place, for instance. We might even accept that "correct" has a different meaning in both environments. The key is being aware of the differences. – Gert Arnold Dec 18 '12 at 08:46
  • I really hoped there would be a good answer. In previous tests I'd setup my mocks in a way that understood the limitation, but it felt messy. Now I'm trying to test a behaviour in a service that relies on this two way relationship in the model. I'm wondering whether it's wrong to rely on behaviour injected by EF. – Richard Garside Dec 18 '12 at 10:40
  • @Richard Personally i do avoid relying on fixups for code execution (mostly for the same reasons as you) but also as it feels like a bit of a side effect you are relying on. – undefined Dec 18 '12 at 11:51
5

A possible solution I've found is to create a mock object that mimics the EF fixup code.

Here is an example using the Mocking framework NSubstitute:

private static Book CreateMockBook()
{
    var book = Substitute.For<Book>();

    // Mock EF fixup: Add a review to collection should also set book for the review
    book.Reviews.Add(Arg.Do<Review>((x) => { if(x.Book != book) x.Book = book; }));

    return book;
}

private static Review CreateMockReview()
{
    var review = Substitute.For<Review>();

    // Mock EF fixup: Set a book for the review should also should add the review to book's review collection
    review.When(x  => x.Book = Arg.Any<Book>()).Do(x => review.Book.Review.Add(review));

    return review;
}

This works quite nicely, but I'm not sure if needing to mock this behaviour is an indication that my tests have got too complicated or that my code is making use of side effects that it shouldn't.

I'd be interested in what other people think of this?

Richard Garside
  • 87,839
  • 11
  • 80
  • 93