Unit Testing DbContext

谁说我不能喝 提交于 2019-11-26 01:49:35

问题


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?


回答1:


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.




回答2:


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.




回答3:


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




回答4:


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.



回答5:


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.



来源:https://stackoverflow.com/questions/6766478/unit-testing-dbcontext

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!