RhinoMocks has been on my radar for a long time, but today is the first day I have managed to use it to good effect.
Up until now, when I wanted to test something, I felt that I had to make a whole database to test my code against. Now, though, I can’t. My domain model is a tiny part of a legacy database. Now, I can build up just the pieces of data I need.
I am going to assume you are using an ORM layer (and if not, why aren’t you?). At work, I’m on nHibernate but your tastes may run to Entity Framework or something else. Whatever it is, so long as you can easily create new domain objects.
My example is based on a small piece of workflow logic. If an Entity meets certain conditions, it is authorized to progress through the workflow. Specifically, we have a student who is trying to apply for a programme limited to Undergraduates. We will go through our requirements and what we need to test, look at a method of testing using a database and then look at how RhinoMocks can help us.
We start with the very basic data entities.
public enum StudentCategory
{
Undergraduate,
Postgraduate
}
public class Student
{
public virtual string Id { get;set; }
public virtual StudentCategory Category { get; set; }
}
public interface IStudentRepository
{
Student GetStudentById(string Id);
}
Now, we worry about the student programme we are trying to assign these poor layabouts, I’m sorry, students to. This is another Entity with AssignedStudents mapped into the database as a One-To-Many relationship.
The Strategy pattern is used here to give the programme its rules on who can be assigned. The class we are implementing only uses one rule. Other options could include using a collection of IAuthorizationRule implementations or implementing IAuthorizationRule with a class that is composed of other rules.
// The behavior we really care about.
public interface IStudentProgramme
{
public bool AddStudent(Student newStudent);
public IEnumerable<Student> AssignedStudents { get; }
}
// Concrete implementation of IStudentProgramme
public class ExampleStudentProgramme : IStudentProgramme
{
private IAuthorizationRule<Student> rule;
private IList<Student> assignedStudents { get; set; }
public ExampleStudentProgramme(IAuthorizationRule<Student> rule)
{
this.rules = rule;
this.assignedStudents = new List<Student>();
}
public int Id { get;set; }
public IEnumerable<Student> AssignedStudents
{
get
{
return assignedStudents;
}
}
public bool AddStudent(Student newStudent)
{
if (rule.Authorize(newStudent))
{
AssignedStudents.Add(newStudent);
return true;
}
return false;
}
}
// Rule used in the Strategy pattern
public class MustBeUnderGrad : IAuthorizationRule<Student>
{
public bool Authorize(Student student)
{
return student.Category == StudentCategory.Undergraduate;
}
}
// Repository Interface
public interface IProgrammeRepository
{
IStudentProgramme GetProgrammeById(string Id);
}
Testing will be performed from the client perspective. They call us from outside our little black box and they don’t care how we fulfill their request. We can test the little bits like the MustBeUnderGrad rules by themselves but that will only tell us about the implementation that we have covered. If we test the unit as a whole it gives us much more confidence in the interface we are providing to others.
There are many ways to call into this kind of component. It could be a windows application, an MVC web app, a web service or something else. Whatever it is, we can provide the following interface to consumers of our component:
public StudentProgrammes
{
private readonly IProgrammeRepository programmeRepository;
private readonly IStudentRepository studentRepository;
public StudentProgrammes(IProgrammeRepository programmeRepository, IStudentRepository studentRepository)
{
this.programmeRepository = programmeRepository;
this.studentRepository = studentRepository;
}
public bool AssignStudentToProgramme(int studentId, int programmeId)
{
Student student = studentRepository.GetStudentById(studentId);
IStudentProgramme programme = programmeRepository.GetProgrammeById(programmeId);
return programme.AddStudent(student);
}
}
Now that we have the example all coded up the next thing to look at is how we can build a test using the database.
[TestMethod]
public void TestAssignUndergraduateReturnsTrue()
{
// Arrange
// Build the nHibernate session and wire up the repositories
ISession session = TestSupport.BuildSessionFactory();
IProgrammeRepository programmeRepository = new ProgrammeRepository(session);
IStudentRepository studentRepository = new StudentRepository(session);
IStudentProgrammes programmes = new StudentProgrammes(programmeRepository, studentRepository);
// Act
bool result = programmes.AssignStudentId(2);
// Assert
Assert.IsTrue(result);
}
That is an elegant way of dealing with code in the main system but building a session and using data from a real database can cause us problems. What if someone else goes to the database and deletes some of your test data? Where are you maintaining it? How are you sure that a particular ID will exercise the test correctly? When things change, you lose your hair trying to find out why.
That’s the database method. Now we look at how Rhinomocks can help us instead.
public void TestAssignUndergraduateReturnsTrue()
{
// Arrange
IStudentProgramme exampleProgramme = new ExampleStudentProgramme(new MustBeUnderGrad())
{
Id = 1
};
Student student = new Student
{
Id = 2,
Category = StudentCategory.Undergraduate
};
IStudentRepository studentRepository = MockRepository.GenerateStub<IStudentRepository>();
IProgrammeRepository programmeRepository = MockRepository.GenerateStub<IProgrammeRepository>();
programmeRepository.Stub(stuboptions => programmeRepository.GetProgrammeById(Args.Is(1))).Return(programme);
studentRepository.Stub(stuboptions => studentRepository.GetStudentById(Args.Is(2))).Return(student);
programmeRepository.Replay();
studentRepository.Replay();
IStudentProgrammes programmes = new StudentProgrammes(programmeRepository, studentRepository);
// Act
bool result = programmes.AssignStudentId(2);
// Assert
Assert.IsTrue(result);
}
What we are doing here is setting up our input objects in our code so that all the properties that we know are needed are set as we expect them. Using MockRepository.GenerateStub we create a magic proxy object that will return a certain object for a certain input. To tell it what those inputs are, we use the .Stub() call. The magic of RhinoMocks uses the lambda function to figure out what we want to do and then the call to .Return() sets the output to be returned for those inputs.
.Replay() then tells the magic proxy objects that they won’t be configured anymore and to act in the way we have told them to.
When the code under test calls the magic proxy objects we give it, it gets the objects we want it to have. Then it runs through all its own logic and if we haven’t screwed up somewhere then it will return the correct value.
This may seem more complicated right now but all the complication is up front. The key benefit is that all of your assumptions are built up here in the test. The only change that can affect your inputs are in your code. We’ve prevented the major cause of non-determinism affecting our test results.
The other key benefit is that you aren’t using other systems that you have to reach across a network or through a process barrier that could slow down tests of logic. If it’s just objects in memory, your tests will blaze through and as we all know, speed is vital in the test-write-retest cycle.
There are ways to clean up what could turn into messy tests too. In most situations, you can factor out the initial object creation because the same or similar objects will be reused a lot. Also, if you’ve got lots of calls being made into other objects, it may be worth refactoring the code under test and see why it is making so many different calls.
One last note. This isn’t test driven development since we’ve written the test last. When this returns green, how do we know that it’s because our code works and not that our test will always succeed? That’s too much for now though, go experiment.
What were your results?