Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension methods don't work for dynamic returns #144

Closed
wilhen01 opened this issue Mar 4, 2014 · 16 comments
Closed

Extension methods don't work for dynamic returns #144

wilhen01 opened this issue Mar 4, 2014 · 16 comments

Comments

@wilhen01
Copy link

wilhen01 commented Mar 4, 2014

Just upgraded to 1.7.2 for the fix to #75 and while the code now works, I have to invoke the extension method in an unusual fashion for it to function correctly. So, to illustrate...

I have an interface with a generic return type which I wish to mock:

public interface IRestResponse<out TResource>
{
     string Content { get; }

     TResource Data { get; }

     List<IHeader> Headers { get; }

     HttpStatusCode StatusCode { get; }
}

In my test code, I set up a mock as follows:

var response = Substitute.For<IRestResponse<dynamic>>();

Then when setting the return object for the Data property, it only works if I use:

SubstituteExtensions.Returns(response.Data, objectToReturn);

If I use the more conventional syntax:

response.Data.Returns(employee);

The following exception is thrown:

SetUp : Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Cannot perform runtime binding on a null reference
   at CallSite.Target(Closure , CallSite , Object , Object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
@alexandrnikitin
Copy link
Member

It's a bit unclear to me why do you use dynamic typing while specifying a substitute. I suppose you know the return type here. You can use object type as the generic type parameter because dynamic is always object.

var response = Substitute.For<IRestResponse<object>>();
response.Data.Returns(objectToReturn);

But still the question is open: How to determine that ReturnParameter in the generic interface is dynamically typed. There's no DynamicAttribute as in methods with dynamic keyword. I guess the situation is the same as in this test.

@wilhen01
Copy link
Author

wilhen01 commented Mar 4, 2014

The reason I'm using dynamic rather than object here is that I want to return the response object on another interface which I'm also mocking, and the return type on that interface is defined as IRestResponse<dynamic> not IRestResponse<object>. There is no way to sensibly construct an IRestResponse, hence I'm mocking both the interface which returns it and the object itself. I'll grant you it's an unusual case...

If dynamic typing doesn't support extension methods then I guess there's nothing we can do to actually fix the issue...it would be good to throw a more descriptive exception to point people towards the syntax which works though.

@alexandrnikitin
Copy link
Member

There's no dynamic type, IRestResponse<dynamic> is compiled to IRestResponse<object> with some magic code for invoking methods on the object, a little bit more info in #141. Could you post more code please so I could show you how to test it.

If dynamic typing doesn't support extension methods then I guess there's nothing we can do to actually fix the issue...it would be good to throw a more descriptive exception to point people towards the syntax which works though.

The problem is we can't determine such cases, there are no any flag/attribute in case of generic type with dynamic as its type parameter. I'll try to dig deeper soon.

@rnorbauer
Copy link

I'm wrestling with this a bit too. I need to set up an Expando in my test:

dynamic model = new ExpandoObject();
model.Name = "Jean-luc";

I would normally stub it like this:

SiteConfigurationDependency.GetTemplateModel().Returns(model);

But of course NSub is baffled by the dynamic types.

I have no problem using the extension methods statically as suggested in the test comments and in the above thread. However, I'm unsure of the syntax for stubbing a method call rather than a property (as above). Any hints would be most appreciated. :)

Back story: Yes, I could theoretically change the return type of GetTemplateModel to Object, but I'd rather not do that because, as it is now, I have a somewhat explicit contract in my defining interface that tells consumers precisely what they're going to get back. If it make it just an Object return, the consumer will need to do some type checking before being able to assume that it's an Expando.

UPDATE: Hmm. The following actually appears to work. (I'm not sure why my syntax highlighting was suggesting this was an error before.)

SubstituteExtensions.Returns(SiteConfigurationDependency.GetTemplateModel(), model);

@alexandrnikitin
Copy link
Member

@rnorbauer

If GetTemplateModel() returns dynamic then everything should work. The following code works fine:

public class DynamicCalls
{
    public interface IInterface
    {
        dynamic ReturnsDynamic();
    }

    [Test]
    public void MethodReturnsDynamic()
    {
        var sut = Substitute.For<IInterface>();
        dynamic d = new ExpandoObject();
        d.Text = "Test";
        sut.ReturnsDynamic().Returns(d);

        var result = sut.ReturnsDynamic();

        Assert.That(result.Text, Is.EqualTo("Test"));
    }
}

What version do you use? Could you share more code please. Simplified reproducible test would be great. Thanks.

@rnorbauer
Copy link

Hi @alexandrnikitin. I'm currently on v1.7.2.0.

GetTemplateModel() actually returns an ExpandoObject, which is a dynamic type but not the type dynamic per se (not a distinction I understand comprehensively). I prefer the return type to dynamic, as it makes the interface more explicit so consumers know what they're getting.

This code doesn't compile:

public class DynamicCalls
{
    public interface IInterface
    {
        ExpandoObject ReturnsExpandoObject();
    }

    [Test]
    public void MethodReturnsDynamic()
    {
        var sut = Substitute.For<IInterface>();
        dynamic d = new ExpandoObject();
        d.Text = "Test";
        sut.ReturnsExpandoObject().Returns(d);

        var result = sut.ReturnsExpandoObject();

        Assert.That(result.Text, Is.EqualTo("Test"));
    }
}

As I say, however, if I use the static method, all is well:

SubstituteExtensions.Returns(SiteConfigurationDependency.GetTemplateModel(), model);

@alexandrnikitin
Copy link
Member

@rnorbauer,

ExpandoObject is just a class. It doesn't support dynamic/late binding by itself. You must use dynamic keyword to tell the compiler to use binding at runtime.

You have three ways here:

  1. Use extension method as a static.
  2. Use dynamic keyword in method signature.
  3. Explicitly cast "dynamic" type to ExpandoObject which allows the compiler to use extension methods.
dynamic d = new ExpandoObject();
d.Text = "Test";
sut.ReturnsExpandoObject().Returns((ExpandoObject)d);

Sidenote: I suppose you use ExpandoObject from ReturnsExpandoObject() with dynamic keyword then the second way is preferred for me.

@rnorbauer
Copy link

OK, yes. Then I'd also have to cast back to dynamic for the assert, but that's no big deal. The following code compiles and passes.

public class DynamicCalls
{
    public interface IInterface
    {
        ExpandoObject ReturnsExpandoObject();
    }

    [Test]
    public void MethodReturnsDynamic()
    {
        var sut = Substitute.For<IInterface>();
        dynamic d = new ExpandoObject();
        d.Text = "Test";
        sut.ReturnsExpandoObject().Returns((ExpandoObject)d);

        var result = (dynamic)sut.ReturnsExpandoObject();

        Assert.That(result.Text, Is.EqualTo("Test"));
    }
}

I think the best approach here would be some more helpful documentation on the NSub website on stubbing and mocking with dynamic types. I was able roughly to figure out how things worked by looking through the source code and Github discussions, but for someone new to the framework that can be a little daunting. ;)

Thanks!

@dtchepak
Copy link
Member

Hi @rnorbauer, the lack of docs for this is my fault. Sorry. If you've got a suggested outline or examples you think would help to have documented please email me@davesquared.net or post them here.

@rnorbauer
Copy link

@dtchepak: No need to apologize. NSubstitute is great! Thanks for all your hard work.

I'm honestly probably not the best person to write suggested docs, as working with dynamics is something I almost never do and the concepts are thus a bit new to me. I just happen to be dealing with a quirk of the Razor templating engine that makes ExpandoObjects operated on dynamically more than normally useful, which is why I stumbled into this problem.

@dtchepak
Copy link
Member

@rnorbauer I didn't mean to suggest that you write them - it's on my list to do. But if you can think of anything specific that would have helped you then I'll incorporate that into the docs. At present I'm just going to throw in some information about the static Returns call that is sometimes required.

@sporieg
Copy link

sporieg commented Mar 23, 2016

At least as of 1.10.0.0 (possible earlier) the methods described in this thread to work around this do not work anymore.
Posting what I found here so others can avoid having to dance around the compiler.

I was mocking a signalr hub context like so.

private IHubCallerConnectionContext<dynamic> GenerateClientStubs()
{
  _clients = Substitute.For<IHubCallerConnectionContext<dynamic>>();
  dynamic all = new ExpandoObject();
  all.receiveMessage = new Action<long, string>((id, message) =>  { });
  //SubstituteExtensions.Returns(_clients.User(Arg.Any<string>()), all);  RuntimeException: ambiguous invocation.
  SubstituteExtensions.Returns<dynamic>(_clients.User(Arg.Any<string>()), all);
  return _clients;
}

In version 1.8.1.0 the call

var subbed = _clients.User("FooBar");

would return the dynamic ExpandoObject, all.
After updating to version 1.10.0.0 it now returns the equivalent of

Task.FromResult(all);

The fix was to cast to object like so:

((object)_clients.User(Arg.Any<string>())).Returns<dynamic>((object)all);
//or
SubstituteExtensions.Returns<dynamic>(((object)_clients.User(Arg.Any<string>())), (object)all);

So that you do not invoke the task extension.

@dtchepak
Copy link
Member

Thanks @sporieg. The task-specific Returns was added in 1.9.0, so I'm guess 1.9.0 and above are affected.

I'm not sure why it is resolving this Returns -- the type of _clients.User("") is dynamic?

@sporieg
Copy link

sporieg commented Mar 24, 2016

Yes.

One option for signlar is to proxy client/server calls using Dynamics so IHubCallerConnectionContext
User("string")''' method will return a dynamic in my case. It could have also been configured to use explicit types, which is typically a better idea.

@jcfiorenzano
Copy link

jcfiorenzano commented Jun 17, 2017

If you modify the interface just a little bit the dynamic mocking does not work.

  [TestFixture]
   public class DynamicCalls
   {
       public interface IInterface<T>
       {
           T ReturnsDynamic();
       }

       [Test]
       public void MethodReturnsDynamic()
       {
           var sut = Substitute.For<IInterface<dynamic>>();
           dynamic d = new ExpandoObject();
           d.Text = "Test";

           sut.ReturnsDynamic().Returns(d);

           var result = sut.ReturnsDynamic();

           Assert.That(result.Text, Is.EqualTo("Test"));
       }
   }

In this case the mocking of dynamic throw a exception : "Cannot perform runtime binding on a null reference" and if you use

SubstituteExtensions.Returns(sut.ReturnsDynamic(), d);

throw another exception telling you that "The call is ambiguos".

@alexandrnikitin
Copy link
Member

alexandrnikitin commented Jun 19, 2017

@jcfiorenzano Your example is the same as the OP's.

To summarize the issue:

  1. There's no dynamic type in CLR. All dynamics are objects with compiler magic that handles calls to those objects in a specific way.

  2. NSubstitute can handle dynamic return parameters that are known at compile time.
    For instance, the following interface:

public interface IInterface<T>
{
    dynamic ReturnsDynamic();
    T ReturnsGeneric();
}

Will compile to this IL code:

.class interface nested public auto ansi abstract IInterface`1<T>
{
    .method public hidebysig newslot abstract virtual instance object ReturnsDynamic () cil managed 
    {
        .param [0]
        .custom instance void [System.Core]System.Runtime.CompilerServices.DynamicAttribute::.ctor() = (
            01 00 00 00
        )
    }

    .method public hidebysig newslot abstract virtual instance !T ReturnsGeneric () cil managed 
    {
    }
}

Note that dynamic ReturnsDynamic() returns object and marked with DynamicAttribute. NSubstitute can handle this case successfully because it can extract that info using Reflection API.

  1. Problems appear when you use dynamic types with generics like in IInterface<dynamic>. I don't know any way to extract such information at runtime (the method or return type aren't marked as dynamic). If you are aware of a way to find that, let us know please.

  2. There are few "workarounds":

  • Use dynamic keyword in the method signature. This lets NSubstitute know that the return type is dynamic. See examples in previous comments.
  • Use Returns extension method as static SubstituteExtensions.Returns(...) call. Unfortunatelly that's the compiler limitation: extension methods can't be dynamically dispatched. See examples in previous comments.
  • Use object type instead of dynamic e.g. Substitute.For<IInterface<object>>() and then treat/cast the return type as dynamic dynamic result = sut.ReturnsDynamic(); See examples in previous comments.

I'm going to close the issue. There is nothing we can do at the moment. Again, if you are aware of any way to find out that a return type is "dynamic" then let us know please.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants