In his notable MVC Storefront series, Rob Conery brought up a very intereting pattern, called Pipe and Filter. My first reaction to that was a bit anxious if it might hurt testability. Before I start with the problem, I think this pattern deserves a little bit introductory words, just in case you have stayed under the rock for the last 12 months, and haven’t checked Rob Conery’s posts.
Pipe and Filter is a pattern recently made popular by Rob’s MVC Storefront episodes. Also known as Filtration pattern, it is a very nice trick to produce a highly fluent Linq2Sql data repository (although, well, it’s not quite repository anymore).
Basically, instead of having several specific-purpose querying filters on repository interface like this:
var list = customerRepository
.FindByStateAndAgeBelow("Tazmania", 20);
We can now use a far more fluent and flexible syntax through chained filtering statements like this:
var list = customerRepository.All()
.WithState("Tazmania")
.WithAgeBelow(20).List();
The magic behind it is .Net 3.5′s Extension Methods.
public static class CustomerFilter
{
public IQueryable<Customer> WithState (this IQueryable<Customer> query, string state)
{
return from cus in query
where cus.HomeAddress.State == state
select cus;
}
public IQueryable<Customer> WithAgeBelow(this IQueryable<Customer> query, int age)
{
var maxDob = Date.Now.AddYear(-age);
return from cus in query
where cus.BirthDate < maxDob
select cus;
}
}
Testing both of these filters is easy. Just create a collection of customer object, execute the filter, then go ahead and check the result. Here is the unit-test code for WithState filter.
// Stub Customer List
var list = new List<Customer>()
{
new Customer() {HomeAddress = new Address() {State = "Illinois"}},
new Customer() {HomeAddress = new Address() {State = "Tazmania"}},
new Customer() {HomeAddress = new Address() {State = "NSW"}},
new Customer() {HomeAddress = new Address() {State = "Tazmania"}}
};
var query = list.ToQueryable();
// Execute
var filtered = query.WithState("Tazmania").ToList();
// Verify
Assert.That(filtered.Count, Is.EqualTo(2));
Assert.IsTrue(filtered.Contains(list[1]));
Assert.That(filtered.Contains(list[3]));
Now imagine I work in an ambitious evil project, where I have a small piece of method in my business logic that sends spam emails to all Tazmanian teens.
public void SendAdvertisement(string message)
{
foreach(var customer in
customerRepository.All()
.WithState("Tazmania").WithAgeBelow(20))
{
emailSender.Send(customer.EmailAddress, message);
}
}
The question is now, how do we write unit test to verify this logic?
Had we used customerRepository.FindByStateAndAgeBelow(“Tazmania”, 20), we would be able to just mock away the call to customerRepository.FindByStateAndAgeBelow(), (hence, behavior or interaction-based verification), and we are sorted.
But here, the problem with Pipe and Filter pattern is the fact that it uses Extension Methods, which are essentially static methods! (And we all know, static methods are the villains in TDD world). They are not mockable, and we have to deal with the real filter implementations.
True, I could just use the same state-based verification approach that we have done above on WithState unit-test by stubbing up a list of customer objects, and then verify the emailSender based on the expected filtered customers. But this is really gross.
I don’t want to care about the internal behavior of the filters here. As the matter of fact, each filter has already had its own unit-test (we have written one above), and I don’t want to repeat myself here. All that I really want to care is if my business logic makes the correct calls to the correct filters, and put the filtration result as our spam targets.
I want to gather how you write unit-test for Pipe|Filter pattern. How you mock out filter logic from your business-logic tests. I have a quick thought in mind about writing a Rhino-Mock helper to catter this scenario. I have yet to try it out, and I will write it on the next post as soon as I do. But first, I would like to hear what other people think about this. Any comment?
Hi
Could you help me, How to convert tables values into textual form through stored procedure or using transact – SQL.
by
Thanks
Palani
Comment by Palani — September 8, 2008 @ 12:21 pm |
They are static, but they’re not really
. You can only use them as methods on an instance so in that sense, they aren’t static. That issue aside, they are fairly easy to test.
In your case, you’ll want to test each method to make sure that it returns what you expect. The first thing you do is stub (or mock) a customer repository with a List:
(this is free-handed code, forgive the errors)
List customerRepository=new List();
for(int i=0;i<100;i++){
//load the list giving the first 10 Customers an age below 20
Customer c=new Customer();
c.Age= i<= 10 ? 15:30;
//fill out the rest of it…
}
//turn it IQueryable…
var customerQuery=customerRepository.AsQueryable();
//run the test
Assert.AreEqual(10, customerQuery.WithAgeBelow(20).Count());
The idea here is to mock up a list, set it to Queryable, and then test the filter.
Comment by Rob Conery — September 8, 2008 @ 6:33 pm |
@Rob,
Thanks for the input.
What I am trying to test here is actually the business logic that USES the filter, rather than the filter itself. In this case, it would be the SendAdvertisement method.
As for testing the filter, I have used the similar approach here on WithState unit-test by stubbing list of customers. But I don’t really want to repeat it when unit-testing SendAdbertisement business logic. I.e., I’m looking to mocking out the dependency to all data filters.
Suggestions?
Comment by hendryluk — September 8, 2008 @ 9:18 pm |
Hendry,
Glad you have some postings about pipe and filter pattern implementation using LINQ to SQL and extension methods. Is Rob Conery here really robcon[A.T.]microsoft[dot]com? He he, just curious.
You can also read http://wolfbyte-net.blogspot.com/2008/04/pipe-and-filters-fluent-apis-and-linq.html about different way to implement pipe and filter patter in LINQ to SQL
Regards,
Agus Suhanto
Comment by Agus Suhanto — September 25, 2008 @ 12:13 am |
[...] in Software Development at 8:34 am by hendryluk In previous post, I was questioning the mockability of query filters in Rob Conery’s Pipe and Filter pattern. [...]
Pingback by Mocking Helper for Pipe and Filter « Hendry Luk — Sheep in Fence — September 25, 2008 @ 8:37 am |
[...] #4 is on unit-testing. Yes it is very easy to unit-test each of the filter independently, but it is extremely difficult to mock out those filters to unit-test services that depends on it. I’ll describe the problem [...]
Pingback by Extensible Query with Specification Patterns « Hendry Luk — Sheep in Fence — March 23, 2009 @ 11:49 am |