Unittesting ADO.NET Entity Data Models

by Rudi17. September 2012 23:59

Entiy Framework 4.0 claimed to be unittestable. Unfortunately, that was only the case for ‘code first’ usage. The "ADO.NET Entity Data Model" way of using Entity Framework (with a drawn entity model) was still not unittestable (without a powerful IL-rewriting mocking tool).

In this article I’ll explain what has to be done in order to make entity data models mockable and unittestable.

The article comes with a sample solution containing an entity model, some code around it, and a (small) battery of unittests.

Unittesting Entity Framework Sample
UnittestingEntityFrameworkSample.zip (40.60 kb)

Note: this article and the delivered code is written towards Entity Framework 4.0.

Creating the model

Before unittesting a model, of course, we need to have a model. Either take an entity model you already have, or create one by adding an "ADO.NET Entity Data Model" to your project, then either generate a model from database, or start with an empty model to design the model yourself.

Making the model testable/mockable

The problem with the code generated by default by the ADO.NET Entity Data Model Designer, is that it does not allow unittesting the generated ObjectContext (without a living backend database). The reason for that is partially because the generated ObjectContext does not define an interface that allows mocking the context.

The solution is however easy as the Entity Data Model Designer allows using a custom code generation template (see: http://msdn.microsoft.com/en-us/library/dd456821.aspx).

First, and if you have not yet done so, install the "EF 4.x EntityObject Generator for C#" from within Visual Studio’s Extension Manager, or by downloading and installing from the following link:

http://visualstudiogallery.msdn.microsoft.com/e6db6554-345c-477a-9a73-3c5db06e9081

Next, on your Entity Model, right-click and choose "Add Code Generation Item...":

Then choose for the "ADO.NET Testable EntityObject Generator":

Finally, check the properties of your entity model: the Code Generation Strategy property should be "None":

Congratulations, you are now ready to... begin make your context testable!

What the code generation template did, was change the generation of your context class to also define a context interface, and to make the context class implement that interface.

The default Entity Data Model Designer code generation template generates something as:

public partial class SomeContext : ObjectContext
{
    public ObjectSet<Customer> Customers ...
    ...
}
 

The "EF 4.x EntityObject Generator for C#" generation template changed this into something as:

public partial interface ISomeContext
{
    public IObjectSet<Customer> Customers;
    ...
}
 
public partial class SomeContext : ObjectContext, ISomeContext
{
    public IObjectSet<Customer> Customers ...
    ...
}
 

So it added an ISomeContext interface and made SomeContext implement that interface. In addition, the entity set members are of type IObjectSet<T> instead of ObjectSet<T>, an important change!

This is a good step forward towards a testable context as the context now relies more on interfaces. But there’s still one missing interface: IObjectContext. The IObjectContext interface is not defined in the .NET framework and so we need to create it ourself. In the enclosed solution you will find the IObjectContext interface in the "Library" project, in the "System.Data.Objects" folder/namespace.

As the IObjectContext is not part of the .NET framework, I didn’t want the code generated by the "EF 4.x EntityObject Generator for C#" template to rely on that interface as it would create an additional dependency on an external library and limit the usability of that template.

Fortunately, thanks to partial classes you can now easily ensure the SomeContext class implements IObjectContext by adding following code (in the enclosed solution this is done in the "Model.cs" file of the "Sample.Domain" project):

partial interface ISomeContext : IObjectContext
{ }
 

(You could also have declared the class SomeContext to implement IObjectContext instead, both ways are good.)

We now have our context inheriting ObjectContext, and implementing a custom context interface that inherits from a custom IObjectContext: our context class is now the implementation of an interface and becomes subject to mocking:

Mocking the objectcontext

We can now start coding (provided we rely on the interfaces to ensure the code is unittestable) and write unittests to test our code.
Within the unittests, we will use a mock of the context. The mock class still needs to be implemented, but the Library project of the enclosed solution provides you a helpful base class: MockObjectContext. Using this class, we can now write a mock context class as follows:

public class MockSomeContext : MockObjectContext, ISomeContext
{
    public MockSomeContext()
        : base(new SomeContext())
    { }
 
    public IObjectSet<Customer> Customers
    {
        get { return this.GetObjectSet<Customer>(); }
    }
    ...
}
 

The context mock is basically a class inheriting from MockObjectContext and implementing our context interface. Thanks to the GetObjectSet<T>() method of its base class, implementing the context interface is easy.

One noteworthy remark: the constructor passes a real context instance to it’s base constructor! The reason for this is that the context mock uses a real context behind the scenes. This ensures that the expected behavior of an object context remains guaranteed.

(You could eventually think of creating a .tt text template to create mock contexts based on the real context class.)

In the enclosed sample I have added the following code to the context mock class:

    #region MockObjectContext overrides
 
    private int nextId = 0;
 
    protected override void WhenSavingAddedEntity(object entity)
    {
        var idProp = entity.GetType().GetProperty("Id");
        if (idProp != null && idProp.CanWrite)
        {
            idProp.SetValue(entity, --nextId, null);
        }
    }
 
    protected override void WhenSavingAddedRelationship()
    {
    }
 
    #endregion
 

This provides every entity with a unique "Id" value (as I’m using primary key int Id values on my model). I make the values negative to avoid conflicts with id values set explicitely. If you always set primary keys explicitely, you could leave both methods empty.

Writing unittests

We are now ready to write unittests!

One of the tests in the enclosed solution is the following:

[TestClass]
public class StockManagerTest
{
    [TestMethod]
    public void DoNotCreateNewLineInOrderWhenArticleAlreadyPresentTest()
    {
        using (var context = new MockStockContext())
        {
            #region Test Setup
            context.Articles.AddObject(new Article()
                  { Id = 1, Name = "a", StockCount = 0, ReorderTarget = 0 });
            context.Orders.AddObject(new Order()
                  { Id = 1 });
            context.OrderLines.AddObject(new OrderLine()
                  { OrderId = 1, ArticleId = 1, Quantity = 12 });
            context.ResetForTest();
            #endregion
 
            // Test:
            var manager = new StockManager() { Context = context };
            manager.AddToOrder(1, 1, 4);
 
            // Assert:
            Assert.AreEqual(1, context.OrderLines.First().OrderId);
            Assert.AreEqual(1, context.OrderLines.First().ArticleId);
            Assert.AreEqual(16, context.OrderLines.First().Quantity, 
               "OrderLine should now have 16 items");
            context.AssertSavedChangesOfType<OrderLine>(0, 1, 0);
            context.AssertSavedChangesOfAnyType(0, 1, 0);
        }
    }
}
 

In the "Test Setup" region, entities are added to the context to create an initial situation. In this case, an Article, Order and OrderLine entity are added. The entities are also related to each other (the ArticleId of the OrderLine is the Id of the Article).
Once the context contains all entities needed to be able to start the test, the "ResetForTest()" method of the mock context should be called. This will reset all status counters, telling that the context contains no changes.

Next, perform the test. The test creates a StockManager, then tries to add 4 pieces of an article already present on the given order. The idea is that when an orderline already exists for that article, no new orderline is created, but the existing orderline is altered.

And that is exactly what is asserted in the "Assert" section of the test.

The "context.AssertSavedChangesOfType<OrderLine>(0, 1, 0)" line asserts that of the entity type OrderLine, none was added, none was deleted, and exactly one was modified.

The AssertSavedChangesOfType<T>() method of MockObjectContext has the following signature:
public void AssertSavedChangesOfType<T>(int? additions, int? modifications, int? deletions)

In addition, the "context.AssertSavedChangesOfAnyType(0, 1, 0)" does the same check, but over all entity types. In other words, this line checks that of all entities, only one was modified, none added and none deleted.

When you write a test that should not impact the database, you could end the test with the assertion "context.AssertNoSavedChanges()", which asserts that nothing was saved by the context.

Unittests with (MEF) composition

It is also possible to have MEF or another dependency injection framework compose the objects for the unittest. I’ve included a "ComposedStockManagerTest" class in the test project to demonstrate that.

About the enclosed solution

The enclosed solution contains the following elements:

A database script to recreate the database used in the sample (although, running the unittests do not require a life database, you’ll only need the database if you want to run the sample console application or update the entity model.

A "Library" project containing the IObjectContext interface as well as the MockObjectContext base class.

A "Sample.ConsoleClient" project, just to proof the code also works outside unittests.

A "Sample.Domain" project, that’s where the entity model resides !

A "Sample.Domain.Test" project, that’s where the MockStockContext class and the unittests reside !

Conclusions

Once setup, it is possible to write unittest for code that relies on Entity Framework data models and even perform assertions on the interaction with the Entity Framework object context. The unittests are straight-forward, easy to read and write. Because the MockObjectContexts uses a real ObjectContext behind the scenes, the behavior of an ObjectContext (especially towards associations) is still present (if you set the customer property of an order, the orders property of that customer will contain that order).

Tags:

Development practice | Entity Framework | Microsoft .NET | Unit testing

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About me

Widget Month List not found.

The file '/blog/widgets/Month List/widget.ascx' does not exist.X

Widget Page List not found.

The file '/blog/widgets/Page List/widget.ascx' does not exist.X