views:

825

answers:

3

I know I can do this:

IDateTimeFactory dtf = MockRepository.GenerateStub<IDateTimeFactory>();
dtf.Now = new DateTime();
DoStuff(dtf); // dtf.Now can be called arbitrary number of times, will always return the same value
dtf.Now = new DateTime()+new TimeSpan(0,1,0);  // 1 minute later
DoStuff(dtf); //ditto from above

What if instead of IDateTimeFactory.Now being a property it is a method IDateTimeFactory.GetNow(), how do I do the same thing?

As per Judah's suggestion below I have rewritten my SetDateTime helper method as follows:

 private void SetDateTime(DateTime dt) {
  Expect.Call(_now_factory.GetNow()).Repeat.Any();
  LastCall.Do((Func<DateTime>)delegate() { return dt; });
 }

but it still throws "The result for ICurrentDateTimeFactory.GetNow(); has already been setup." errors.

Plus its still not going to work with a stub....

A: 

You can use Expect.Call to accomplish this. Here's an example using the record/playback model:

using (mocks.Record())
{
   Expect.Call(s.GetSomething()).Return("ABC"); // 1st call will return ABC
   Expect.Call(s.GetSomething()).Return("XYZ"); // 2nd call will return XYZ
}
using (mocks.Playback())
{
   DoStuff(s);
   DoStuff(s);
}
Judah Himango
Yeah, but this only applies if you're calling the stubbed method once within DoStuff() what if you want it to return that result always the same as would happen with a property? You can use .Repeat() but I don't think you can override that.
George Mauer
Plus I don't think this works with stubs
George Mauer
George, ok, that wasn't clear from your post. I'll write up a new answer how you can accomplish this.
Judah Himango
A: 

Ok, so my first answer doesn't work for you because GetSomething may be called multiple times, and you don't know how many times.

You're getting into some complex scenario here -- unknown number of method invocations, yet with different results after DoSomething is called -- I recommend breaking up your unit test to be simpler, or you'll have to have unit tests for your unit tests. :-)

Failing that, here's how you can accomplish what you're trying to do:

bool shouldReturnABC = true;
using (mocks.Record())
{
   Expect.Call(s.GetSomething()).Repeat.Any();

   LastCall.Do((Func<string>)delegate()
   {
      return shouldReturnABC ? "ABC" : "XYZ";
   }
}
using (mocks.Playback())
{
   DoStuff(s);
   shouldReturnABC = false;
   DoStuff(s);
}
Judah Himango
Its not that complex, I'm mocking DateTime.Now so I have a IDateTimeNowFactory with GetNow(); which I want to return the same value until I tell it to jump ahead. I cant imagine this is too edge case, its the same thing as stub properties but with methods
George Mauer
Fair enough. Did my answer solve your problem? I tested it here and it seemed to work correctly.
Judah Himango
Oddly enough, no, still telling me the result has already been set up, let me update my question so its closer to my actual code
George Mauer
Ok, George, I posted yet another answer and have verified it works.
Judah Himango
A: 

George,

Using your updated code, I got this to work:

MockRepository mocks = new MockRepository();

[Test]
public void Test()
{
    IDateTimeFactory dtf = mocks.DynamicMock<IDateTimeFactory>();

    DateTime desiredNowTime = DateTime.Now;
    using (mocks.Record())
    {
        SetupResult.For(dtf.GetNow()).Do((Func<DateTime>)delegate { return desiredNowTime; });
    }
    using (mocks.Playback())
    {
        DoStuff(dtf); // Prints the current time    
        desiredNowTime += TimeSpan.FromMinutes(1);  // 1 minute later    
        DoStuff(dtf); // Prints the time 1 minute from now
    }
}

void DoStuff(IDateTimeFactory factory)
{
    DateTime time = factory.GetNow();
    Console.WriteLine(time);
}

FWIW, I don't believe you can accomplish this using stubs; you need to use a mock instead.

Judah Himango
Thanks for your help Judah
George Mauer
Glad it works for you! FWIW, I emailed Ayende about this post. He might have a better answer for you than I do.
Judah Himango
George, Ayende looked at these answers. He said we could use a closure and use SetupResult.For instead of the Expect.Call(...).Repeat.Any(). Otherwise he didn't object to these answers
Judah Himango
Ayende also said, "You can use BackToRecord to replace expectation from the method." That is to say, you can call SetupResult.For multiple times, just like you envisioned originally, you'd just have to call BackToRecord method before calling it.
Judah Himango