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<Customer>. 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.

Related:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Chris Missal

Oh hey, I'm a Senior Consultant for Headspring in Austin, TX. I've been working in software professionally since 2006 and I really, really love it. I'm mostly in the Microsoft world, but enjoy building computer things of all sorts (to be vague). When I'm not slinging code, I'm probably out and about slinging discs, bowling balls, or good beer with great friends.
This entry was posted in Design Patterns, DRY, Moq, Testing. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://wizardsofsmart.net/ Ryan Riley

    While the abstract repository implementation works well for testing, I hope everyone will keep in mind that pulling back an entire table in something like LINQ to SQL will cause a large performance hit. If you would like to see an example of specifications used in filtering results from the database, check out Linq, the Specification Pattern and Encapsulation (code).

  • Jeremy Wiebe

    Great examples, Chris.

    I’m wondering though, why do you need the ‘foreach’ in the last example? Couldn’t you just return:

    return GetAllEntities().Where(specification.IsSatisfiedBy);

    Does the yield buy you anything that I’m missing?

  • http://www.lostechies.com/members/chrismissal/default.aspx Chris Missal

    @Ryan
    Testing or other, this was set up for in memory collections. When making any kind of remote call, you’re right. I suppose you could argue that showing code like this could lead to those bad practices of pulling an entire data set (or using Linq to SQL) ;)

    @Jeremy

    Good point, I believe you’re correct. I only used the yield to take advantage of deferred execution.

  • Paco

    “this was set up for in memory collections” -> Why do you call it repository? It’s a confusing term to use for an in-memory collection.

    Why don’t you put the list option in separate repository classes instead of in the specification?
    ISpecification
    {
    bool IsSatisfiedBy(T entity);
    IEnumerable
    AreSatisfiedBy(IEnumerable entities);
    }

  • http://www.lostechies.com/members/chrismissal/default.aspx Chris Missal

    @Paco

    When I ask a repository for something, I’m not concerned whether it’s getting me data from an in memory collection or a database. I just want to know that it holds what I’m looking for, whatever the implementation may be.

  • Paco

    In that case, I would still go for the AreSatisfiedBy approach instead of the method in repository approach, because that decouples the specification form the type of collection to satisfy on. When the repository pattern is interpreted as “Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.” the repository should be an IEnumerable and not have a method that returns an IEnumerable

    public interface IRepository : IEnumerable
    {
    }

    The specification can be used as
    IEnumerable result = new MySpecification().AreSatisfiedBy(repository)

    instead of IEnumerable result = repository.Get(new MySpecification());

    there is nothing wrong with a Get method in the repository that has a specification as argument, but with the “repository is a collection” approach, the repository interface can be substituded by any other collection interface in both tests and the real code.

  • http://www.webhostings.in/ hosting server

    Above the Query is excellent written skills.I enjoyed to visit this kind of valuable tips.

  • http://www.webhostings.in/ web hosting companies

    Your thoughts are also very good and i am very inspired from your post

  • http://www.webhostings.in/ hosting server

    Most of the people looking for this kind of valuable tips.

  • http://www.webhostings.in/ Hosting company

    Very good programming language….Its a valuable things..