Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to substitute Object.ToString using NSubstitute?

Tags:

c#

nsubstitute

When I try to use NSubstitute 1.7.1.0 to define behaviour of Object.ToString (which is a virtual method), NSubstitute is throwing an exception.

To reproduce:

[Test]
public static void ToString_CanBeSubstituted()
{
    var o = Substitute.For<object>();
    o.ToString().Returns("Hello world");

    Assert.AreEqual("Hello world", o.ToString());
}

The failure:

NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException : Could not find a call to return from.

Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)),
and that you are not configuring other substitutes within Returns() (for example, avoid this: mySub.SomeMethod().Returns(ConfigOtherSub())).

If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member.
Return values cannot be configured for non-virtual/non-abstract members.

Correct use:
    mySub.SomeMethod().Returns(returnValue);

Potentially problematic use:
    mySub.SomeMethod().Returns(ConfigOtherSub());
Instead try:
    var returnValue = ConfigOtherSub();
    mySub.SomeMethod().Returns(returnValue);

Is there a way to make the above test pass?

Is the exception thrown from my naive test a bug or is it "By Design"?

like image 242
Milan Gardian Avatar asked Feb 14 '14 14:02

Milan Gardian


2 Answers

NSubstitute is based on Castle.Core library and uses dynamic proxies to intercept and manage calls. Interception of the Object class' methods is suppressed in both frameworks.

  1. It's suppressed in Castle's IProxyGenerationHook default implementation. You can find the code here. I think there are reasons for that. Sure, it's possible to implement own IProxyGenerationHook that allows Object class' methods interception, but...

  2. NSubstitute also suppresses interceptions of Object's methods and NSubstitute's syntax is the reason. "NSubstitute records the calls made on a substitute, and when we call Returns, it grabs the last call made and tries to configure that to return a specific value." Assume we have the following code:

    var service = Substitute.For<IService>();
    var substitute = Substitute.For<object>();
    service.AMethod(substitute).Returns(1);
    

    We call "AMethod()" here, NSub intercepts the execution and makes its internal things, suppose adds "substitute" value to a dictionary which calls substitute.GetHashCode(). If we would intercept the "GetHashCode()" method then it would be the last recorded call made. NSub would tie the specified return value to it which is wrong. It's nearly impossible to avoid such thing.

like image 121
Alexandr Nikitin Avatar answered Sep 22 '22 19:09

Alexandr Nikitin


You're going to have trouble substituting any methods from System.Object as it's treated very specially by .Net.

For example, this also fails for the same reason:

[Test]
public void x()
{
    var o = Substitute.For<anything>();
    o.GetHashCode().Returns(3);
    Assert.AreEqual(4, o.GetHashCode());
}

But this works fine:

public class Anything {
    public virtual string Else() {
        return "wrong";
    }
}

[Test]
public void x() {
    var o = Substitute.For<Anything>();
    o.Else().Returns("right");
    Assert.AreEqual("right", o.Else());
}

Sorry I can't give better news, but mocking low level objects doesn't work very well in .Net.

like image 37
Iain Ballard Avatar answered Sep 18 '22 19:09

Iain Ballard