Freigeben über


Unit Testing a WCF RIA DomainService: Part 2, The Repository Pattern

This is part two in a series I’m writing about how to unit test your DomainServices. In part one, I discuss how you could use an IDomainServiceFactory to factor external dependencies out of your code.  Continuing along the same lines of thinking, I will use this post to discuss factoring out the database dependency as well. In general, factoring out external dependencies will increase both the isolation and consistency of your tests.

The pattern I’m using to factor out external dependencies is called the Repository Pattern. It’s well-known and comes in many variations. The variation I’ve chosen here is one that fits quite well with the way RIA consumes and Entity Framework ObjectContext. If you’re using a different data-access technology, feel free to modify this pattern until you get something that works for your scenario.

A Repository Sample

Before I jump into the Repository API or the DomainService setup, I’d like to show what introducing a repository might do to standard DomainService operations. Here are a few operations written directly against an ObjectContext.

   // Test 1: Operation should return all books
  // Test 2: Operation should return books with categories
  // Test 3: Operation should return books ordered by BookID
  public IQueryable<Book> GetBooks()
  {
    return this.ObjectContext.Books.Include("Category").
             OrderBy(b => b.BookID);
  }

  // Test 1: Operation should return all books for category
  // Test 2: Operation should return books ordered by BookID
  public IQueryable<Book> GetBooksForCategory(int categoryId)
  {
    return this.ObjectContext.Books.
             Where(b => b.CategoryID == categoryId).OrderBy(b => b.BookID);
  }

  // Test 1: Operation should update book
  // Test 2: Operation should return validation errors
  public void UpdateBook(Book book)
  {
    this.ObjectContext.Books.AttachAsModified(
      book, this.ChangeSet.GetOriginal(book));
  }

Upon introducing a repository into this code, the operations will be slightly different.

   // Test 1: Operation should return all books
  // Test 2: Operation should return books with categories
  // Test 3: Operation should return books ordered by BookID
  public IQueryable<Book> GetBooks()
  {
    return this._bookRepository.GetBooksWithCategories().
             OrderBy(b => b.BookID);
  }

  // Test 1: Operation should return all books for category
  // Test 2: Operation should return books ordered by BookID
  public IQueryable<Book> GetBooksForCategory(int categoryId)
  {
    return this._bookRepository.GetEntities().
             Where(b => b.CategoryID == categoryId).OrderBy(b => b.BookID);
  }

  // Test 1: Operation should update book
  // Test 2: Operation should return validation errors
  public void UpdateBook(Book book)
  {
    this._bookRepository.Update(
      book, this.ChangeSet.GetOriginal(book));
  }

By-and-large we’ve preserved the existing implementation. However, you can now see we don’t have a direct dependency on ObjectContext. Instead all interaction is funneled through the book repository. Doing this will allow us to introduce a mock repository implementation at test time which will allow us to test without any dependency on a database.

The Repository API

I’m going to abbreviate the API I show in this post. There are more interesting types and methods in the sample but I want to keep the conversation here focused. The first type we should look at is the IRepository<T> interface.

   public interface IRepository<T> : IRepository
  {
    IQueryable<T> GetEntities();
    void Insert(T entity);
    void Update(T entity, T original);
    void Delete(T entity);
  }

A repository is a simple type that surfaces methods for the Query, Insert, Update, and Delete operations that commonly show up in DomainServices. Also, the repository interface has a per-type affinity. Specifically, you’d have a separate instance for each root entity type (as a side note, this felt a little verbose to me, but also worked nicely. I’ll let you make the final call as to whether it’s worth the extra typing).

Here’s a partial Repository implementation using Entity Framework.

   public class Repository<T> : IRepository<T> where T : EntityObject
  {
    public virtual IQueryable<T> GetEntities()
    {
      return this.ObjectSet;
    }

    public virtual void Insert(T entity)
    {
      if (entity.EntityState != EntityState.Detached)
      {
        this.ObjectContext.ObjectStateManager.
          ChangeObjectState(entity, EntityState.Added);
      }
      else
      {
          this.ObjectSet.AddObject(entity);
      }
    }
  }

As you can see, the repository contains the familiar code required to work against the EF ObjectContext and ObjectSet.

The next class to look at is a derived repository interface. As you may have noticed already, our book repository has a GetBooksWithCategories method that isn’t in the base interface.

   public interface IBookRepository : IRepository<Book>
  {
    IQueryable<Book> GetBooksWithCategories();
  }

The concrete implementation of this method will show us why it’s interesting.

   public class BookRepository : Repository<Book>, IBookRepository
  {
    public BookRepository(BookClubEntities objectContext)
        : base(objectContext)
    {
    }

    public IQueryable<Book> GetBooksWithCategories()
    {
        return this.ObjectSet.Include("Category");
    }
  }

Specifically, we’ve used the Include method on ObjectSet. Since the include method is specific to entity framework, I’ve placed it behind the repository interface.

The UnitOfWork API

Both DomainServices and Repositories work on the principal that you can make a lot of changes and then submit them all as a single unit-of-work. To facilitate this, our Repository pattern also provides an IUnitOfWork interface.

   public interface IUnitOfWork
  {
    void Save();
  }

Simple. Now we just need to make sure it is called when our DomainService is ready to persist changes. Luckily, the DomainService base class provides a virtual protected method to allow us to do just that.

   // Test 1: Unit of work should be saved
  protected override bool PersistChangeSet()
  {
    this.UnitOfWork.Save();
    return true;
  }

Finally, we need to make sure the unit-of-work can be used to persist the changes to our Entity Framework model. To do this, we can use a partial class to implement the interface on BookClubEntities.

   public partial class BookClubEntities : IUnitOfWork
  {
    public void Save()
    {
      this.SaveChanges();
    }
  }

Now we’ll need to take a look at how all these pieces are wired up.

A DomainService Using a Repository

Like I showed in my previous post, we’ll need to pass the repository to the DomainService’s constructor.

   public BookClubDomainService(
    IUnitOfWork unitOfWork,
    IBookRepository bookRepository,
    ILibraryService libraryService,
    IApprovalSystem approvalSystem)
    : base(unitOfWork, bookRepository) 
  {
    if (bookRepository == null)
    {
      throw new ArgumentNullException("bookRepository");
    }
    if (libraryService == null)
    {
      throw new ArgumentNullException("libraryService");
    }
    if (approvalSystem == null)
    {
      throw new ArgumentNullException("approvalSystem");
    }

    this._bookRepository = bookRepository;
    this._libraryService = libraryService;
    this._approvalSystem = approvalSystem;
  }

You can refer back to my previous post on how to create and bootstrap an IDomainServiceFactory. I won’t repeat the information here, but I’ll give you a quick look at the factory code that creates the DomainService.

   public class BookClubDomainServiceFactory : IDomainServiceFactory
  {
    public DomainService CreateDomainService(
      Type domainServiceType, DomainServiceContext context)
    {
      DomainService domainService;
      if (typeof(BookClubDomainService) == domainServiceType)
      {
        BookClubEntities bookClubEntities = new BookClubEntities();
        domainService = new BookClubDomainService(
          bookClubEntities,
          new BookRepository(bookClubEntities), 
          new LibraryService(),
          new ApprovalSystem());
      }
      else
      {
        domainService = (DomainService)
          Activator.CreateInstance(domainServiceType);
      }

      domainService.Initialize(context);
      return domainService;
    }
  }

Finally, we need to set the BookClubDomainService  to provide the correct metadata about the types it supports. Since we’re returning Entity Framework types, we’ll need to use the LinqToEntitiesDomainServiceDescriptionProviderAttribute.

   [LinqToEntitiesDomainServiceDescriptionProvider(
     typeof(BookClubEntities))]
  public class BookClubDomainService : RepositoryDomainService { ... }

Also, you’ll notice I’ve created a RepositoryDomainService base class to encapsulate some of the common Repository concerns. With all these pieces together, it becomes a simple matter to write DomainService operations.

   // Test 1: Operation should return all books
  // Test 2: Operation should return books with categories
  // Test 3: Operation should return books ordered by BookID
  public IQueryable<Book> GetBooks()
  {
    return this._bookRepository.GetBooksWithCategories().
             OrderBy(b => b.BookID);
  }

Testing With a Repository

Though I plan to save most of this for the next post, it’s worth looking at the MockRespository we’ll use for testing.

   public class MockRepository<T> : IRepository<T>
  {
    private readonly Dictionary<Type, IList> _entities =
      new Dictionary<Type, IList>();

    public List<TEntity> GetTable<TEntity>() { ... }

    public virtual IQueryable<T> GetEntities()
    {
      return this.GetTable<T>().AsQueryable();
    }

    public virtual void Insert(T entity)
    {
      this.GetTable<T>().Add(entity);
    }
  }

The mock repository allows us to populate our table with default data before a test and check the updated contents of the table after the test.

   private class MockBookRepository : MockRepository<Book>, IBookRepository
  {
    public MockBookRepository()
    {
      this.GetTable<Book>().AddRange(new []
      {
        new Book
        {
          AddedDate = DateTime.UtcNow,
          ASIN = "1234567890",
          Author = "Author",
          BookID = 1,
          ...
        },
        ...
      });
    }
  }

Now, when we create our DomainService in our tests, we can pass an instance of MockBookRepository in instead of our real implementation. Using a repository like this in our tests improves the isolation and consistency. In the last post in this series on testing, I will finally show how to write unit tests for your DomainServices.

Unit Testing Series

Comments

  • Anonymous
    August 30, 2011
    Great series of articles. I enjoyed it very much. I posted a small article in my blog about non technical benefits of Repository Pattern. Check it out at knowillence.com/.../repository-pattern-non-technical-advantage

  • Anonymous
    October 17, 2011
    Found a typo for you.  I believe the work "and" in the following sentence should be "an". The variation I’ve chosen here is one that fits quite well with the way RIA consumes and Entity Framework ObjectContext.

  • Anonymous
    November 03, 2011
    I have implemented the repository in a similar way. However, I wasnt able to use the IRepository.GetEntities() in a nested select: I keep getting a "Linq to Entities doesnt recognize the method...". Searching around I've found that it do accept an IQueryable property of the repository in the nested select, but not an IQueryable method. Would you know why does this happen?

  • Anonymous
    November 08, 2011
    @Arthur What you're seeing is just a limitation of LINQ. Your best option is to follow the pattern I outline with the BookRepository above. Complex queries (like GetBooksWithCategories) should sit behind the repository interface, not on top of it.