Trying to return the value of a stubbed property from a different property changes the return value of the first.

1,051 views
Skip to first unread message

DHilgarth

unread,
Nov 8, 2011, 6:48:38 AM11/8/11
to NSubstitute
I have an interface like so:

public interface IDateTime
{
DateTime Now { get; }
DateTime Today { get; }
}

And I am stubbing it like so:
var dateTime = Substitute.For<IDateTime>();
dateTime.Now.Returns(new DateTime(2009, 1, 31, 22, 15, 00));
dateTime.Today.Returns(dateTime.Now.Date);

This will result in a substitute that returns default(DateTime) for
dateTime.Today and a DateTime without the time for dateTime.Now.

Changing it to the following yields the expected result:

var dateTime = Substitute.For<IDateTime>();
var now = new DateTime(2009, 1, 31, 22, 15, 00);
dateTime.Now.Returns(now);
dateTime.Today.Returns(now.Date);


Is this a bug or well known behavior - well known at least to
others ;-)

Aaron Powell

unread,
Nov 8, 2011, 7:30:46 AM11/8/11
to nsubs...@googlegroups.com
Verified here, I'd say it's a bug and it's just plain whacky!

Aaron Powell
MVP - Internet Explorer (Development) | FunnelWeb Team Member

http://apowell.me | http://twitter.com/slace | Skype: aaron.l.powell | Github | BitBucket

--
You received this message because you are subscribed to the Google Groups "NSubstitute" group.
To post to this group, send email to nsubs...@googlegroups.com.
To unsubscribe from this group, send email to nsubstitute...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nsubstitute?hl=en.

David Tchepak

unread,
Nov 8, 2011, 7:49:59 AM11/8/11
to nsubs...@googlegroups.com
Yes, this is a bug and a known behaviour. :(
Unfortunately this is hitting into a limitation of the NSubstitute syntax. The actual order of calls for the "dateTime.Today.Returns(dateTime.Now.Date)" line :

  dateTime.Today getter;
  dateTime.Now getter; (evaluating the arg to pass to Returns)
  Returns extension method;

This means when Returns is doing its work it thinks we are setting the return value of the last call which is dateTime.Now, not dateTime.Today as expected.

I'm open to suggestions for ways to fix this, but in the mean time you can fix the immediate problem using deferred execution (so dateTime.Now isn't called immediately):

var dateTime = Substitute.For<IDateTime>();
dateTime.Now.Returns(new DateTime(2009, 1, 31, 22, 15, 00));
dateTime.Today.Returns(x => dateTime.Now.Date); // <<< lambda here

Or the local variable in your second example.

Sorry, I know it's a pain, but hopefully the trade-off to get the general syntax is worth it. It's not a case that seems to come up very often, but when it does it is pretty awful.

As an aside, for this particular case I would be tempted to not use a substitute at all and instead use a TestDateTime class with a setter for Now and a real Today property that works as expected based on the Now value. This will make setting up the tests a bit simpler. Whenever I start needing to push behaviour into my subs (like linking properties) I try to reconsider that part of the design, use the real type, or use a custom test double with the behaviour I need. That's no excuse for the bug; just some advice I've found useful. :)

Hope this helps,
David


DHilgarth

unread,
Nov 8, 2011, 9:18:55 AM11/8/11
to NSubstitute
First of, thanks for the advice. I will consider it.

Now that I stumbled across this problem a few more times, I realized
myself that it must be a well known bug, because it was obvious what
was going on - just what you described. Problem is, you only realize
that's the problem after you debugged the test including SUT at least
once...

Unfortunately, I also have no idea how to solve this in a clean way...

Daniel

David Tchepak

unread,
Nov 8, 2011, 9:25:46 AM11/8/11
to nsubs...@googlegroups.com
I don't think it's obvious what's going on until you consider how the syntax has to work. Which ideally no one would have to do because it just works. Unfortunately the illusion breaks down here.

I'll have a look at documenting some of the gotchas to make it quicker for people to get back up and running once they hit limitations like this (the other big one is calls to non-virtuals members).

Sorry for any hassle this has caused you.

DHilgarth

unread,
Nov 8, 2011, 9:33:47 AM11/8/11
to NSubstitute
No need to apologize, thanks for the great tool! :-)

OK, maybe it is not obvious, but it dawned on me that it must be a
problem with recorded method calls on some stack, after I observed the
behavior multiple times.

BTW, about the manually implemented stub you mentioned.
If I understand it correctly, I would need to make all methods and
properties as non-virtual that I actually want to be executed on my
stub. (At least according to http://nsubstitute.github.com/help/creating-a-substitute/).
However, will validating method calls still work?
Example:
class MyStub : IStub { public void Method() { /* code I want to
execute */ } }

mock = Substitute.For<MyStub>();
...

mock.Received().Method();

David Tchepak

unread,
Nov 8, 2011, 9:47:03 AM11/8/11
to nsubs...@googlegroups.com
For a manual stub I would use:

  public class TestDateTime : IDateTime {
    public DateTime Now { get; set; }
    public DateTime Today { get { return Now.Date; } }
  }

Then in the test:

  var dateTime = new TestDateTime();
  dateTime.Now = new DateTime(2009, 1, 31, 22, 15, 00)); //or set via ctor parameter

i.e. no mocking framework involved. This should be ok because you normally wouldn't be asserting a data object like this received any particular calls; you just want the data used correctly. If you want to stub out calls *and* check they were received then it starts to get messier and we need to either simplify the design (such as by applying "tell don't ask" principle) or settle for pushing more behaviour into substitutes.

But yes, you are right about substituting for classes; you should make everything virtual so the substitute (which proxies the class) can intercept those calls. The current NSubstitute version, 1.2, does not support partial mocks so it can't check something was received and call the original method as well. So for your example you couldn't check mock.Received().Method() and also execute the code in Method().

Regards,
David

DHilgarth

unread,
Nov 8, 2011, 9:53:01 AM11/8/11
to NSubstitute
Thanks, that's what I feared. I am all for cleaning up the
architecture, but unfortunately, I can't do everything at once :-)
Reply all
Reply to author
Forward
0 new messages