Jump to a Section:
Convert from List<T> to DbSet<T>
Initialise the auto mocking kernel
Create the test data (for test when Case has no Creditors)
Create the mock DbContext object and populate with test data
Create the target Method's concrete Class instance using DI/auto mocking
Test the call to the concrete method
Create the test for when a Case has a Creditor
The alternative
General notes
Version information for the technologies in use
I found myself needing (wanting?) to create Unit Tests for methods that execute non-trivial LINQ queries within an application that uses Dependency Injection.
Before we even get started, we should pause and examine what it is we think we are trying to achieve. We want to be able to call a method that performs a LINQ query on some DbSet
The specifics of my application are that it is using an "Entity Framework"-like ORM and Dependency Injection via Ninject. My Unit Tests introduce Moq and the auto-mocking Ninject extension Ninject.MockingKernel.Moq.
I want to unit test a non-trivial LINQ query in a method in a concrete Class:
public IEnumerable<Creditor> GetCaseCreditors(Case oCase)
{
var caseCreditors = from c in this.dbContext.Cases
where c.DebtorCaseRef == oCase.DebtorCaseRef
from cc in c.CaseCreditors
select cc.Creditor;
return caseCreditors.ToList();
}Further, the above method is in a Class constructed using DI, and whose Constructor signature is as follows:
public CaseProviderDb(
IDbContext dbContext,
ICachedEntities cachedEntities)
{
this.dbContext = dbContext;
this.entities = cachedEntities;
}
Working backwards from the method execution we can see that we need to:
- Create some Case test data (for Cases both with and without Creditors)
- Ensure the containing concrete Class' constructor is satisfied
- Ensure the created Case test data is used when the target method's LINQ query executes. This is where the main effort is required - to mock the dbContext and ensure its Cases property returns our test data
Convert from List<T> to DbSet<T>
The first problem we have is that the target Cases property is of type IDbSet<Case> - so we need a test data structure compatible with the target data type. The easiest format for in-memory data creation is a typed List, so we need to get from List<Case> to IDbSet<Case>.
Thankfully an MSDN article has the answer. In the article we can see that a List<Blog> is created, and a mock of a <DbSet<Blog>> is created. Then the relevant IQueryable<Blog> properties of the DbSet mock are populated from the equivalent IQueryable properties of the List [converted to IQueryable via the call to .AsQueryable()].
From there, we can derive a helper method that converts an IQueryable<T> to Mock<DbSet<T>>:
This in turn led to another helper method that takes an IList<T> and returns a Mock<DbSet<T>>:
The test data for the first case is:
Thankfully an MSDN article has the answer. In the article we can see that a List<Blog> is created, and a mock of a <DbSet<Blog>> is created. Then the relevant IQueryable<Blog> properties of the DbSet mock are populated from the equivalent IQueryable properties of the List [converted to IQueryable via the call to .AsQueryable()].
From there, we can derive a helper method that converts an IQueryable<T> to Mock<DbSet<T>>:
private static void SetupMockDbSetFromIQueryable<T>(IQueryable<T> testSource, Mock<DbSet<T>> mockTarget) where T : class
{
// http://msdn.microsoft.com/en-gb/data/dn314429.aspx
mockTarget.As<IQueryable<T>>().Setup(m => m.Provider).Returns(testSource.Provider);
mockTarget.As<IQueryable<T>>().Setup(m => m.Expression).Returns(testSource.Expression);
mockTarget.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(testSource.ElementType);
mockTarget.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(testSource.GetEnumerator());
}
This in turn led to another helper method that takes an IList<T> and returns a Mock<DbSet<T>>:
private Mock<DbSet<T>> GetMockDbSetFromList<T>(IList<T> list) where T : class
{
var dbSetMock = new Mock<DbSet<T>>();
SetupMockDbSetFromIQueryable<T>(list.AsQueryable(), dbSetMock);
return dbSetMock;
}
Initialise the auto mocking kernel
We don't want to use the normal Ninject kernel, we are using the auto mock extension specific to the Moq mocking framework.private static kernel = new Ninject.MockingKernel.Moq.MoqMockingKernel();
Create the test data (for test when Case has no Creditors)
We have two test scenarios: when the Case has no Creditors, and when the Case has a Creditor.The test data for the first case is:
// create a test Case with no linked Creditor
var caseTestData = new List<Case>
{
new Case() { CaseId = 1000, DebtorCaseRef = "XX11001" }
};
Create the mock DbContext object and populate with test data
For the DbContext parameter, we create a mock object and then set the Cases property to the DbSet<Case> mock object created earlier.// convert from List to DbSet (IQueryable) var casesMock = GetMockDbSetFromList<Case>(caseTestData); // create an empty IDbContext mock var dbContextMock = kernel.GetMock<IDbContext>(); // populate the Cases property with our mock DbSet<Case> dbContextMock.Setup(m => m.Cases).Returns(casesMock.Object);
Create the target Method's concrete Class instance using DI/auto mocking
We saw from the concrete class's constructor that we need to satisfy the need for an IDbContext and an ICachedEntities. We have created our DbContext, and using the auto-mock kernel we can omit the ICachedEntities parameter, allowing the kernel to create a mock on the fly.var caseProviderDb = kernel.Get<CaseProviderDb>();
Test the call to the concrete method
All of the previous work falls under "Arrange" in the AAA approach to Unit Testing.Now we are going to Act, then Assert:
// Act
var caseCreditors = caseProviderDb.GetCaseCreditors(
new Case() { CaseId = 1000, DebtorCaseRef = "XX11001" }
);
// Assert
Assert.AreEqual<int>(caseCreditors.Count(), 0);
This is a simple assertion that there are no Creditors associated with the Case (and the test passes!).Create the test for when a Case has a Creditor
Our second test in this simple(!) scenario is when the Case has a Creditor. (A possible next test would be a Case having multiple Creditors, but hopefully I don't need to include that here.) The test data for this test is:// create a test Case with at least one linked Creditor
var caseTestData = new List<Case>
{
new Case()
{
CaseId = 1000,
DebtorCaseRef = "XX11001",
CaseCreditors = new List<CaseCreditor>
{
new CaseCreditor()
{
CaseId = 1000,
CreditorId = 1,
Creditor = new Creditor() { CreditorId = 1, CreditorName = "Creditor1", CreditorRef = "XX" }
}
}
}
};
And then all of the code is the same up to the Assert section, which now expects one Creditor returned for the Case:
// Assert Assert.AreEqual<int>(caseCreditors.Count(), 1);
The test passes, BUT this is because we have pre-"joined" in-memory data, that is to say, you get out what you put in. Try changing the CaseId's so that they don't match, and the test still passes.
The alternative
See EFFORT for a possible solution to in-memory queries that will use Linq to Entities.There are also in-memory databases out there you could use as a heavyweight/real back-end.
For purists out there it is probably worth recognising that we find ourselves in a testing no-man's land. This approach is not a Unit Test, because it relies on external resources, and it isn't an authentic Integration Test because it isn't using the same/target backend (database).
General notes
Unit Testing LINQ queries in this way is not a replacement for Integration Testing against a real database (due to the way queries aren't actually executed until enumerated and that the generated query may change over time with versions), but it may be useful for checking your application logic for a given scenario.Of course, you can mock the target method itself to just return what you want, under another scenario where you are testing logic at a higher level.
I have stripped the code down as far as possible to leave the salient information. As a result, comments were removed, along with exception handling, etc.
Version information for the technologies in use
For those who are interested, the versions of the technologies used are included here:.Net Framework 4.0
Visual Studio Extension EntityFramework Reverse POCO Code First Generator, version 2.6
NuGet package EntityFramework, version 6.1.0
NuGet package Moq, version 4.2.1402.2112
NuGet package Ninject, version 3.2.0.0
NuGet package Ninject.MockingKernel, version 3.2.0.0
NuGet package Ninject.MockingKernel.Moq, version 3.2.0.0