A way to unit test data access logic when using the Entity Framework.
I've spent a lot of time wondering about the best way to unit test my data logic when my filtering and paging logic is in code, such as when using the Entity Framework, and I finally decided on a strategy that I find "good enough".
When I started breaking down what it actually meant to use the Entity Framework, It came to me that what I really was doing was just using LINQ. The LINQ query just happened to be run against a database. After coming to that conclusion, which is correct for the most part, I started working on a way to unit test my LINQ-to-EF queries.
Please note, that when I talk about unit tests, I am actually talking about proper unit tests, that is a test that purely tests the logic, but doesn't go all the way down into the database, which requires me to create mocks for the actual data. So I decided upon a structure for my code that goes something like the following.
Taking advantage of IQueryable
The actual logic of fetching the raw data, or references to that data, is contained in storage classes. They should not perform any logic other than doing the data access itself. So no paging, no filtering, no projections, just pure data access. Then I create business classes which contain the actual logic used to manipulate the data, which in our case are LINQ queries, which are performed on data returned from our storage class. The business logic classes are then created with construction injection in mind. Knowing how the entity Framework works by using it's expression trees I soon realized that the IQueryable<T> interface was something that could help me out.
Let's see an example. Let's say we have an entity model which contains a User entity, containing Id, Name, Email and IsDeleted properties. Our storage interface and default implementation then look like this.
public interface IDataStorage
{
IQueryable<User> GetUsers();
}
public class DataStorage : IDataStorage
{
private OurDataEntities entities = new OurDataEntities();
public IQueryable<User> GetUsers()
{
return this.entities.Users;
}
}
This looks simple enough, and provides us with most of what we need. We can see how we have made our actual data source mockable by simply referring to the entity collections as IQueryable collections. Now let's look at what our unit testable business class and interface look like:
public interface IDataBusiness
{
IQueryable<User> GetValidUsers();
IQueryable<User> SearchByEmail(string searchQuery, int? numResults = null);
}
public class DataBusiness : IDataBusiness
{
private readonly IDataStorage storage;
public DataBusiness(IDataStorage customStorage = null)
{
this.storage = customStorage ?? new DataStorage();
}
public IQueryable<User> GetValidUsers()
{
return this.storage.GetUsers().Where(user => user.IsDeleted == false);
}
public IQueryable<User> SearchByEmail(string searchQuery, int? numResults = null)
{
var users = this.GetValidUsers().Where(user => user.Email.StartsWith(searchQuery));
if(numResults.HasValue)
{
users = users.Take(numResults.Value);
}
return users;
}
}
Seems simple enough. It's simple, takes advantage of LINQ composition and is quite unit testable. Next, let's look at a simple unit test (note that I'm using the excellent Moq framework for mocking)
The unit tests
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var mock = new Mock<IDataStorage>();
mock.Setup(x => x.GetUsers()).Returns(new[]
{
new User
{
Id = 1,
Name = "Stefán Jökull Sigurðarson",
Email = "[email protected]",
IsDeleted = false
},
new User
{
Id = 2,
Name = "Stefán Jökull Sigurðarson 2",
Email = "[email protected]",
IsDeleted = false
},
new User
{
Id = 3,
Name = "Stefán Jökull Sigurðarson",
Email = "[email protected]",
IsDeleted = true
}
}.AsQueryable());
IDataBusiness business = new DataBusiness(mock.Object);
var validUsers = business.GetValidUsers();
Assert.AreEqual(2, validUsers.Count());
Assert.IsTrue(validUsers.All(user => user.IsDeleted == false));
var findUsersBySearch = business.SearchByEmail("some");
Assert.AreEqual(2, findUsersBySearch.Count());
var findUsersBySearchOnlyreturnsOne = business.SearchByEmail("some", 1);
Assert.AreEqual(1, findUsersBySearchOnlyreturnsOne.Count());
}
}
And there you have it. Now we can verify that our logic is doing exactly what we think it is doing. And to completely tie this all together, we could also create integration tests, that use the unmocked storage implementation and run queries against an actual database populated with test data to make sure that the Entity Framework is also behaving like we expect. That also helps out to weed out any usage of unsupported LINQ operators.
Hope this helps :)