Sunday, February 24, 2013

Using Microsoft Fakes to Unit Test Entity Framework

If you have used Entity Framework, then I am sure you have read in various blogs that using the Repository pattern is the best way to test it.

Though I hardly have the expertise to challenge that notion, I take that statement with caution. The pattern itself is great for usages where you want to shield your data access from your middle tier, my personal opinion is that the most value of a repository pattern is achieved when you want to access disparate sources but provide a consistent experience of interacting with entities.

So the idea of introducing a pattern for the sake of testability seemed a bit strange. For one, I couldn’t fathom why the application should carry the weight of an additional pattern when it is going to be used only for testing. So I went about exploring alternate options of testing EF without using the repository pattern. Its no secret that I am an admirer of Fakes (Wrote about its ancestor Pex and Moles here), so I decided to see if Fakes will stand up to the challenge of unit testing EF.

The structure of my solution itself is fairly trivial. I have a service project with an edmx in it and one GetValue method that returns an entity from my table using EF. A unit test project completes the solution.

image

My service method

   1: public class FakesService : IFakesService
   2:     {
   3:         public MyEntity GetValue(int value)
   4:         {
   5:             using (MyDbContext entities = new MyDbContext())
   6:             {
   7:                 return entities.MyEntities.Find(value);
   8:             }
   9:         }
  10:     }

This is the method I want to test. To shim my EF entities, I created a Fakes Assembly for the service dll in my test project..


The method that we are interested in shimming is the MyEntitiesGet; done with the code below



   1: ShimMyDbContext.AllInstances.MyEntitiesGet = (shimContext) =>
   2: {
   3:     return shimContext.MyEntities;
   4: };

 

But there is an issue with the above snippet. I am detouring the Entities Get to execute my code, but not mocking anything. Because the shimContext has a reference to MyDbContext, the code executed will still be the EF code, pointing to the database. To truly mock this, I will have to create a fake DbContext.

 


   1: public class FakesContext : DbContext
   2:    {
   3:        public DbSet<MyEntity> MyEntities { get; set; }
   4:        public FakesContext()
   5:        {
   6:            
   7:        }
   8:    }

To have this constructor invoked when MyDbContext class is initialized, it has to be shimmed



   1: MyDbContext.Constructor = (paramDbContext) => GenerateConstructor();
   2: private DbContext GenerateConstructor()
   3:         {
   4:             return new FakesContext();
   5:         }

So, that’s all!! Or so it would seem. Just when I was ready to celebrate my little victory of mocking EF, something hit when I debugged the test method.


image


The line that was causing this error was


return entities.MyEntity.Find(value);


Digging a bit more, I found that the using statement was the culprit and the error occurred in Dispose method of the DbContext object in EF. At first I was left scratching my head. But then I realised that since we are mocking the constructor, it is trying to dispose the original MyDbContext class and it isn’t able to. After trying various alternatives, I came to the conclusion that the only way to get around this is to shim the Dispose method of the EF DbContext. To do this, I had to generate a Fakes Assembly for the EntityFramework dll (I am using EF 5). I wasn’t sure how well Fakes was going to handle this but to my surprise, it went smoothly enough and I ended up with the method below.



   1: ShimDbContext.AllInstances.Dispose = (context) => DummyDispose();
   2: private void DummyDispose()
   3: { 
   4: }

The only thing left to do now, is to return my own list of entities instead of ones from the database. We go back to the EntitiesGet shim to do that. The code for that now looks something like the below.



   1: MyDbContext.AllInstances.MyEntitiesGet = (shimContext) =>
   2: {
   3:     FakesContext dummyContext = new FakesContext();
   4:     Database.SetInitializer<FakesContext>(null);
   5:     Entity entity1 = new Entity();
   6:     entity1.Id = 1;
   7:     entity1.Name = "First created from test";
   8:     dummyContext.MyEntities.Add(entity1);
   9:     Entity entity2 = new Entity();
  10:     entity2.Id = 2;
  11:     entity2.Name = "Second created from test";
  12:     dummyContext.MyEntities.Add(entity2);
  13:     return dummyContext.MyEntities;
  14: };

Nothing out of ordinary except for the SetInitializer statement. That is to turn off tracking by EF. There is a post by Scot Hanselman (I think) on it, so I wont go into it too much.


So there you go! A successfully mocked unit test on EF. I have to admit that I haven’t tried mocking other EF methods and there could be challenges there, but with this little exercise, I am fairly confident that those challenges can be overcome.  Happy Coding!!

2 comments:

  1. hi,

    i tried your code and I was not able to make it to work. When I call MyDbContext.MyEntities.ToList(), it returns a zero count list. What version of entity framework are you using? and can you provide a working sample? Thanks! I am also trying to create a shim of entity framework to create unit tests. If this does not work, I would have to create a repository of my entities which I also don't like since it will create another layer which I am avoiding.

    ReplyDelete
    Replies
    1. Ryan,

      I am using EF 5.0.

      My guess is that the ToList is executing against the database. If you have not already done it, kill the db connection and try again. The ToList() method is an IEnumerable method and hence you have to shim that too. I haven't personally done it,but there are details on how to do it in this link http://community.dynamics.com/crm/b/zhongchenzhoustipstricksandportaldevelopment/archive/2012/07/09/dynamics-crm-2011-unit-test-part-2-microsoft-fakes-with-linq-query-organizationservicecontext-and-crmorganizationservicecontext.aspx#.UUDvVhykoRo

      This handles CRM but should be similar for DbSet. If you still need my sample code, please let me know and I can send it to you.

      Delete