Ben Biddington

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

Posts Tagged ‘nunit

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