Here we look at: What is an Expression and how to use it. Then we look at some examples of expressions like Simple, Reusable, and Advanced. We also look at a mocking scenario where I tell the stubbing framework to expect any kind of expression. Then finally we look at the solution, where I show how we can write more specific unit tests that are more resilient to changes or false negatives.
For this post, please keep in mind that I’m by no means versed in expression trees so I will try my best to explain.
What Is an Expression and How to Use It
Going by the explanation defined here, an Expression<T>
is a strongly typed lambda expression which treats the lambda expression as structured data instead of just being a pointer to a function.
The reason one would use an Expression is so that Linq can be manipulated, read, or constructed at runtime. Such features are usually used in database providers to translate normal LINQ expressions into database queries, for example in Linq-to-SQL.
Examples
Please note that these examples are from the book “C# 8.0 in a Nutshell”, from which the samples can be found in LinqPad.
Simple Expression
The simple example is as follows:
Expression<Func<int>> return5 = () => 5;
Func<int> compiled = return5.Compile();
Console.WriteLine(compiled());
This snippet compiles a declared function and executes it.
Reusable Expression
Expression<Func<string, string, bool>> expression = (x, y) => x.StartsWith(y);
var compiled = expression.Compile();
Console.WriteLine(compiled("First", "Second"));
Console.WriteLine(compiled("First", "Fir"));
In this example, an expression is compiled and executed twice.
Advanced Expression
This example builds an expression by hand and then compiles it for its use.
ParameterExpression param = Expression.Parameter(typeof(int), "i");
Expression<Func<int, bool>> isOdd =
Expression.Lambda<Func<int, bool>>(
Expression.Equal(
Expression.And(
param,
Expression.Constant(1, typeof(int))),
Expression.Constant(1, typeof(int))),
new ParameterExpression[] { param });
Func<int, bool> isOddCompiledExpression = isOdd.Compile();
Console.WriteLine("With a compiled expression tree:");
for (int i = 0; i < 10; i++)
{
if (isOddCompiledExpression(i))
Console.WriteLine(i + " is odd");
else
Console.WriteLine(i + " is even");
}
In this example, the expression gets built programmatically to determine if a number is odd or even. Even though it’s quite verbose and occupies a big chunk of code just to create a simple expression, the power here comes from the fact that we can break up that expression into smaller parts and build an expression dynamically.
The Mocking Scenario
I’ve created an interface over a repository so that I can swap out implementations, and I also wanted the interface to be open so that I can dynamically create a repository for any data type I would need.
The interface is just a standard generic repository interface that will receive the T
type via dependency injection. In my case, I use stashbox (maybe I’ll write a post about stashbox, but I’ve been putting it off due to the sheer amount of functionality, and the fact that it’s already pretty well documented).
public interface IStorageRepository<T> where T: class
{
IEnumerable<T> GetAll();
IEnumerable<T> GetAllWithInclude(params Expression<Func<T, object>>[] includes);
IEnumerable<T> Find(Expression<Func<T, bool>> expression,
int skip = 0, int limit = int.MaxValue);
Task UsertAsync(T user);
T FindOne(Expression<Func<T, bool>> expression);
Task DeleteAsync(Expression<Func<T, bool>> predicate);
}
Because the implementation of this interface is meant to be injected, it also means it can be mocked/stubbed for unit tests. One solution for this (before I found the solution that follows) was to tell the stubbing framework to expect any kind of expression, in my case I use NSubstitute in combination with stashbox
but for this example, I’m only going to use NSubstitute
. Here’s an example of it:
public class Tests
{
[Test]
public void UsingAnyArgs()
{
IStorageRepository<TestClass> testClassRepositorySub =
Substitute.For<IStorageRepository<TestClass>>();
TestClass testClass = new TestClass
{
Id = 1
};
testClassRepositorySub
.Configure()
.FindOne(Arg.Any<Expression<Func<TestClass, bool>>>())
.Returns(testClass);
TestClass expectedTestClass = testClassRepositorySub.FindOne
(class1 => class1.Id == testClass.Id);
Assert.That(testClass.Id, Is.EqualTo(expectedTestClass.Id));
}
public class TestClass
{
public int Id { get; set; }
}
}
As we can see, this works for simple scenarios where we don’t care what the expression is but we want to retrieve a specific instance for our testing purposes.
The problem arises when we want to run the query multiple times with different expressions like a different Id.
The Solution
While having encountered that very scenario in which I needed to run multiple queries with different results, I stumbled upon this answer on stackoverflow which helped me find out about this project and its associated nuget package.
The new approach with the addition of multiple queries is as follows:
[Test]
public void UsingSpecificExpressions()
{
IStorageRepository<TestClass> testClassRepositorySub =
Substitute.For<IStorageRepository<TestClass>>();
TestClass testClass1 = new TestClass
{
Id = 1
};
TestClass testClass2 = new TestClass
{
Id = 1
};
testClassRepositorySub
.Configure()
.FindOne(Arg.Is<Expression<Func<TestClass, bool>>>(expression =>
Lambda.Eq(expression, tc => tc.Id == testClass1.Id)))
.Returns(testClass1);
testClassRepositorySub
.Configure()
.FindOne(Arg.Is<Expression<Func<TestClass, bool>>>(expression =>
Lambda.Eq(expression, tc => tc.Id == testClass2.Id)))
.Returns(testClass2);
TestClass expectedTestClass1 = testClassRepositorySub.FindOne
(class1 => class1.Id == testClass1.Id);
TestClass expectedTestClass2 = testClassRepositorySub.FindOne
(class2 => class2.Id == testClass2.Id);
Assert.That(testClass1.Id, Is.EqualTo(expectedTestClass1.Id));
Assert.That(testClass2.Id, Is.EqualTo(expectedTestClass2.Id));
}
As we can see, there is more to write and I’m sure there are even more ways to clean it up but it gets the job done.
There’s a bonus, some of you might have noticed the IEnumerable<T> GetAllWithInclude(params Expression<Func<T, object>>[] includes)
method on the interface, well, since params translate into an array, we can use it as follows:
[Test]
public void UsingSpecificExpressionsFromArrays()
{
IStorageRepository<TestClass> testClassRepositorySub =
Substitute.For<IStorageRepository<TestClass>>();
TestClass testClass = new TestClass
{
Id = 1
};
testClassRepositorySub
.Configure()
.GetAllWithInclude(Arg.Is<Expression<Func<TestClass, object>>[]>(expression =>
Lambda.Eq(expression[0], tc => tc.InnerTestClass)))
.Returns(new[] {testClass});
TestClass expectedTestClass = testClassRepositorySub.GetAllWithInclude
(class1 => class1.InnerTestClass).Single();
Assert.That(testClass.Id, Is.EqualTo(expectedTestClass.Id));
}
public class TestClass
{
public int Id { get; set; }
public InnerTestClass InnerTestClass { get; set; }
}
Now we can write more specific unit tests that are more resilient to changes or false negatives.
Conclusion
Even though now I have to rewrite my unit tests that use Args.Any()
, at least now I have more control over what is being tested. I would have liked for the test setup to be a little less verbose, and I’m sure some of you would have better implementations, but at least it is a start.
I hope you enjoyed this winding path of frameworks and problem solving and that it helps you as it did me.
Until next time, happy coding!