23

I'm looking to find out how I can mock a method that returns a different value the second time it is called to the first time. For example, something like this:

public interface IApplicationLifetime
{
    int SecondsSinceStarted {get;}
}

[Test]
public void Expected_mock_behaviour()
{
    IApplicationLifetime mock = MockRepository.GenerateMock<IApplicationLifetime>();

    mock.Expect(m=>m.SecondsSinceStarted).Return(1).Repeat.Once();
    mock.Expect(m=>m.SecondsSinceStarted).Return(2).Repeat.Once();

    Assert.AreEqual(1, mock.SecondsSinceStarted);
    Assert.AreEqual(2, mock.SecondsSinceStarted);
}

Is there anything that makes this possible? Besides implementing a sub for the getter that implements a state machine?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
AlexC
  • 1,646
  • 1
  • 14
  • 26
  • Take a look at ordered and unordered mocks: http://ayende.com/wiki/Rhino%20Mocks%20Ordered%20and%20Unordered.ashx – Maciej May 23 '12 at 13:44
  • 2
    Whats wrong with code you provided? `Repeat.Once()` should work – Sergey Berezovskiy May 23 '12 at 14:56
  • @lazyberezovsky The first call to m.SecondsSinceStarted returns 2, not 1 as expected – AlexC May 23 '12 at 15:14
  • 1
    @AlexC can you provide real test code? Because `Repeat.Once()` will return 1 during first call – Sergey Berezovskiy May 23 '12 at 15:29
  • @lazyberezovsky The code above is real. If I run it with the latest build of RhinoMocks (after replacing m.SecondsSinceStarted with mock.SecondsSinceStarted inside the Asserts - a silly error) then the test fails with: Expected: 1 But was: 2 – AlexC May 23 '12 at 16:12
  • FFFFFFFUUUUUUUUUUUU... If I *run* the test it passes. If I *debug and step through* it fails. Must be evaluating the mock's properties in my locals window or something... – AlexC May 23 '12 at 16:18

3 Answers3

37

You can intercept return values with the .WhenCalled method. Note that you still need to provide a value via the .Return method, however Rhino will simply ignore it if ReturnValue is altered from the method invocation:

int invocationsCounter = 1;
const int IgnoredReturnValue = 10;
mock.Expect(m => m.SecondsSinceLifetime)
    .WhenCalled(mi => mi.ReturnValue = invocationsCounter++)
    .Return(IgnoredReturnValue);

Assert.That(mock.SecondsSinceLifetime, Is.EqualTo(1));
Assert.That(mock.SecondsSinceLifetime, Is.EqualTo(2));

Digging around a bit more, it seems that .Repeat.Once() does indeed work in this case and can be used to achieve the same result:

mock.Expect(m => m.SecondsSinceStarted).Return(1).Repeat.Once();
mock.Expect(m => m.SecondsSinceStarted).Return(2).Repeat.Once();
mock.Expect(m => m.SecondsSinceStarted).Return(3).Repeat.Once();

Will return 1, 2, 3 on consecutive calls.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
k.m
  • 30,794
  • 10
  • 62
  • 86
  • As I understand, for something other than sequential numbers he should create array and return values by index? – Sergey Berezovskiy May 23 '12 at 14:46
  • I agree that this method works, but this is essentially the same as having to implement a state machine, which I mentioned I am trying to avoid (i.e. if I wanted to return 11 on the second call, then I'd need to do something like "if (invocationsCounter == 1) return 1; else if (invocationsCounter == 2) return 11;" which looks very much like a state machine. – AlexC May 23 '12 at 15:08
  • Re: your edit. Not sure if this is some functionality that has changed in the version of RhinoMocks that I am using and the one you are using, but I have run the test that is in my original question, and can tell you it fails (i.e. the first call to SecondsSinceStarted returns 2). Have you got a link to where you found that it does work? – AlexC May 23 '12 at 15:25
  • Seems the problem was carelessness on my part - test passes if I run, but fails if I step through, probably evaluating the property for a watch window somewhere. Answer accepted, ta. – AlexC May 23 '12 at 16:20
7

Simply use

mock.Expect(m=>m.SecondsSinceStarted).Return(1).Repeat.Once();
mock.Expect(m=>m.SecondsSinceStarted).Return(2).Repeat.Once();

This will return 1 during the first call, and 2 during the second call. At least in Rhino Mocks 3.6.0.0.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • This certainly conflicts with what I see when I run the test above – AlexC May 23 '12 at 16:16
  • As mentioned in the accepted answer, this is correct, my test was only failing as I was stepping through and evaluating something inadvertently. Upvoted. – AlexC May 23 '12 at 16:21
0

If time is the determining factor in what the method returns, I would try to abstract out the time passage so you have direct control over it in your tests.

Something like an interface called ITimeProvider with a member called GetElapsedTime() and then create a default implementation to use throughout your application.

Then when you are testing, pass in a mocked ITimeProvider so you control what is perceived by your application to be time passed.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
theMothaShip
  • 1,191
  • 1
  • 12
  • 24
  • Really, the time thing was just a handy example without fully describing what it is i'm trying to test. Even so, though, if I wanted to mock an ITimeProvider, which had a GetElapsedTime() method, the problem would still exist if I wanted to write a unit test which called GetElapsedTime() twice, and wanted my mock ITimeProvider to return a different time for each call. – AlexC May 23 '12 at 15:12
  • Could you have the ITimeProvider be a property dependancy? Thus being able to inject new mocks before each call? Even so, if its not time you're dealing with, my answer is probably worthless... – theMothaShip May 23 '12 at 15:18
  • My question was more about whether RhinoMocks supports this kind of interaction with generated mocks/stubs, and less about how I might work around it, should it not be supported. Thanks though. – AlexC May 23 '12 at 15:22