Introduction
In TDD, it is important to write your unit tests first to test code logic without relying on a database. Even later in the development life cycle, maintaining a DB just for unit testing could be both cumbersome and erroneous. In addition, having a DB for automated unit testing with a CI (continuous integrated) build process may introduce unnecessary complexity. This article provides an example to show how to mock test an EF Model-First project using ADO.NET Entity Data Model template to strengthen and simplify your unit test implementation.
Background
My current project uses .NET 4 + EF 4 with generated code from the ADO.NET Entity Data Model template (.edmx). In my research of a good way to mock test the code, I find most of the examples available are focused on Code First/POCO projects with very few mentioning Model-First ones. This is probably due to the generated code in Designer.cs and its lack of flexibility as a result of some oversights from MS to enable code to be mock tested.
Unfortunately, to overcome these problems means we'll need to make some changes to the generated code. However, since these changes are relatively easy to make, a script could be written to alleviate the pain of this repetitive task every time after you've updated the model.
To Get Started
Let's first have a very simple data model defined:
Then let's create an .edmx file based on this model:
Now you should have added the .edmx file and its generated code to your project. Let's take a look at the generated code and understand what the problems are.
Open up MyModel.Designer.cs and go to the class declaration and you should see a line like this:
public partial class MyEntities : ObjectContext
The first problem is that this class inherits from ObjectContext
by default. ObjectContext
has an internal constructor which establishes a DB connection based on the connection string. Since this is an internal constructor, it can't be overridden by using either a partial
class or defining a common interface
. Hence, if we want to use an interface
for our mock entity base class, we'll have to modify this generated code.
Next, the Customers
entity collection is declared as:
public ObjectSet<Customer> Customers
Since ObjectSet<T>
is a concrete class, we'll need to change it to an interface
that ObjectSet<T>
implements in order to be able to mock it. In summary, we need to make the following changes to MyModel.Designer.cs:
- Replace inheritance from
ObjectContext
to the common interface
you define (explained below) - Replace all
ObjectSet<T>
to IObjectSet<T>
Now that shouldn't be so bad. Let's move on to how we code our mock entity and interface
.
Polymorphism is Your Friend
In order to have a mock entity class that has the same set of methods and be able to use it to do mock test code, we need a common interface
defined for both the real and mock entity class.
public interface IMyEntities
{
IObjectSet<Customer> Customers { get; }
IObjectSet<Order> Orders { get; }
int SaveChanges();
}
Once we have this interface
defined, we can have both of our real and mock entity class implement it. Here's what the mock entity class looks like:
public class MyEntitiesMock : IMyEntities
{
private IObjectSet<Customer> customers;
private IObjectSet<Order> orders;
public IObjectSet<Customer> Customers
{
get { return customers ?? (customers = new MockObjectSet<Customer>()); }
}
public IObjectSet<Order> Orders
{
get { return orders ?? (orders = new MockObjectSet<Order>()); }
}
public int SaveChanges()
{
return 0;
}
}
Note that since both the real and mock entity class should implement the same IMyEntities interface
, we'll need to do the first change aforementioned in the generated code in MyModel.Designer.cs:
public partial class MyEntities : IMyEntities
In MyEntitiesMock
, both public
property Customers
and Orders
return an instance of MockObjectSet<T>
, which we will explain later. Notice the method SaveChanges()
really doesn't do anything except returning 0
. You can have other skeleton methods here doing the same thing to mock those supported by EF.
The MockObjectSet<T>
class has to implement all the methods defined in IObjectSet
to provide the same functionality. Here's what it looks like:
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Data.Objects;
namespace MyApplication
{
public partial class MockObjectSet<T> : IObjectSet<T> where T : class
{
private readonly IList<T> collection = new List<T>();
#region IObjectSet<T> Members
public void AddObject(T entity)
{
collection.Add(entity);
}
public void Attach(T entity)
{
collection.Add(entity);
}
public void DeleteObject(T entity)
{
collection.Remove(entity);
}
public void Detach(T entity)
{
collection.Remove(entity);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return collection.GetEnumerator();
}
#endregion
#region IQueryable<T> Members
public Type ElementType
{
get { return typeof(T); }
}
public System.Linq.Expressions.Expression Expression
{
get { return collection.AsQueryable<T>().Expression; }
}
public IQueryProvider Provider
{
get { return collection.AsQueryable<T>().Provider; }
}
#endregion
}
}
The last thing we'll need is a context container to instantiate and return the proper object context. Again, we need to define a common interface
so we can have two different container classes, each with its own behavior.
The context container, along with the real context class looks like this:
namespace MyApplication
{
public interface IContextContainer
{
IMyEntities Current { get; }
}
public class ContextContainer : IContextContainer
{
private static readonly IMyEntities objectContext = new MyEntities();
public IMyEntities Current
{
get { return objectContext; }
}
}
}
The mock context container implements the same interface
and looks like this:
namespace MyApplication
{
public class ContextContainerMock : IContextContainer
{
private static readonly IMyEntities context = new MyEntitiesMock();
public IMyEntities Curren
{
get { return context; }
}
}
}
Now we have all the basics, let's move on to see how we use them.
Service and Entity Class
In my design, I have a service and data layer. The former consists of all the business logic while the latter is responsible solely for data access. This design allows me to have decoupled layers which can be easily scaled and maintained. This is what the CustomerService
class looks like:
using System.Data.Objects;
namespace MyApplication
{
class CustomerService
{
protected readonly IContextContainer container;
public CustomerService(IContextContainer container)
{
this.container = container;
}
public Customer GetCustomer(int id)
{
var customer = new Customer(this.container);
return customer.Select(id);
}
}
}
As you can see, the GetCustomer
method could perform some business logic, then call the Select()
method from the entity class, which looks like:
public partial class Customer
{
private readonly IContextContainer container;
public Customer()
{
}
public Customer(IContextContainer container)
{
this.container = container;
}
public Customer Select(int id)
{
return this.container.Current.Customers.FirstOrDefault(x =>x.ID == id);
}
}
Note that it's declared as a partial
class since we have another partial
class of Customer
from the generated code. The Select()
method here is simply EF magic; i.e., going to the data source, getting the data then mapping it to a Customer
object to return.
Now we have everything in our project, let's see how we're going to unit test the GetCustomer()
method in our service class.
Unit Test
In our unit test code, all we have to do is to use the ContextContainerMock
to return a mock entity class that implements IMyEntities
. Then, we can call/test those methods as we wish:
[TestMethod]
public void GetCustomer()
{
ContextContainerMock container = new ContextContainerMock();
IMyEntities en = container.Current;
Customer c = new Customer { ID = 1, FirstName = "John", LastName = "Doe" };
en.Customers.AddObject(c);
CustomerService service = new CustomerService(container);
var a = service.GetCustomer(1);
Assert.AreEqual(c.FirstName, a.FirstName);
Assert.AreEqual(c.LastName, a.LastName);
}
What About Methods that Call Stored Procedures?
If your methods use stored procedures and function import in the EF model, you can still mock test them by applying one more change to the generated code.
Suppose you have a stored procedure called GetCustomers
which simply returns all customer rows in the Customer
table. Once you include the stored procedure in your EF model, complete the function import part and save the model you should have generated code like this:
public ObjectResult<Customer> GetCustomers()
{
return base.ExecuteFunction<Customer>("GetCustomers");
}
You'll need to change the returned type from ObjectResult<T>
to IEnumerable<T>
so you can declare this method in the IMyEntities interface
:
public interface IMyEntities
{
IObjectSet<Customer> Customers { get; }
IObjectSet<Order> Orders { get; }
IEnumerable<Customer> GetCustomers();
int SaveChanges();
}
Then you can add the code in MyEntitiesMock
when this method is called from your mock test. Basically, it just returns a new instance of MockObjectSet<GetCustomersResult>
:
public IEnumerable<GetCustomersResult> GetCustomers
{
get { return customers ?? (customers = new MockObjectSet<GetCustomersResult>()); }
}
Finally, in your mock unit test, you'll do something like:
[TestMethod]
public void GetCustomers()
{
ContextContainerMock container = new ContextContainerMock();
IMyEntities en = container.Current;
GetCustomersResult c1 = new GetCustomersResult
{ ID = 1, FirstName = "John", LastName = "Doe" };
GetCustomersResult c2 = new GetCustomersResult
{ ID = 2, FirstName = "Mary", LastName = "Doe" };
en.GetCustomersResult.AddObject(c1);
en.GetCustomersResult.AddObject(c2);
CustomerService service = new CustomerService(container);
var a = service.GetCustomers();
Assert.AreEqual(c1.FirstName, a[0].FirstName);
Assert.AreEqual(c2.FirstName, a[1].FirstName);
}
Summary
In this article, we see how we could mock test an EF Model-First project with some minor changes to the generated code from the ADO.NET Entity Data Model template. I think it's a small price to pay to be able to do your mock tests. More importantly, it allows you to easily automate your unit tests with a CI build process without worrying about having and maintaining a DB instance just for those tests. After all, the purpose of your unit testing should be validating your logic, not data.