Unit testing is the base level of the testing pyramid and thus a vital cornerstone of effective software development. In order to effectively unit test your code you should make use of SOLID design principles and mocking frameworks. That said, it isn’t always easy to accomplish such as mocking IQueryable Extensions.
I recently ran into a case where I was trying to strangle some code out of a monolith. I added an interface around an OData client to expose a single IQueryable property for now. As part of this dev cycle I’m creating a new caching layer around that interface, putting it into it’s own Git repository, and then publishing as a reusable Nuget package. We plan on adding more data type caches to this library and want to reuse it across multiple internal projects. The majority of our tests are MSTest with Moq so that’s the pattern I need to follow.
Properly unit testing your code can help make you a better developer just like it does for me.
Scenario
Consider an interface something along these lines:
public interface IDataClient
{
IQueryable<MyModel> MyModels { get; }
}
In this particular use-case I have a cache specifically built around MyModel
. That cache has IDataClient
as a direct dependency. My cache has a few different methods and I need to test each one of them. In order to have a complete unit test I need to verify that if an item is already in the cache it doesn’t attempt to load it again. Hopefully I’ve now set enough of the stage for the problem I faced.
Mocking IQuerable Extensions: Attempt #1
Seeing as this was new territory to me I went to the internets in search of an answer. I came across a pretty old StackOverflow question that seemed inline with what I was looking for. Since the edit for this answer seems legit, that’s the direction I’ll go first. Basically the following:
Expression<Func<MyModel, bool>> func = (param) => param.Id == 1
var mockModel = new Mock<IQueryable<MyModel>>();
mockModel.Verify(x => x.Where(func));
Unfortunately this throws an exception: System.NotSupportedException: 'Unsupported expression: x => x.Where(MyModelManagerTests.<>c__DisplayClass7_0.func)
Extension methods (here: Queryable.Where) may not be used in setup / verification expressions.'
Mocking IQueryable Extensions: Attempt #2
This led me to keep searching which turned up another StackOverflow question and answer. This one seemed a bit more bare-metal but I was willing to give it a try. This answer (and the link to MSDN it includes) general premise is that you can represent the LINQ to SQL (or to OData, etc) by mapping it to LINQ to Objects instead. Generally speaking, this was the suggested setup:
var mockSet = new Mock<DbSet<MyModel>>();
mockSet.As<IQueryable<MyModel>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<MyModel>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<MyModel>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<MyModel>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
One problem I had was that I wasn’t actually working with a DbSet. If I looked at the original text for the question, however, it gave me an idea. Rather than mocking the DbSet directly I could just mock IQueryable and seed it with data from an IList.
One important thing to note, however, was that I was still unable to Verify
if the IQueryable
was being called. Keep that in mind as we transition to the next part.
My Solution
I started by creating a “fakes” concrete to implement the IDataClient
. Inside this class I created a new List<MyModel>
and populated it with a few records then converted it to an IQueryable
. In order to mock the IQueryable
extensions I do a little Moq magic and map the properties from my mocked object to the properties from my List.
In order to accomplish “verifying” calls to the IQueryable
I also piped in a callback for the Expression
property. It is ultra-simple and not elegant by any stretch of the imagination, but it works. I am counting whether or not an Expression
is firing on the IQueryable
. Here is the “fakes” class:
public class FakesDataClient : IDataClient
{
public FakesDataClient()
{
var data = new List<MyModel>
{
new MyModel{ ID = 1 },
new MyModel{ ID = 2 },
new MyModel{ ID = 3 }
}.AsQueryable();
MockMyModels = new Mock<IQueryable<MyModel>>();
MockMyModels.As<IQueryable<MyModel>>().Setup(m => m.Provider).Returns(data.Provider);
MockMyModels.As<IQueryable<MyModel>>().Setup(m => m.Expression).Callback(()=> ExpressionCalls++).Returns(data.Expression);
MockMyModels.As<IQueryable<MyModel>>().Setup(m => m.ElementType).Returns(data.ElementType);
MockMyModels.As<IQueryable<MyModel>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
MyModels = MockMyModels.Object;
}
public int ExpressionCalls { get; private set; }
public IQueryable<MyModel> MyModels { get; }
public Mock<IQueryable<MyModel>> MockMyModels { get; }
}
And for completeness sake, here is a snippet of my MSTest unit tests for the cache:
[TestInitialize]
public void Initialize()
{
DataClientFake = new FakesDataClient();
Target = new MyModelManager(DataClientFake);
}
[TestMethod]
public void GetModel_AddsToCache_Success()
{
// arrange
var modelId = 1;
// act
var result = Target.GetModel(modelId);
// assert
Assert.AreEqual(modelId, result.ID);
Assert.AreEqual(1, DataClientFake.ExpressionCalls);
}
[TestMethod]
public void GetModel_AlreadyInCache_Success()
{
// arrange
var modelId = 1;
// seed cache
var seed = Target.GetModel(modelId);
// act
var result = Target.GetModel(modelId);
// assert
Assert.AreEqual(seed, result);
Assert.AreEqual(1, DataClientFake.ExpressionCalls);
}
I test two things in these tests: 1) that the initial call to grab an object first hits the IQueryable and 2) that a subsequent call for the same object does not.
Alternatives
So there are clearly some alternatives to mocking IQueryable extensions. One of the options I had was to further abstract the data client out using the repository pattern. In fact, as I continue refactoring this code I’ll likely take that direction anyway. Why? Because it allows me to more easily swap our data layer out (which we’re considering for future enhancements).
Abstracting my data layer out with a repository would have allowed me to easily mock the repository in my tests. The issue I had with this approach is it would only introduce an extra (at this time unnecessary) layer into my code and I’d *still* need to write integration tests anyway. I opted to keep it simple for now.
Conclusion
Ultimately we see that while mocking IQueryable extensions with Moq is difficult, there are some workarounds if you’re willing to think about what you’re actually trying to accomplish. In my case I only need to count that a call is being made. I can accomplish similar results by abstracting my data out into the repository pattern or something similar.