We needed to fake or mock out Entity Framework so that we could test our “service layer” that holds our business logic without hitting a real database. We are using EF as our Repository and skipping all the extra work in creating a repository code layer that only wraps EF. We are ok with being this closely tied to EF.
It was difficult to figure out how to fake the context with an interface we made ourselves. We found some helpful Nuget packages, so I decided to share it.
EntityFramework.Testing:
EntityFramework Testing
EntityFramework.Testing.FakeItEasy:
EntityFrameworkTesting.FakeItEasy NuGet package
Looking at the project site for EntityFramework.Testing, they have some sample code for FakeItEasy as well as other mocking frameworks.
EntityFramework.Testing Project site
EntityFramework.Testing.FakeItEasy provides a helpful extension method to mock EntityFramework's DbSets using FakeItEasy.
This also supports Moq and other libraries.
Sample code using it:
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using Acme.Data;
using Acme.Models;
using FakeItEasy;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Acme.Services.Tests
{
[TestClass]
public class ProductServiceTests
{
[TestMethod]
[TestCategory("Product Service Get")]
public void Get_All_ReturnsExpected()
{
// Create test data
var testData = new List<Product>
{
new Product {ProductId = 1, Name = "Product 1", Description = "this is a description", Active = true},
new Product {ProductId = 2, Name = "Product 2", Description = "this is a description", Active = true},
new Product {ProductId = 3, Name = "Product 3", Description = "this is a description", Active = false},
new Product {ProductId = 4, Name = "Product 4", Description = "this is a description", Active = true},
};
// Arrange
var set = A.Fake<DbSet<Product>>(o => o.Implements(typeof(IQueryable<Product>)).Implements(typeof(IDbAsyncEnumerable<Product>)))
.SetupData(testData);
var context = A.Fake<AcmeContext>();
A.CallTo(() => context.Products).Returns(set);
var productService = new ProductService(context);
// Act
var products = productService.GetAll().ToList();
// Assert
Assert.AreEqual(4, products.Count(), "Should have 4");
Assert.AreEqual(1, products.First().ProductId, "Should be 1");
}
[TestMethod]
[TestCategory("Product Service Get")]
public void Get_Active_OnlyReturnsActive()
{
// Create test data
var testData = new List<Product>
{
new Product {ProductId = 1, Name = "Product 1", Description = "this is a description", Active = true},
new Product {ProductId = 2, Name = "Product 2", Description = "this is a description", Active = true},
new Product {ProductId = 3, Name = "Product 3", Description = "this is a description", Active = false},
new Product {ProductId = 4, Name = "Product 4", Description = "this is a description", Active = true},
};
// Arrange
var set = A.Fake<DbSet<Product>>(o => o.Implements(typeof(IQueryable<Product>)).Implements(typeof(IDbAsyncEnumerable<Product>)))
.SetupData(testData);
var context = A.Fake<AcmeContext>();
A.CallTo(() => context.Products).Returns(set);
var productService = new ProductService(context);
// Act
var products = productService.GetActiveProducts().ToList();
// Assert
Assert.AreEqual(3, products.Count(), "Should have 3");
Assert.AreEqual(4, products.Last().ProductId, "Should be 4");
Assert.IsFalse(products.Any(x =>x.Active == false), "None should be active");
}
}
}
Here’s an alternative a co-worker had created before the NuGet package was discovered.
The test:
[TestMethod]
[TestCategory("Product Service Get")]
public void Get_All_ReturnsExpected()
{
// Arrange
var contextFaker = new ContextFaker();
contextFaker.Products.AddRange(new List<Product>
{
new Product { ProductId = 1, Name="Product 1", Description = "this is a description", Active = true},
new Product { ProductId = 2, Name="Product 2", Description = "this is a description", Active = true},
new Product { ProductId = 3, Name="Product 3", Description = "this is a description", Active = false},
new Product { ProductId = 4, Name="Product 4", Description = "this is a description", Active = true},
}));
var productService = new ProductService(contextFaker.FakeContext);
// Act
var products = productService.GetAll().ToList();
// Assert
Assert.AreEqual(4, products.Count(), "Should have 4");
Assert.AreEqual(1, products.First().ProductId, "Should be 1");
}
The helper code:
public class ContextFaker
{
public List<Product> Products = new List<Product>();
private IAcmeContext _fakeContext;
public IAcmeContext FakeContext
{
get
{
A.CallTo(() => _fakeContext.Products).Returns(ListFaker<Product>.GetFake(Products));
return _fakeContext;
}
set { _fakeContext = value; }
}
public ContextFaker()
{
_fakeContext = A.Fake<IAcmeContext>();
}
}
public static class ListFaker<T> where T : class
{
public static DbSet<T> GetFake(List<T> data)
{
var dataAsQueryable = data.AsQueryable();
var fakeDbSet = A.Fake<DbSet<T>>(b => b.Implements(typeof(IQueryable<T>)));
A.CallTo(() => ((IQueryable<T>)fakeDbSet).GetEnumerator()).Returns(dataAsQueryable.GetEnumerator());
A.CallTo(() => ((IQueryable<T>)fakeDbSet).Provider).Returns(dataAsQueryable.Provider);
A.CallTo(() => ((IQueryable<T>)fakeDbSet).Expression).Returns(dataAsQueryable.Expression);
A.CallTo(() => ((IQueryable<T>)fakeDbSet).ElementType).Returns(dataAsQueryable.ElementType);
return fakeDbSet;
}
}