A few days ago I wrote a post about how to test your business layer using Rhino Mocks.

Last time I set an expectation on an method that accepts a normal int. Today I'll show how to work on something more complicated: a NHibernate Expression.

Marmot exiting from hibenation

How to Mock the FindOne(ICriterion[])

I've to test a method that returns a user given its username.

   1:  public User GetByUsername(string username)
   2:  {
   3:    User returnUser =
   4:     _repository.FindOne(Expression.Eq("EmailAddress", username));
   5:    if (returnUser == null)
   6:    {
   7:      returnUser = new UnknownUser();
   8:    }
   9:    return returnUser;
  10:  }

The FindOne repository method builds a NHibernate Criteria and calls its UniqueResult method.

The test is exactly the same as the one I wrote in the previous post: the only difference is that at line 10 I set a different expectation:

Expect.Call(mockedRepository.FindOne(
  Expression.Eq("EmailAddress", "email@address.com"))
  ).Return(expectedUser);

This tells the mocked repository to expect a call to the FindOne method with the parameter Expression.Eq.

Unfortunately the Expression.Eq method returns a different Expression each time it is called, so the default reference equality performed by Rhino Mocks to understand if the call is among the ones expected will fail.

But Ayende thought about this scenario, so Rhino Mocks also provide a way to override the default behavior: Constraints.

With constrains you can ask the mock engine to accept, for example, any parameter different from null, or any number less than 10, or any string that match a certain regex pattern and many other operations on the parameters. You can even say to ignore the parameters and always match the expectation.

For example you can ask an hypothetical method that accepts a string parameter to "match" every time the string ends with ".com":

Expect.Call(mocked.testDomain("domain.com") ).Constraints(
       Text.EndsWith(".com")).Return(true);

Unfortunately none of the default constrains are helping with the NH Expression object. So I have to create my own constraint extending the AbstractConstraint.

Extending Rhino Mocks AbstractConstraint

Creating a new Constraint is pretty easy: you just inherit from AbstractConstraint and implements two methods plus the constructor:

  • constructor: it sets up the rules specific to your constraint, and is called during the setup of the expectation
  • bool Eval (object): this is the method that is called by the mocking engine to verify if the parameter is among that ones that are expected. The parameter of that method is the parameter that is passed to the call of the mocked object method.
  • string Message: this property is used when all the possible matches fail, and is printed in the debug console.

The problem here is to understand how to get from the array of Expressions something that can be used to test for equality.

Luckily a ICriterion.toString() returns a string of the type "property = value". For example:

ICriterion crit = Expression.Eq("EmailAddress", "email@address.com");
string str = crit.toString(); //EmailAddress = email@address.com

So, since I'm dealing with an array, I loop through all the elements in the array, and if one match with the expected string I return true.

public class NHSimpleExpressionConstraint:
                        AbstractConstraint
{
  private string _testStr;

  public NHSimpleExpressionConstraint(string testStr)
  {
     _testStr = testStr;
  }

  public override bool Eval(object obj)
  {
     ICriterion[] nhCriteria = obj as ICriterion[];
     if (nhCriteria.Length == 0)
        return false;
     foreach (ICriterion criterion in nhCriteria)
     {
        if(criterion.ToString().Equals(_testStr))
           return true;
     }
     return false;
  }

  public override string Message
  {
     get { return "Testing for " + _testStr; }
  }
}

Using a NHSimpleExpressionConstraint

Now that I've this new type of constraint I can use it on my mock object to verify that the Expression used in the call is the one I'm expecting.

Expect.Call(mockedRepository.FindOne(
          Expression.Eq("EmailAddress", "email@address.com"))
          ).Constraints(
          new NHSimpleExpressionConstraint("EmailAddress = email@address.com")
          ).Return(expectedUser);

The Constraint above works only with SimpleExpression (Eq, Lt, Gt, ...) not with more complex expressions. But it will be easy to extend to support also more complex expressions whose toString doesn't return a string in the format "property = value".

Technorati tags: , , ,

kick it on DotNetKicks.com