Ben Biddington

Whatever it is, it's not about "coding"

RhinoMocks — repeat times

with one comment

My pair and I discovered some unexpected behaviour with repeat times yesterday. It appears repeat times behaves differently when using the Expect than it does when using AAA.

Our expectation fails, i.e., the following test passes, when the actual number of invocations exceeds expected:

[Test]
public void expect_repeat_n_times_does_not_work_when_actual_greater_than_expected() {
    const Int32 ActualTimesToCall = 6;
    const Int32 ExpectedTimesToCall = 4;

    var mock = MockRepository.GenerateMock<IExample>();
    mock.Expect(example => example.ExampleMethod()).Repeat.Times(ExpectedTimesToCall);

    for (var i = 0; i < ActualTimesToCall; i++) {
        mock.ExampleMethod();
    }

    // [?] This one passes
    mock.VerifyAllExpectations();
}

And yet when using AAA, we get the desired outcome (the following test fails):

[Test]
public void aaa_repeat_n_times_does_work_when_actual_greater_than_expected() {
    const Int32 ActualTimesToCall = 6;
    const Int32 ExpectedTimesToCall = 4;

    var mock = MockRepository.GenerateMock<IExample>();

    for (var i = 0; i < ActualTimesToCall; i++) {
        mock.ExampleMethod();
    }

    // This one fails (as expected)
    mock.AssertWasCalled(
        example => example.ExampleMethod(),
        options => options.Repeat.Times(ExpectedTimesToCall)
    );
}

Produces the message:

Rhino.Mocks.Exceptions.ExpectationViolationException: IExample.ExampleMethod();
Expected #4, Actual #6.

As expected.

The happens because of AbstractExpectation.CanAcceptCalls stops recording after range max has been reached, and so the actual number of calls recorded is never greater than the expected number:

// AbstractExpectation
public bool CanAcceptCalls
{
    get
    {
        //I don't bother to check for RepeatableOption.Never because
        //this is handled the method recorder
        if (repeatableOption == RepeatableOption.Any)
            return true;

        return expected.Max == null || actualCallsCount  > actualCallsCount )
            return false;

        if (Expected.Max==null)
            return true;

        // BJB: The following is always true
        return actualCallsCount <= expected.Max.Value;
    }
}

Solution?

I have been advised by someone on the Google group to use AAA syntax instead. And it is nicer to be sure — if a little less discoverable.

AAA and methods that return values

Expect is overloaded to handle return values — it supports Action<T> and Func<T, TReturnValue>. AssertWasCalled on the other hand does not support Func<T, TReturnValue>, so we have to suppress the return value manually in the function:

Stream mockStream = MockRepository.GenerateMock<Stream>();
mockStream.Expect(stream => stream.CanRead).
    Return(true).
    Repeat.Once();
...
mockStream.VerifyAllExpectations();

Becomes something like:

Stream mockStream = MockRepository.GenerateMock<Stream>();
...
mockStream.AssertWasCalled(
    stream => { var temp = stream.CanRead; },
    options => options.Return(true).Repeat.Once()
);
Advertisements

Written by benbiddington

23 June, 2009 at 08:50

One Response

Subscribe to comments with RSS.

  1. Check the version, from memory this is no longer true (AAA and return values).

    Elvis

    27 August, 2009 at 21:03


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: