Wednesday, May 23, 2007

Unit testing with stubs and Rhino Mocks

I've been using Rhino Mocks for about a year now, and Oren has never failed to impress me with the features he keeps adding on a regular basis.  I needed to test a particular method that accepted an IProfile interface as an argument.  I didn't want to use an existing IProfile implementation I found, I was really interested in just sending the method a stub.  If I used Rhino Mocks to create a mock, I'd have to set a bunch of expectations to get everything set up, but I really just want a stub.  It's a huge pain to set up a stub manually right now, as this would entail creating your own class that implemented IProfile with a basic implementation, etc.  For more information about mocks, stubs, dummy objects and fake objects, check out Fowler's paper on the subject.  Here's the test I created:

[TestMethod]
public void SetPaymentType_WithValidPayment_AddsPaymentFieldToPaymentFields()
{
    MockRepository repo = new MockRepository();
    IProfile profile = repo.Stub<IProfile>();
    IPayment payment = repo.Stub<IPayment>();

    using (repo.Record())
    {
        profile.Payments = new IPayment[] {payment};
        payment.PaymentCode = "CC";
    }

    using (repo.Playback())
    {
        bool result = ProfileHelper.SetPaymentType("TestValue", profile);

        Assert.AreEqual(true, result);
        Assert.AreEqual(1, payment.PaymentFields.Length);

        IField paymentField = payment.PaymentFields[0];

        Assert.AreEqual("PaymentType", paymentField.FieldKey);
        Assert.AreEqual("TestValue", paymentField.FieldValue);
    }
}

The MockRepository object is from Rhino Mocks.  I call the Stub method to generate a stub object for the interfaces I'm interested in, which are specifically the IProfile and IPayment types.  I set the MockRepository to Record to put in the initial values for my stubs.  Note that Rhino Mocks creates the interface types, and nowhere in my code will I create an implementation of IProfile or IPayment.  Rhino Mocks does this for me.  I set the MockRepository back to Playback mode and call the method I wanted to test (ProfileHelper.SetPaymentType).  Notice that the SetPaymentType method modifies the PaymentFields property on the IProfile object, and does it correctly.  I finish out the test making assertions about the values that should be set in the IProfile object.

What's clear from looking at this test is that I'm only concerned about testing the interaction between the ProfileHelper.SetPaymentType method and the IProfile object, but I don't care about the specific implementation of the IProfile object.  If I passed in a specific implementation of an IProfile object, there may be some unwanted side effects that might cause some false positives or false negatives.  Using stubs makes sure I limit the scope of what's being tested only to the method I'm calling.

No comments: