55

I've researched some information about techniques I could use to unit test a DbContext. I would like to add some in-memory data to the context so that my tests could run against it. I'm using Database-First approach.

The two articles I've found most usefull were this and this. That approach relies on creating an IContext interface that both MyContext and FakeContext will implement, allowing to Mock the context.

However, I'm trying to avoid using repositories to abstract EF, as pointed by some people, since EF 4.1 already implements repository and unit of work patterns through DbSet and DbContext, and I really would like to preserve all the features implemented by the EF Team without having to maintain them myself with a generic repository, as I already did in other project (and it was kind of painful).

Working with an IContext will lead me to the same path (or won't it?).

I thought about creating a FakeContext that inherits from main MyContext and thus take advantage of the DbContext underneath it to run my tests without hitting the database. I couldn't find similar implementations, so I'm hoping someone can help me on this.

Am I doing something wrong, or could this lead me to some problems that I'm not anticipating?

Community
  • 1
  • 1
Nelson Reis
  • 4,780
  • 9
  • 43
  • 61
  • For anyone still looking for answers - I wrote a simple library to facilitate mocking DbContext a very simple way. For details see my other answer on SO to a similar question: https://stackoverflow.com/a/33716219/111438. PS - I agree with what Ladislav Mrnka is saying, however I think in some cases it's quite inevitable that you need to mock your DbSet and run unit-tests against it. Although you need to keep in mind that you shouldn't be testing your LINQ queries to verify whether they return correct data. That's where integration tests are more applicable. – niaher Nov 15 '15 at 04:15

4 Answers4

38

Ask yourself a single question: What are you going to test?

You mentioned FakeContext and Mocking the context - why to use both? Those are just different ways to do the same - provide test only implementation of the context.

There is one more bigger problem - faking or mocking context or set has only one result: You are not testing your real code any more.

Simple example:

public interface IContext : IDisposable
{
    IDbSet<MyEntity> MyEntities { get; }
}

public class MyEntity
{
    public int Id { get; set; }
    public string Path { get; set; } 
}

public class MyService
{
    private bool MyVerySpecialNetMethod(e)
    {
        return File.Exists(e.Path);
    }

    public IEnumerable<MyEntity> GetMyEntities()
    {
        using (IContext context = CreateContext())
        { 
            return context.MyEntities
                .Where(e => MyVerySpecialNetMethod(e))
                .Select(e)
                .ToList();
        }
    }
}

Now imagine that you have this in your SUT (system under test - in case of unit test it is an unit = usually a method). In the test code you provide FakeContext and FakeSet and it will work - you will have a green test. Now in the production code you will provide a another derived DbContext and DbSet and you will get exception at runtime.

Why? Because by using FakeContext you have also changed LINQ provider and instead of LINQ to Entities you are running LINQ to Objects so calling local .NET methods which cannot be converted to SQL works as well as many other LINQ features which are not available in LINQ to Entities! There are other issues you can find with data modification as well - referential integrity, cascade deletes, etc. That is the reason why I believe that code dealing with context / LINQ to Entities should be covered with integration tests and executed against the real database.

mikolaj semeniuk
  • 2,030
  • 15
  • 31
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • I see what you mean. Seems like integration tests are really the way to go. Thanks Ladislav. – Nelson Reis Jul 22 '11 at 13:06
  • 2
    Ladislav - do you know any good resources on how to run (and automate) integration tests in the situation described? – littlechris Sep 03 '11 at 21:25
  • 1
    @littlechris: I used standard database and test with base class where test initialization and test finalization where defined. Initialization started transaction and finalization rolled transaction back. It ensured that my tests will always use database with the same state. – Ladislav Mrnka Sep 15 '11 at 22:37
  • 17
    Regarding preferring to do integration testing, I disagree that this is the only way of testing code that uses EF. I believe that it is important to be able to do both. When unit testing business logic that happens to use EF, you don't want to be testing that your model is in sync with your database by doing an integration test. It's the definition of unit testing. Obviously the integration testing is important, but it's a separate type of test. I've written up an alternative answer that explains how you can do your unit testing. – Josh Gallagher Oct 18 '11 at 06:25
  • This is where I've grown to love code first for EF - create my sql express or sql ce db when the unit test harness starts up, and removes it when done. – Adam Tuliper Oct 27 '11 at 05:19
  • 4
    I've read many Q&A's on how to mock the ObjectContext, but all of them felt 'incomplete'. With Ladislav's answer, I now know where was the smell. You have pinpointed it very well: "There is one more bigger problem - faking or mocking context or set has only one result: You are not testing your real code any more." --- Not only my!! The EF has so much little intricates, that I actually DO WANT to test THEIR ObjectContext implementation, because every now and then I stumble on something so fu*** stupid on their side or some missing features that noone would thought are missing.. Thx – quetzalcoatl Apr 24 '12 at 09:36
  • 3
    Really? Can you not imagine a single case where mocking the DB would be useful? Those particular tests won't tell you about LINQ-to-SQL errors, but they would test other errors. – heneryville Jan 14 '13 at 22:27
  • 2
    @heneryville: Sure they will! But isn't it simply better to write your code in the way that you define clear boundary between code which must be covered by integration test and code which should be covered with unit tests? In such code you will not fake the context or set because its usage will be hidden by another class with responsibility to execute queries. That class will be covered with integration tests and its usage in other code will be faked in unit tests. – Ladislav Mrnka Jan 14 '13 at 22:37
  • 1
    I agree that the real context could fail where the fake context works. But that's the whole point of fakes! By reading your answer one could think that injecting fake implementations is inherently wrong while it is not. Real implementations of business services do subtle things and could fail for many reasons and we still mock/fake them using simple-and-always-working implementations. For example, I have an email service that sends emails and my fake just logs the email. But the real implementation could fail because the server is down at the moment. Yet, this doesn't make my fake wrong. – Wiktor Zychla Apr 29 '13 at 13:02
  • 3
    @WiktorZychla: Analogy to your email service is not running database server and that is of course something you don't bother with when unit testing your code. The problem with faking context and sets is with silently replacing Linq provider. Unless you test your code with a real database provider as well you didn't tested your Linq queries at all. That is the whole point. By refactoring the code you can get much better separation where your fake will hide query (not just set or context) and the query itself will be tested separately. – Ladislav Mrnka Apr 29 '13 at 17:52
  • 1
    On the other hand, this is not the linq provider you are testing with fake context but rather a business class that uses the data. I am still under impression that by replacing a dbcontext with a fake you still insist that this is the dbcontext acting as a SUT and you have to test its implementation for some reason. But if the dbcontext is injected into another object, the real SUT, does it really matter whether or not services injected into it are imlemented correctly? My point is, it doesn't matter. – Wiktor Zychla Apr 29 '13 at 18:07
  • 1
    @WiktorZychla: If the point of the SUT is not to test the query but the code using the result of the query, you can use fakes but what will then test the query itself? From my experience it doesn't matter only if the Linq query is tested again with correct Linq provider because the query itself is part of the tested logic (and very often the only real logic people are trying to test with the fake). – Ladislav Mrnka Apr 29 '13 at 18:37
  • See also: https://stackoverflow.com/a/33716219/861716 – Gert Arnold Jun 24 '21 at 07:24
14

I am developing an open-source library to solve this problem.

http://effort.codeplex.com

A little teaser:

You don't have to add any boilerplate code, just simply call the appropriate API of the library, for example:

var context = Effort.ObjectContextFactory.CreateTransient<MyContext>();

At first this might seem to be magic, but the created ObjectContext object will communicate with an in-memory database and will not talk to the original real database at all. The term "transient" refers to the lifecycle of this database, it only lives during the presence of the created ObjectContext object. Concurrently created ObjectContext objects communicate with dedicated database instances, the data is not shared accross them. This enables to write automated tests easily.

The library provides various features to customize the creation: share data across instances, set initial data of the database, create fake database on different data layers... check out the project site for more info.

tamasf
  • 1,068
  • 2
  • 10
  • 22
11

As of EF 4.3, you can unit test your code by injecting a fake DefaultConnectionFactory before creating the context.

just.another.programmer
  • 8,579
  • 8
  • 51
  • 90
5

Entity Framework 4.1 is close to being able to be mocked up in tests but requires a little extra effort. The T4 template provides you with a DbContext derived class that contains DbSet properties. The two things that I think you need to mock are the DbSet objects that these properties return and properites and methods you're using on the DbContext derived class. Both can be achieved by modifying the T4 template.

Brent McKendrick has shown the types of modifications that need to be made in this post, but not the T4 template modifications that can achieve this. Roughly, these are:

  1. Convert the DbSet properties on the DbContext derived class into IDbSet properties.
  2. Add a section that generates an interface for the DbContext derived class containing the IDbSet properties and any other methods (such as SaveChanges) that you'll need to mock.
  3. Implement the new interface in the DbContext derived class.
Josh Gallagher
  • 5,211
  • 2
  • 33
  • 60
  • 6
    But this is exactly what is wrong and useless. If you mock the set / context and use the example from my answer you will get a green unit test but an exception at runtime. It will not be exception from database - it will be exception from entity framework = you wasted time on writing test which tests nothing and says nothing about your code. So what is the value of such test and what does it test? Moreover many people follow rule that you should not mock code you don't own because in such case you just guess its functionality when you mock it. – Ladislav Mrnka Oct 18 '11 at 07:53
  • 10
    Yes, for your example you'll get inconsistent results between unit and integration testing and realise that you'd used a technique that didn't work. But if you're using more vanilla LINQ to retrieve entities and then performing some business logic with them, it's still very valid to be testing that business logic in a unit test. E.g. change your example to perform the operation in two steps: 1) retrieve the items via LINQ (maybe .ToList), 2) strip the list down by using your method. Then the unit test will still be useful and the integration test doesn't have to cover all the edge cases. – Josh Gallagher Oct 18 '11 at 08:30
  • 6
    Sure, your approach works in some cases but unit testing strategy must work in all cases - to make it work you must move EF and `IQueryable` completely from your tested method. That is not point of unit test to pretend that you write it correctly - the point of test is to validate that you write the logic correctly. Also it is not a good practice to write both unit and integration test to validate the same code which in your scenario must be done. – Ladislav Mrnka Oct 18 '11 at 08:35
  • @Ladislav: Can you please elaborate a bit on how exactly one should extract the EF code from the BL code and put it in a separate location? I am thinking of cases where a BL method contains several different calls to 'entities.something' and at the end there is 'entities.SaveChanges'... I have posted my question [HERE](http://stackoverflow.com/questions/10940873/unit-testing-ef-how-to-extract-ef-code-out-from-bl) – John Miner Jun 07 '12 at 22:45
  • There are practicalities to consider. I've been doing this for two years now and get a lot of value out of a) having tests for the EF and IQueryable logic, and b) not having those tests need to deal with the complexity of tests that include the database, e.g. data clean-up, slower execution time, lack of parallel execution of tests. – Josh Gallagher Sep 25 '13 at 09:53