Sneak Preview: Entity Framework 4.0 Testability Improvements

 


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.


 

Writing unit tests is a core practice in the vast majority of modern software development approaches. There are several benefits to it, but the one I personally tend to value the most is how I can take a component with enough unit test coverage and start refactoring it without worrying that I could accidentally introduce regressions.

With Test Driven Development and Unit Testing in general, it is critical that unit tests are lightning fast: in practice, you should run all tests for a component after applying each refactoring step, and that should not affect your rhythm. Whenever you feel the urge to skip the test runs, it is time to streamline your tests.

A well known way to accomplish this level of swiftness is to substitute the actual dependencies of the component being tested with test doubles. Test doubles expose the same API surface as the dependencies they impersonate, but instead of trying to emulate the often complex behavior of the real thing, they can limit themselves to provide canned responses or record the inputs received during the execution of the tests (which can help in verification even more).

When applied to the testing of the domain logic in an application using some O/RM technology, test doubles should ideally substitute the persistence layer. That way, unit tests typically never invoke the actual persistence framework, and more important, never in fact hit the database.

During the development Entity Framework 4.0, we spent a lot of time thinking how to make it easier to substitute Entity Framework in your unit tests. Here are a few improvements that we believe will make a difference on that front:

  1. POCO Support: As Faisal described in his post, you can now write your own entity classes that do not depend on Entity Framework, so you can instantiate them and use them more easily in your tests.
  2. Repository pattern: Repositories are the best known way to decouple the persistence layer from the rest of your application. While we won’t include or prescribe a particular variation of the pattern in the product, we have been working with Microsoft’s Patterns & Practices Team to produce new guidance and a Reference Implementation of this pattern (along with other patterns used Domain Driven Design) with Entity Framework. This exercise is helping us understand friction points and improvements we will hopefully address in this and future releases. Also, we will go deeper on this subject in a future blog post series.
  3. New IObjectSet<T> interface: the newObjectSet<T> class derives from ObjectQuery<T> and represents a “root” query object for an EntitySet, that also has AddObject, DeleteObject and Attach methods. In order to improve testability, we also defined the corresponding IObjectSet<T> interface that derives from IQueryable<T>. IObjectSet<T> happens to be super easy to implement as an in-memory fake object. Provided that your queries and CUD operations can refer to instances of IObjectSet<T>, your code will now be easier and faster to test.
  4. Template-based code generation: No matter what pattern variations you prefer for Repository, UnitOfWork, persistence contexts, or their interfaces and corresponding test doubles, if you think you might end up writing the same boilerplate code once and again, you are probably better off customizing one of our T4 templates to suit your testability needs.
  5. LINQ to Entities Improvements: Some popular approaches to testability involve redirecting LINQ queries that at runtime would be executed against a database to be evaluated in-memory (using LINQ to Objects) during the execution of unit tests.  For those cases, the closer the behaviors of both LINQ implementations are, the lower the odds are that query bugs will go undetected until integration tests are executed. We have made several improvements in LINQ to Entities that we believe will help here:

a. Support for more LINQ operators, such as Contains, DefaultIfEmtpty, and improved support for Single/SingleOrDefault.

b. More translations for common Base Class Library patterns including various Math class methods, Guid.NewGuid, etc (some of this work is not included in Beta1).

c. Improved preservation of nested OrderBy operations.

d. Several bug fixes that further align the behavior of LINQ to Entities with LINQ to Objects.

The following sample code shows a very simple way to create and use a fake implementation of an ObjectContext that exposes properties of type IObjectSet<T>:

 

 /// <summary>
/// Data access context interface for SimpleBlogging 
/// </summary>
public interface 

ISimpleBlogging

  :

IDisposable

 {
    

IObjectSet<Blog

 > Blogs { 

get

 ; }
    

IObjectSet<Post

 > Posts { 

get

 ; }
}


/// <summary>
/// Fake data access context for SimpleBlogging
/// </summary>
public class 

SimpleBloggingFakeContext : ISimpleBlogging

 {
    private 

FakeObjectSet<Blog

 > _blogs;

    public 

IObjectSet<Blog

 > Blogs
    {
        

get

  { return _blogs ?? (_blogs = new 

FakeObjectSet<Blog

 >()); }
    }

    private 

FakeObjectSet<Post

 > _posts;

    public 

IObjectSet<Post

 > Posts
    {
        get { return _posts ?? (_posts = new 

FakeObjectSet<Post

 >()); }
    }

    public void Dispose()
    {
    }
}

/// <summary>
/// Utility factory method for contexts
/// </summary>
private static 

ISimpleBlogging

  GetContext()
{
    

ISimpleBlogging

  context = new 

SimpleBloggingFakeContext

 ();
    InitializeWithCannedData(context);
    return context;
}
[

TestMethod

 ]
public void TestAddPost()
{
    using (

ISimpleBlogging

  context = GetContext())
    {
        var blogging = new BloggingService(context);
        var newPost = blogging.AddPost(
            "Diego",
            "Hiatus", 
            "This is my last post for a while.");
        var blog = context.Blogs.Single(b => b.ID == "Diego");
        

Assert

 .AreSame(newPost.Blog, blog);
    }
}

Stay tuned for a more in depth discussion of testability improvements, as well as other topics mentioned in this post.

Thanks,

Diego Vega
Program Manager, Entity Framework