Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

Don’t be afraid of complex constraints

The nice thing about mocking frameworks is that they let you test the interaction of your units painlessly. For example, given the following code:

public class UserRepository
{
  private readonly IDataStore _store;
  public UserRepository(IDataStore store)
  {
    _store = store;
  }

  public User FindById(int userId)
  {
    return _store.FindById(userId) ?? User.NullUser;
  }
}

We could write a test specifically to ensure the interaction between our repository and store is solid (for completeness we’d likely add 2 more tests to ensure the proper behavior when our store returns null and when it returns an actual user):

[Fact]
public void RequestsUserFromStore()
{
   var mock = new MockRepository();
   var store = mock.DynamicMock<IDataStore>();

   store.Expect(s => s.FindById(94)).Return(null);
   
   store.Replay();
   new UserRepository(store).FindById(94);
   store.VerifyAllExpectations();            
}  

Occasionally, you’ll need to test for more complex parameters. This isn’t a problem if you externally control the parameter (you can just pass it in and use the same default behavior as above), for example:

[Fact]
public void RequestsUserFromStore()
{
   var mock = new MockRepository();
   var store = mock.DynamicMock<IDataStore>();
   var user = new User();

   store.Expect(s => s.Update(user));
   
   store.Replay();
   new UserRepository(store).Update(user);
   store.VerifyAllExpectations();            
}  

However, things get a lot trickier when the parameter is built within the method itself. Let’s add a method to our UserRepository:

public User FindByCredentials(string userName, string password)
{ 
   var query = new Query<User>()
       .Where(w => w.Add("UserName", userName).Add("Password", password)); 

   return _store.FindOne(query) ?? User.NullUser;
}

Using some of RhinoMock’s built-in constraints, we can provide some level of coverage. We could, for example, make sure that our method doesn’t pass a null query:

_store.Expect(s => s.FindOne(Arg<Query<User>>.Is.NotNull)).Return(null);

However, this isn’t very solid – we could make any number of changes to FindByCredentials without breaking this test – which isn’t good given that the purpose of the test pretty much revolves around the properness of our query.

At this point, you have two solutions. The first isn’t particularly elegant, but it’s great for one-off solutions. We simply build our query in a different method, and mock that. Our repository thus becomes:

public User FindByCredentials(string userName, string password)
{ 
   var query = CreateFindByCredentialsQuery(userName, password);
   return _store.FindOne(query) ?? User.NullUser;
}

internal virtual IQuery<User> CreateFindByCredentialsQuery(string userName, string password)
{
   return new Query<User>()
       .Where(w => w.Add("UserName", userName).Add("Password", password)); 
}

which in turn can be tested with the following code:

[Fact]
public void RequestsUserFromStore()
{
   var mock = new MockRepository();
   var store = mock.DynamicMock<IDataStore>();
   var repository = mock.PartialMock<UserRepository>(store);
   var query = new Query<User>();

   repository.Stub(r => r.CreateFindByCredentialsQuery(null, null)).IgnoreArguments().Return(query);
   store.Expect(s => s.FindOne(query)).Return(null);
   
   mock.ReplayAll();
   repository.FindByCredentials("a", "b");
   store.VerifyAllExpectations();            
}

The problem with the above code is that we’ve created additional code to test. We not only have to test CreateFindByCredentialsQuery, but also the interaction between it and FindByCredentials. Extracting to a virtual method (methods must be virtual to be mocked from a Partial) is a useful trick when no alternatives exists (say when using FormsAuthentication.SetAuthCookie), but surely we can come up with better, no?

In fact, we can provide custom constraints. One way to do so is using an inline constraint:

[Fact]
public void RequestsUserFromStore()
{
   var mock = new MockRepository();
   var store = mock.DynamicMock<IDataStore>();

   var constraint = Arg<Query<User>>.Matches(q =>
       q.Where[0].Name == "UserName" && q.Where[0].Operation == Operation.Equals && q.Where[0].Value == "a" &&
       q.Where[1].Name == "Password" && q.Where[1].Operation == Operation.Equals && q.Where[1].Value == "b" &&
       q.Where.Count == 1
   );
   store.Expect(s => s.FindOne(constraint)).Return(null);
   
   mock.ReplayAll();
   new UserRepository(store).FindByCredentials("a", "b");
   store.VerifyAllExpectations();            
}

Again, this works ok for simple cases, but despite having a very simple query our constraint is a mess. Since we’ll likely have a number of tests which must validate our query object, it makes sense to build our own custom constraint classes.

We’ll create two custom constraints, which both inherit from RhinoMocks’ AbstractConstraint class. The first is used to test the number of where clauses:

internal class WhereCount : AbstractConstraint
{
  private readonly int _expected;
  private int _actual;

  public WhereCount(int expected)
  {
      _expected = expected;
  }

  public override bool Eval(object obj)
  {
      var query = obj as IQuery;
      if (query == null)
      {
          _actual = -1;
          return false;
      }
      _actual = query.Where.Count;
  }

  public override string Message
  {
      get { return string.Format("Expected {0} criterias but got {1}", _expected, _actual); }
  }
}

The other validates that a specific where exists:

internal class Where : AbstractConstraint
{
  private readonly string _name;
  private readonly object _value;
  private readonly Operation _operation;

  public Where(string name, object value) : this(name, Operation.Equals, value){}
  public Where(string name, Operation operation, object value)
  {
      _name = name;
      _value = value;
      _operation = operation;
  }

  public override bool Eval(object obj)
  {
      var query = obj as IQuery;
      if (query == null)
      {                
          return false;
      }
      foreach (var where in query.Where)
      {
        if (where.Name == _name && where.Operation == _operation && where.Value.Equals(_value))
        {
            return true;
        }
      }
      return false;
  }

  public override string Message
  {
      get { return "Could not find matching where"; }
  }
}

The last piece of the puzzle is something of a factory which will provide us a clean API when specifying our constraints:

internal static class Q
{
  public static AbstractConstraint Where(string name, object value)
  {
      return new Where(name, value);
  }
  public static AbstractConstraint Where(string name, Operation operation, object value)
  {
      return new Where(name, operation, value);
  }
  public static AbstractConstraint WhereCount(int expected)
  {
      return new WhereCount(expected);
  }
}

Finally, our test, using our new constraints, looks like:

[Fact]
public void RequestsUserFromStore()
{
   var mock = new MockRepository();
   var store = mock.DynamicMock<IDataStore>();

   var constraint = Arg<Query<User>>.Matches
   (
       Q.WhereCount(2) && Q.Where("UserName", "a") && Q.Where("Password", "b")
   );
   store.Expect(s => s.FindOne(constraint)).Return(null);
   
   mock.ReplayAll();
   new UserRepository(store).FindByCredentials("a", "b");
   store.VerifyAllExpectations();            
}

There you have it, complex constraints can be handled one of three different ways: using a partial mock and externalizing the parameter creation, using inline custom constraints or using full blown constraint objects. Hope that helps.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

4 Responses to Don’t be afraid of complex constraints

  1. karl says:

    Leonid:
    It’s a custom query object I’ve been playing with lately. It’s quite simple. It really does nothing more than hold collections of where, joins and other stuff. A “Converter” then takes it and either turns it into SQL or HQL (depending on whether or not the project is using NHibernate)

  2. Where does Query<> object come from?

  3. karl says:

    I’m not sure breaking when the unit under test changes is brittle or effective testing. How would you do it?

  4. Jason says:

    Isn’t the unit test in essence just duplicating the query written in the code then making sure they match? It seems brittle to me.