views:

746

answers:

4

I have TimeMachine class which provides me current date/time values. The class looks like this:

public class TimeMachine
{
 public virtual DateTime GetCurrentDateTime(){ return DateTime.Now; };
 public virtual DateTime GetCurrentDate(){ return GetCurrentDateTime().Date; };
 public virtual TimeSpan GetCurrentTime(){ return GetCurrentDateTime().TimeOfDay; };
}

I'd like to use TimeMachine stub in my tests in such way that I'd just stub the GetCurrentDateTime method and let the other 2 methods use the stubbed GetCurrentDateTime method so as I don't have to stub all the three methods. I tried to do write the test like this:

  var time = MockRepository.GenerateStub<TimeMachine>();
  time.Stub(x => x.GetCurrentDateTime())
   .Return(new DateTime(2009, 11, 25, 12, 0, 0));
  Assert.AreEqual(new DateTime(2009, 11, 25), time.GetCurrentDate());

But the test fails. GetCurrentDate returns default(DateTime) instead of using GetCurrentDateTime stub internally.

Is there any approach I could use to achieve such behavior or is it just some basic conceptual feature of RhinoMocks I don't catch at the moment? I know I could just get a rid of those two GetDate/Time methods and inline the .Date/.TimeOfDay usage, but I'd like to understand whether this is possible at all...

A: 

A stub simply provides canned answers to method and property calls, it knows nothing about the actual implementation of TimeMachine. I'm afraid you will have to set up results for each of the 3 methods (or for the particular method you would like to test).

AdamRalph
I don't think you're right in case of stubbing real classes (not in case of interfaces). The stub just derives from the class and overrides its' methods. If a method isn't virtual it can't be overriden and remains functional as implemented by the real class.
Buthrakaur
Are you sure about that?
AdamRalph
+2  A: 

Change your TimeMachine to an abstract class:

public abstract class TimeMachine
{
    public abstract DateTime GetCurrentDateTime();
    public DateTime GetCurrentDate(){ return GetCurrentDateTime().Date; };
    public TimeSpan GetCurrentTime(){ return GetCurrentDateTime().TimeOfDay; };
}

For production purposes, you can create a concrete implementation of TimeMachine like this:

public class SystemTimeMachine : TimeMachine
{
    public override DateTime GetCurrentDateTime()
    {
        return DateTime.Now;
    }
}

All classes consuming TimeMachine can now be injected with the abstraction, but in production you can wire up your object graph with SystemTimeMachine.

Mark Seemann
+1  A: 

I just found out that it's possible to achieve this by not using virtual on those two methods - it protects the methods from being overriden when generating stub.

public class TimeMachine
{
    public virtual DateTime GetCurrentDateTime(){ return DateTime.Now; };
    public DateTime GetCurrentDate(){ return GetCurrentDateTime().Date; };
    public TimeSpan GetCurrentTime(){ return GetCurrentDateTime().TimeOfDay; };
}

The test passes now.

Buthrakaur
This may make the test pass but methods should only be made virtual if the class is intended to act as a base class and the method is specifically designed to be overridden. It should not be made virtual just for the sake of unit testing.
AdamRalph
Why not? Why not make all methods virtual in sake of extensibility? :) And even though I accept your arguments the GateCurrentDateTime is imo completely ok to be overriden.
Buthrakaur
I agree. Why not? Well almost agree. In Java every method is virtual. But then you were given no compile time safety that you were overriding the right method. That is until annotations saved the day. But .Net has no need for the annotation because it forces you to declare a method as virtual and the overriding method as overrides. But it would be nice if there was a better solution to this.
uriDium
A: 

I'm not sure which version of Rhino.Mocks you're using, but what I would do is make only the GetCurrentDateTime() method virtual (like an earlier poster suggested), then create your mock object using PartialMock(). There are lots of ways to set things up, but the following should work:

var mocks = new MockRepository();
var time = mocks.PartialMock<TimeMachine>();
using (mocks.Record())
{
   Expect.Call(time.GetCurrentDateTime()).Return(new DateTime(2009, 11, 25, 12, 0, 0));
}
using (mocks.Playback())
{
   Assert.AreEqual(new DateTime(2009, 11, 25), time.GetCurrentDate());
}
glaxaco
PartialMocks and this record/playback usage is imo deprecated in RhinoMocks. But this solution is probably equivalent to my previous answer.
Buthrakaur