Mocking Helper for Pipe and Filter

In previous post, I was questioning the mockability of query filters in Rob Conery’s Pipe and Filter pattern. I left the post open with a hint about one possible approach to do it, hence this post.

So I have written a simple helper class, QueryableMock, specifically to address this issue, and apparently it is much more complex than I ever expected. The idea is basically not to really mock away the filter implementation, but instead to record the final result of expression-tree generated by the filter. During the playback, the SUT will run the filter methods again, and again, we capture again its expression-tree, and finally compare it with the previously recorded expression. If it matches, then we can safely say that our SUT has passed the test by running exactly all expected filters.

So just to remind ourselves, let’s take a look at the code we want to test from the last post, a Spammer class that sends spam emails to all Tazmanian teens.

public void SendTazmanianTeenSpam(string spamMessage)
{
	var targets = this.customerRepository.All()
		.LivesInCity("Tazmania")
		.YoungerThan(18);

	foreach(var customer in targets)
		emailSender.Send(customer.Email.Address, spamMessage);
}

So let’s check out how we write the unit-test using our helper QueryableMock class.

[Test]
public void CanSendSpamToTazmanianTeen()
{
	var targetStubs = new List<Customer>
	{
		new Customer(1) {Email = new Email("test@email.com")},
		new Customer(2) {Email = new Email("anotherTest@email.com")}
	};

	var customerQueryMock = new QueryableMock<Customer>();
	customerQueryMock.Expect()
		.LivesInCity("Tazmania")
		.YoungerThan(18);
	customerQueryMock.StubFinalResult(targetStubs);

	using (mockery.Record())
	{
		Expect.Call(customerRepository.All()).Return(customerQueryMock);
		emailSender.Send("test@email.com", spamMessage);
		emailSender.Send("anotherTest@email.com", spamMessage);
	}

	using (mockery.Playback())
	{
		spammer.SendTazmanianTeenSpam(spamMessage);
	}
}

Look closer at line #10:

customerQueryMock.Expect()
	.LivesInCity("Tazmania")
	.YoungerThan(18);
customerQueryMock.StubFinalResult(targetStubs);

What it does is building expected filtration expression. It is done by executing the real filter methods and record the final Queryable Expression. This recorded Expression will later on be compared with the Expression being executed later on in SUT: spammer.SendTazmanianTeenSpam(spamMessage);

UNDER THE HOOD
That was all we need to do in the unit-test. Here’s some relevant bit of the code behind QueryableMock.

public class QueryableMock<T> : QueryableProxy<T>
{
	// ... Other stuffs ...
	protected override IEnumerator GetFinalEnumerator()
	{
		VerifyExpression();
		return stubbedFinalEnumerable.GetEnumerator();
	}
	public void VerifyExpression()
	{
		if(!ExpressionComparer.CompareExpression(
			Provider.QueriedExpression, recorderQuery.Provider.QueriedExpression))
			throw new Exception("Query expression does not match expectation");
	}
}

The idea is so far pretty straightforward: when the SUT is about to execute (enumerate) the query filter, it will try to do VerifyExpression to compare the filter expression against expected expression.
The biggest challange here is to compare the equality of 2 expression trees. I.e., how to verify if x=>x+1 is equal to x=>x+1. I was quite surprised that there is no easy way of doing it. Life cannot be as simple as calling Expression.Equals(). So I wrote a quick brute-force comparison logic in ExpressionComparer class. You can check the detail on the attached source-code. If you know of any easier way to do it, please do let me know.

EVEN FURTHER
Finally, this QueryableMock utility is very specifically intended for unit-testing around Pipe-Filter pattern. It does a very limited thing, but so far it works very well to tackle my need (namely: to mock query-filters). Another interesting scenario that is also covered is:

customerQueryMock.Expect()
	.LivesInCity("New York")
	.SelectEmailAddresses()
	.InDomain("gmail.com");
customerQueryMock.StubFinalResult(emailList);

Note that the filtration switched from IQueryable<Customer> to IQueryable<Email> its half way through.

You may argue that TypeMock can provide much elegant solution for this. It uses bytecode instrumentation that can therefore intercept even static and extension methods. But I think the problem is not really whether I can do it or not. But if I cannot easily test a piece of code without relying on bytecode magic, I will question if the code design is even worth doing at all.

Admittedly, I am not 100% comfortable with this approach of mocking. Let me know what you think.

SOURCE CODE
You can download the full source-code of this post here to see what on earth I have been talking about.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s