Tested with:
.Net Framework 4.5
Entity Framework 6.2.0
Moq 4.13.1
MSTest 1.3.2
Github repository here.
This is a demo of how to write unit tests for Entity Framework.
This demo is based on Microsoft article.
It consists of testing EF query and non-query scenarios on both async and non-async calls.
The entity model looks like this.
The data access class look like this.
public class InfinityDataAccess
{
private readonly InfinityEntities dbContext;
public InfinityDataAccess(InfinityEntities dbContext)
{
this.dbContext = dbContext;
}
public IEnumerable<Misc> GetMisc()
{
using (this.dbContext)
{
return this.dbContext.Misc
.Select(o => o)
.ToList();
}
}
public async Task<IEnumerable<Misc>> GetMiscAsync()
{
using (this.dbContext)
{
return await this.dbContext.Misc
.Select(o => o)
.ToListAsync();
}
}
public bool InsertMisc(string data, string description)
{
try
{
using (this.dbContext)
{
var newModel = new Misc
{
Data = data,
Description = description
};
this.dbContext.Misc.Add(newModel);
this.dbContext.SaveChanges();
return true;
}
}
catch (Exception ex)
{
return false;
}
}
public async Task<bool> InsertMiscAsync(string data, string description)
{
try
{
using (this.dbContext)
{
var newModel = new Misc
{
Data = data,
Description = description
};
this.dbContext.Misc.Add(newModel);
await this.dbContext.SaveChangesAsync();
return true;
}
}
catch (Exception ex)
{
return false;
}
}
public bool UpdateMisc(int miscId, string data, string description)
{
try
{
using (this.dbContext)
{
var dataModel = this.dbContext.Misc.Find(miscId);
if (dataModel != null)
{
dataModel.Data = data;
dataModel.Description = description;
this.dbContext.SaveChanges();
}
return true;
}
}
catch (Exception ex)
{
return false;
}
}
public async Task<bool> UpdateMiscAsync(int miscId, string data, string description)
{
try
{
using (this.dbContext)
{
var dataModel = await this.dbContext.Misc.FindAsync(miscId);
if (dataModel != null)
{
dataModel.Data = data;
dataModel.Description = description;
await this.dbContext.SaveChangesAsync();
}
return true;
}
}
catch (Exception ex)
{
return false;
}
}
public bool DeleteMisc(int miscId)
{
try
{
using (this.dbContext)
{
var dataModel = this.dbContext.Misc.Find(miscId);
if (dataModel != null)
{
this.dbContext.Misc.Remove(dataModel);
this.dbContext.SaveChanges();
}
return true;
}
}
catch (Exception ex)
{
return false;
}
}
public async Task<bool> DeleteMiscAsync(int miscId)
{
try
{
using (this.dbContext)
{
var dataModel = await this.dbContext.Misc.FindAsync(miscId);
if (dataModel != null)
{
this.dbContext.Misc.Remove(dataModel);
await this.dbContext.SaveChangesAsync();
}
return true;
}
}
catch (Exception ex)
{
return false;
}
}
}
For query scenario on non-async call, it is pretty straight forward.
[TestMethod]
public void TestGetMisc()
{
// Data to be returned
var data = new List();
data.Add(new Misc { Data = "Data1", Description = "Description1" });
data.Add(new Misc { Data = "Data2", Description = "Description2" });
data.Add(new Misc { Data = "Data3", Description = "Description3" });
// Create mock data
var mockData = new Mock();
mockData.As().Setup(m => m.Provider).Returns(data.AsQueryable().Provider);
mockData.As().Setup(m => m.Expression).Returns(data.AsQueryable().Expression);
mockData.As().Setup(m => m.ElementType).Returns(data.AsQueryable().ElementType);
mockData.As().Setup(m => m.GetEnumerator()).Returns(data.AsQueryable().GetEnumerator());
// Create mock EF context
var mockContext = new Mock();
mockContext
.Setup(mock => mock.Misc)
.Returns(mockData.Object);
// Assert
var dataAccess = new InfinityDataAccess(mockContext.Object);
var result = dataAccess.GetMisc();
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count());
Assert.AreEqual("Description1", result.FirstOrDefault(o => o.Data == "Data1").Description);
}
The test for query scenario creates mock data of queried EF entity model and setup the mock’s Provider, Expression, ElementType and Enumerator. The return object of the setup are the correspondent properties of the returned data which is a type of IQueryable
(see // Create mock data
in the code above).
Next, the test creates mock of EF context. The mock is setup to return mock data we created earlier (see // Create mock EF context
in the code above).
For non-query scenario on non-async call, it is almost similar to query scenario.
[TestMethod]
public void TestInsertMisc()
{
// Data to be returned
var data = new Mock();
// Create mock EF context
var mockContext = new Mock();
mockContext
.Setup(mock => mock.Misc)
.Returns(data.Object);
// Assert
var dataAccess = new InfinityDataAccess(mockContext.Object);
var result = dataAccess.InsertMisc("UnitTestData1", "UnitTestDescription1");
Assert.IsTrue(result);
mockContext.Verify(m => m.Misc.Add(It.IsAny()), Times.Once);
mockContext.Verify(m => m.SaveChanges(), Times.Once);
}
The test creates mock data of EF entity model, but it is not necessary to setup the mock’s Provider, Expression, ElementType or Enumerator.
Next, the test creates mock of EF context (see // Create mock EF context
in the code above).
For async calls, there are additional helper classes we need to create.
These classes are in-memory query provider to process async requests. We use custom query provider because EF use LINQ extension methods for async calls and these methods are designed to be used only with EF. If we try to use them with our unit tests, it will throw errors.
The 3 classes we need to create are:
- DbAsyncQueryProvider
- DbAsyncEnumerable
- DbAsyncEnumerator
With these helper classes, we can create mock data capable of in-memory async calls. This is the generic method to create the mock data.
private Mock GenerateMockDbSet(IQueryable mockData)
where T : class
{
var mock = new Mock();
mock.As()
.Setup(x => x.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator(mockData.GetEnumerator()));
mock.As()
.Setup(x => x.Provider)
.Returns(new TestDbAsyncQueryProvider(mockData.Provider));
mock.As()
.Setup(x => x.Expression)
.Returns(mockData.Expression);
mock.As()
.Setup(x => x.ElementType)
.Returns(mockData.ElementType);
mock.As()
.Setup(x => x.ElementType)
.Returns(mockData.ElementType);
return mock;
}
So for query scenario on async call, we just call the above method to create mock data for us.
[TestMethod]
public async Task TestGetMiscAsync()
{
// Data to be returned
var data = new List();
data.Add(new Misc { Data = "Data1", Description = "Description1" });
data.Add(new Misc { Data = "Data2", Description = "Description2" });
data.Add(new Misc { Data = "Data3", Description = "Description3" });
// Create mock data
var mockData = GenerateMockDbSet(data.AsQueryable());
// Create mock EF context
var mockContext = new Mock();
mockContext
.Setup(mock => mock.Misc)
.Returns(mockData.Object);
// Assert
var dataAccess = new InfinityDataAccess(mockContext.Object);
var result = await dataAccess.GetMiscAsync();
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count());
Assert.AreEqual("Description1", result.FirstOrDefault(o => o.Data == "Data1").Description);
}
There are more sample tests on the github repository to demonstrate every single scenarios in both async and non-async calls.