次の方法で共有


Unit Testing Your Entity Framework Domain Classes

Technorati Tags: Entity Framework,TDD

One interesting question customers that are TDD practitioners usually ask is how to do unit testing with the Entity Framework using mock objects. That is, testing only the domain logic part of the model, without ever touching persistence logic or round-tripping to the database. Usual reasons you want to do this include:

  • Test performance
  • Size of the database
  • Avoid test side effects

The saving grace for this approach is that persistence is a separate concern from the business logic in your domain, and so it should be  tested separately.

Also, we test the Entity Framework a lot here at Microsoft. So, for customers using our code, it should be more cost effective to test their own code :)

How easy it is to apply this practice to the Entity Framework depends heavily on how your code is factored. There are a few things to consider:

Explicitly separate concerns

If you want to unit test your domain classes (either IPOCO or code-generated classes fleshed with domain logic, since EF v1 does not support pure POCO classes), the first step is to push out of the picture all the code paths that define and execute queries against the database.

That means that all code that deals with IQueryable<T>, ObjectQuery<T>, and IRelatedEnd.Load() needs to be encapsulated in a separate DAL component.

I can envision a pattern in which such component exposes fixed function methods that produce entire object graphs based on specific parameters.

As a simple example, we can specify an interface with all the necessary methods to get Northwind entities:

Edit: I changed the name of the interface from INorthwidnContext to INorthwindStore to show that it is not necessarily something you implement in your typed ObjectContext.

    interface INorthwindStore
    {
        IEnumerable<Product> GetProducts
(int? productID, int? categoryID);
        IEnumerable<Customer> GetCustomers
(string customerID, string customerName);
        IEnumerable<Order> GetOrdersWithDetailsAndProducts
(int orderID);
...
    }

Once defined, the interface can be implemented as methods that hydrate the object graphs from the database, but also as a mock that hydrates pre-built object graphs for your tests.

Why not IQueryable<T> properties?

There is a case for exposing IQueryable<T> properties directly (or ObjectQuery<T> properties as typed ObjectContexts do) instead of fixed function methods: The ability to compose queries in LINQ comprehensions gives much flexibility and is very attractive.

However, not all IQueryable implementations are made equal, and the differences among them are only apparent at runtime.

There are a number of functions that LINQ to Objects support that LINQ to Entities doesn’t. Also, there are some query capabilities that in EF v1 are only available to ESQL and not for LINQ.

Moreover, there is no way to execute ESQL queries against in-memory objects.

Finally, query span behavior (i.e. ObjectQuery<T>.Include(string path) method) would be too difficult to reproduce for in-memory queries.

By implementing our query method as fixed function points, we are drawing a definite boundary at a more appropriate level of abstraction.

The good news is that it is relatively easy get an IEnumerable<T> results either from a LINQ or ESQL query, and doing so does not imply loosing the streaming behavior of IQueryable<T>.

You can simply return query.AsEnumerable() or (in C#) write a foreach loop that “yield returns” each element.

What happens with lazy loading?

When I say that a method must produce entire graphs, the real constraint is that once the method is invoked, client code should be safe to assume that all necessary objects are going to be available. In theory, that constraint can be satisfied with either eager or automatic lazy loading.

EF v1 codegen classes only support explicit loading, but if you implement your own IPOCO classes or you manipulate code generation, you can get automatic lazy loading working.

Still, the mock implementation should better populate full graphs in one shot.

Edit: All this is said assuming you know that you want lazy loading even if this is at the risk of in-memory inconsistencies and extra round-trips. See here for an implementation of transparent lazy loading for Entity Framework.

How to deal with ObjectContext?

As Danny explains in a recent post, ObjectContext provides a number of important services to entity instances through their lifecycle, and so it is generally a good idea to keep a living ObjectContext around and to keep your entity instances (at least the ones you expect to change) attached to it.

There are few approaches that would work:

  1. Encapsulate ObjectContext in your DAL component.
  2. Pass an ObjectContext instance in each method invocation to your DAL component.
  3. Maintain some kind of singleton instance available that all the code can share.

For the mocking implementation, it is possible to initialize a context with a “metadata-only” EntityConnection:

var conn = new EntityConnection(
    @"metadata=NW.csdl|NW.ssdl|NW.msl;
    provider=System.Data.SqlClient;");
var context = new NorthwindEntities(conn);

This will provide enough information for all but the persistence related functions of ObjectContext to work.

One common concern about keeping an ObjectContext around is that it will keep a database connection alive too. However, ObjectContext contains connection management logic that automatically opens the connection when it is needed and then closes it as soon as it is not being used.

What about CUD operations and SaveChanges()?

Besides providing a launch point to queries, ObjectContext implements the Unit of Work pattern for EF. Most of the behavioral difference resulting from having your entities attached to an ObjectContext, only take place at the time you perform CUD operations (Insert, Update or Delete) or invoke the SaveChanges() method. This is when changes are tracked and saved, and then is when concurrency control is enforced.

Invoking AddObject(), Delete() or changing property values on your entities from within your test cases should work without changes.

In order for the mock DAL component not to hit the database every time SaveChanges() is invoked, we should redirect SaveChanges() to AcceptAllChanges().

Most operations will work as expected whether the ObjectContext is fully connected or “metadata-only”. But to make things more complicated, there are some additional side effects we need to take care of:

  • SaveChanges() may trigger the refresh of store generated values.
  • EntityKeys on entities and EntityReferences may have different values after SaveChanges().

To mitigate these issues, no code outside your persistence layer should rely on those side effects. A simple rule of thumb that satisfies this requirement is to start anew with a fresh ObjectContext every time you finish your unit of work.

Also, EntityKeys should be dealt with only in persistence code or serialization code, not in business logic.

Conclusion?

It is actually premature to use the word “conclusion”. Mixing EF and TDD in the same pan is something I am only starting to think about. This is a set of scenarios that I want to see among our priorities for future versions.

In order to come to a real conclusion, I need to at least develop a sample application in which I apply and distill the approaches I am suggesting in this post. I hope I will find the time to do it soon.

Comments

  • Anonymous
    March 03, 2008
    Danny and I appear to be giving inconsistent advice on this regard in our recent weekend posts: Danny:

  • Anonymous
    March 03, 2008
    Danny and I appear to be giving inconsistent advice on this regard in our recent weekend posts: Danny

  • Anonymous
    March 09, 2008
    Diego Vega, a Program Manager on the EF team at Microsoft, considers the challenges of Unit Testing EF

  • Anonymous
    October 02, 2008
    I've recently blogged about unit testing security in business objects.  Might be of interest: http://mrpmorris.blogspot.com/2008/09/unit-testing-security.html

  • Anonymous
    December 21, 2008
    http://andrewpeters.net/2007/04/28/fixing-leaky-repository-abstractions-with-linq/ What about such approach? I'm novice in Unit Testing, but this sounds simple and powerfl solution.

  • Anonymous
    January 06, 2009
    The comment has been removed

  • Anonymous
    January 13, 2009
    Jag talar ofta med kunder och partners som vill använda vedertagna designmönster för att separera ansvaret

  • Anonymous
    January 19, 2009
    Hi Whut, Sorry it took some time for me to answer. I have given some more thought lately to the approach described in Andrew’s blog, and I believe it is worth pursuing. I have to mention, though, that in the first version of Entity Framework, there are enough limitations in LINQ to Entities that you may need to be more careful what kind of LINQ query you write. There are some limitations that are not shared by Entity SQL (i.e. the case with date arithmetic not being accessible from LINQ to Entities) and in those cases it might make sense to write a separate parameterized method that will compose the first part of the query in Entity SQL and then return the IQueryable<T> so that you can continue composing it in LINQ. Hope this helps, Diego

  • Anonymous
    January 23, 2010
    Unit Testing using a mocking framework isn't possible when using the Entity Framework. I'm disappointed to learn that EF is not viable for production level applications. If you were building an application that would be at the core of your business, what .Net based ORM would you recommend using? Due to the size and complexity of the application, the team distribution, etc., it must be unit testable; e.g., mock-friendly. Thanks, Jason

  • Anonymous
    January 24, 2010
    Jason, It is interesting that you say that. What makes you think that you cannot use a mocking framework with EF? I wrote this post a long time ago, when we were still planning the second version of Entity Framework. There have been plenty of new articles about the subject since then, from members of the team and from other parties. Also, a new version of Entity Framework is now in beta (you can download it together with Visual Studio 2010 and .NET 4.0 beta 2 here: http://msdn.microsoft.com/en-us/vstudio/dd582936.aspx) that supports better separation of concerns and persistence ignorance. A new interface called IObjectSet was added to simplify the creation of test doubles (i.e. mocks, fakes, etc.) in some scenarios. Besides, even without much other help from EF than Persistence Ignorance support, defining an abstract repository can separate your data access code from the rest of your application and provides a good place to inject test doubles. I would certainly recommend learning more about Entity Framework, especially EF 4.0, and considering it when you decide what O/RM to use for your next project. Here is some recommended reading: How to use TypeMock to break dependencies and mock EF 3.5 SP1 http://mosesofegypt.net/post/Introducing-Entity-Framework-Unit-Testing-with-TypeMock-Isolator.aspx A Sneak Preview of the testability improvements in EF 4: http://blogs.msdn.com/adonet/archive/2009/05/18/sneak-preview-entity-framework-4-0-testability-improvements.aspx A walkthrough on how to use the new testability improvements in EF 4: http://blogs.msdn.com/adonet/pages/walkthrough-test-driven-development-with-the-entity-framework-4-0.aspx Thanks, Diego