Actually Querying with the Specification Pattern


In my previous post, I talked about using the specification pattern for querying collections. I didn’t actually show any code that does what I was talking about, I just showed the set-up and creation of specifications. The following is how you can put this into practice.

Setting Up Some Tests

These examples are going to be using Moq as an isolation/mocking framework. I find that the new Linq to Mocks syntax works pretty well for creating the scenarios. The first step I used is to create an InMemoryRepository. This will hold some pre-set customers that I can use in my tests to ensure that my Specifications get the expected number of Customers based on what the repository is holding.

public class InMemoryCustomerRepository : Repository<Customer>
    {
        private readonly IList<Customer> customers = new List<Customer>();

        public InMemoryCustomerRepository()
        {
            customers.Add(new Customer().WithTwentyOrders());
            customers.Add(new Customer().WithTwentyOrders());
            customers.Add(new Customer().WithTwentyOrders()
.WithLifetimeValueOf(8888m)); customers.Add(new Customer().WithTwentyOrders()
.WithLifetimeValueOf(10001m)); customers.Add(new Customer().WithLifetimeValueOf(4000m)); customers.Add(new Customer().WithLifetimeValueOf(5000m)); customers.Add(new Customer().WithLifetimeValueOf(6000m)); customers.Add(new Customer().WithLifetimeValueOf(7000m)); customers.Add(new Customer().WithLifetimeValueOf(8000m)); customers.Add(new Customer().WithLifetimeValueOf(9000m)); customers.Add(new Customer().WithTwentyOrders()
.WithLifetimeValueOf(3000m).AsDiscountMember()); customers.Add(new Customer().WithTwentyOrders()
.WithLifetimeValueOf(6000m).AsDiscountMember()); customers.Add(new Customer().WithTwentyOrders()
.WithLifetimeValueOf(9000m).AsDiscountMember()); } protected override IEnumerable<Customer> GetAllEntities() { return customers; } }

This class only provides some sample data that we’re going to use in our tests. I used some extension methods and chaining to build up some customers to add to our list.

Using Linq to Mocks

Moq has recently introduced Linq to Mocks where you can use Linq to query for an object you want to create. Basically, you’re saying:

Give me one customer that is a discount member, has an order count of 13 and a lifetime customer value of $5,555.

When there is a lot of setup involved with creating some fake objects, you’ll want move that into some sort of method so you don’t have to repeat yourself all over the place. Here’s how I did this with my customers:

 

private static Customer GetPlatinumCustomer()
{
    return (from customer in Mocks.Query<Customer>()
            where customer.IsDiscountMember == true &&
                  customer.OrderCount == 13 &&
                  customer.LifetimeCustomerValue == 5555m
            select customer).First();
}
private static Customer GetPreferredCustomer()
{
    return (from customer in Mocks.Query<Customer>()
            where customer.LifetimeCustomerValue == 5555m
            select customer).First();
}
private static Customer GetGoldCustomer()
{
    return (from customer in Mocks.Query<Customer>()
            where customer.OrderCount == 13 &&
                  customer.LifetimeCustomerValue == 5555m
            select customer).First();
}

If you need to build up some complex mocks/fakes, this method works fairly well, as demonstrated by Daniel Cazzulino, creator of Moq.

The tests that I wrote were very straightforward. Most were something along these lines:

[Fact]
public void GoldCustomer_should_also_meet_PreferredCustomer_requirements()
{
    var goldCustomer = GetGoldCustomer();

    var result = new PreferredCustomerSpecification().IsSatisfiedBy(goldCustomer);

    Assert.True(result);
}

I’m a big fan of using the Arrange/Act/Assert (AAA) syntax in unit tests, here I’m using the AAA syntax by doing the following:

  • Arrange: Getting a “Gold” customer
  • Act: Passing the customer into a specification
  • Assert: Ensuring that the result of the specification is false.

Implementing the Repository

Now it’s time to make sure this test passes, let’s implement the Get method of the CustomerRepository that takes an ISpecification. Here’s how the abstract class is set up:

public abstract class Repository<T>
{
    public IEnumerable<T> Get(ISpecification<T> specification)
    {
        foreach (var customer in GetAllEntities().Where(specification.IsSatisfiedBy))
            yield return customer;
    }
 
    protected abstract IEnumerable<T> GetAllEntities();
}

This is all I did for these examples. The abstract class above does all the work. Given a repository of type T, and a specification of type T, it will return all objects of T that match the specification.

Using the Specification Pattern for Querying