Search code examples
c#unit-testingrhino-mocks

Rhino Mocks use input parameters of stubbed function to create return value


I have two interfaces:

interface ITimeframe
{
    DateTime beginTime {get;}
    DateTime endTime {get;}
}

interface ITimeframeFactory
{
     ITimeframe Create(Datetime beginTime, DateTime endTime);
}

My test object uses an ITimeframeFactory to create several ITimeframes. To test my TestObject, I give it a mocked TimeframeFactory. Because my test object processes the created timeframes, the mocked TimeframeFactory needs to create mocked timeframes that return proper values.

StackOverflow: How to stub a function seems to hint in the direction: use WhenCalled:

var mockRepository = new MockRepository();
ITimeframeFactory mockedTimeframeFactory = mockRepository.Stub<ITimeFrameFactory>();

// when the mocked TimeframeFactory is asked to Create a timeframe,
// let it return a new mockedTimeFrame that returns the proper values for BeginTime and EndTime
using (mockRepository.Record())
{
    mockedTimeframeFactory.Stub( (factory) => factory.Create(
        Arg<DateTime>.Is.Anything,
        Arg<DateTime.Is.Anything))
        .WhenCalled( (call) =>
        {
            DateTime beginTime = (DateTime)call.Arguments[0];
            DateTime endTime = (DateTime)call.Arguments[1];

            // mock a new ITimeframe;
            // this ITimeframe should return beginTime and endtime:
            ITimeframe createdTimeframe = mockRepository.Stub<ITimeframe>();
            createdTimeframe.Stub((timeframe) => timeframe.BeginTime).Return(beginTime);
            createdTimeframe.Stub((timeframe) => timeframe.EndTime).Return(endTime);
            call.ReturnValue = createdTimeframe;
        });
}

Usage:

using (mockRepository.Playback())
{
    DateTime beginTime = new DateTime(2020, 1, 1);
    DateTime endTime = new DateTime(2019, 2, 2);

    ITimeframe createdTimeframe = mockedFactory.Create(beginTime, endTime);
    Assert.IsNotNull(createdTimeframe);

    DateTime mockedBeginTime = createdTimeframe.BeginTime;
    DateTime mockedEndTime = createdTimeframe.EndTime;

    Assert.AreEqual(beginTime, mockedBeginTime);
    Assert.AreEqual(endTime, mockedEndTime);
}

During mockedFactory.Create(...) I see that WhenCalled is processed. During WhenCalled it has the proper beginTime and endTime.

call.ReturnValue also works, because after the call to Create the createdTimeframe is not null.

However, when I want to check the properties of the createdTimeframe I get an exception:

System.InvalidOperationException: 'Previous method 'ITimeframe.get_BeginTime();' requires a return value or an exception to throw.'

It seems that the stub to createdTimeframe.Stub is not valid?

How to solve this?


Solution

  • The solution is easier than I thought: during Playback, when the mocked timeframe is created, this mocked timeframe is still in record state.

    All I have to do is set it in replay state by calling Replay().

    This way I can create several different Timeframes, as the following code shows.

    using (this.mockRepository.Playback())
    {
        List<ITimeframe> createdTimeframes = new List<ITimeframe>();
        DateTime beginTime = new DateTime(2020, 1, 1);
        DateTime endTime = new DateTime(2019, 2, 2);
    
    
        for (int i = 0; i < 10; ++i)
        {
            Timeframe createdTimeframe = this.mockedFactory.Create(beginTime, endTime);
    
            // This should be a new Timeframe, not returned before
            Assert.IsNotNull(createdTimeframe);
            Assert.IsFalse(createdTimeframes.Contains(createdTimeframe));
            createdTimeframes.Add(createdTimeframe);
    
            // before using the stubs of this timeframe set in in playback:
            createdTimeframe.Replay();
            DateTime mockedBeginTime = createdTimeframe.BeginTime;
            DateTime mockedEndTime = createdTimeframe.EndTime;
            Assert.AreEqual(beginTime, mockedBeginTime);
            Assert.AreEqual(endTime, mockedEndTime);
    
            // change beginTime and endTime to create a new timeframe
            beginTime = beginTime.AddMonths(i);
            endTime = endTime.AddMonths(-i);
        }
    }