引用:http://vaideeswaranr.blogspot.com/2013/02/using-microsoft-fakes-to-unit-test.html
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.
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: };
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.
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!!