Moles and Linq to SQL: Mocking Linq to SQL Behavior
In recent years there’s been major investments in developing tools in order to provide good quality assurance features and incorporate industry practices into them.
TDD, DDD, Unit Testing, Continuous Integration, etc.
Microsoft has recently (not so recently actually :)) ship some very nice features to help us improve our code quality by allowing us to create better tests over units of code.
Unit testing is all about proving individual units of code (positive and negative testing). Since a great deal of software is not always designed with this “unit independence” in mind, it can be a challenge to isolate dependencies for every piece of source code (specially legacy code).
Moles is an Isolation Framework that mocks/emulates the behavior of our components and external libraries, even for private members. You could even mock how a particular System.* class object in .NET behaves (check Pex & Moles site in Microsoft Research).
The following is a Repository object that uses Linq to Sql Data Context internally and doesn't provide a simple way of decoupling database access from its behavior. It means we cannot unit test the following class without a SQL database.
{
public IList<Customer> GetCustomers()
{
using (var db = new AdventureWorksDataContext())
{
return db.Customers.ToList();
}
}
public Customer GetCustomer(int id)
{
using (var db = new AdventureWorksDataContext())
{
return db.Customers.FirstOrDefault(c => c.CustomerID == id);
}
}
}
By using Moles, we could replace inner behavior of this class.
Note: This is White Box Testing, meaning you need to have access to internal implementation in order to know how to mock it.
Mocking with Moles
Download and install Moles.
- Add Mole Assemblies for the project hosting the previous class (or the code you wish to test)
- Add Mole Assembly for System.Data.Linq
- Add Mole Assembly for System.Core
Check this tutorial about Pex & Moles for more information about the previous steps.
The following code fragment unit test our GetCustomers method:
[HostType("Moles")]
public void GetCustomersTest()
{
System.Data.Linq.Moles.MTable<Customer>.AllInstances.GetEnumerator =
tc =>
{
var mockCustomers = new List<Customer>()
{
new Customer() { FirstName = "Javier" },
new Customer() { FirstName = "Jorge" },
new Customer() { FirstName = "Dario" }
};
return mockCustomers.GetEnumerator();
};
CustomerRepository target = new CustomerRepository();
var customers = target.GetCustomers();
Assert.IsNotNull(customers);
Assert.IsTrue(customers.Count == 3);
Assert.AreEqual("Javier", customers[0].FirstName);
}
Check out the use of Moles to specify the behavior of enumeration for the Table<Customer> (Customers) that Linq to Sql Provider uses to execute the query in the database. In the previous code, we avoid querying data from database and return an in-memory list of customers.
When executing a lambda expression using Linq to Sql, the source Linq provider must interpret the query and then issue a command to the database.
In order to intercept the query and return mock results, we must override default behavior for the expression evaluation logic of the Linq Provider.
[HostType("Moles")]
public void GetCustomerTest()
{
var expectedCustomer = new Customer() { FirstName = "Javier", CustomerID = 1 };
System.Data.Linq.Moles.MTable<Customer>.AllInstances.ProviderSystemLinqIQueryableget =
tc =>
{
var qp = new System.Linq.Moles.SIQueryProvider();
qp.ExecuteExpression01(exp => expectedCustomer);
return qp;
};
CustomerRepository target = new CustomerRepository();
var actualCustomer = target.GetCustomer(expectedCustomer.CustomerID);
Assert.IsNotNull(actualCustomer);
Assert.AreEqual(expectedCustomer.CustomerID, actualCustomer.CustomerID);
Assert.AreEqual(expectedCustomer.FirstName, actualCustomer.FirstName);
}
The previous mocking code returns a Mock QueryProvider that does not evaluate the expression, it simply returns the in-memory customer reference.
I encourage you that read more about Moles here.
PS: This cool framework intercepts any managed operation by creating a CLR Host for the application.