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
Comments
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.
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. |
The reason I'm using dynamic rather than object here is that I want to return the 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. |
There's no dynamic type,
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. |
I'm wrestling with this a bit too. I need to set up an Expando in my test:
I would normally stub it like this:
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 UPDATE: Hmm. The following actually appears to work. (I'm not sure why my syntax highlighting was suggesting this was an error before.)
|
If 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. |
Hi @alexandrnikitin. I'm currently on v1.7.2.0.
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); |
You have three ways here:
dynamic d = new ExpandoObject();
d.Text = "Test";
sut.ReturnsExpandoObject().Returns((ExpandoObject)d); Sidenote: I suppose you use |
OK, yes. Then I'd also have to cast back to 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! |
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. |
@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. |
@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. |
At least as of 1.10.0.0 (possible earlier) the methods described in this thread to work around this do not work anymore. 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. 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. |
Thanks @sporieg. The task-specific I'm not sure why it is resolving this |
Yes. One option for signlar is to proxy client/server calls using Dynamics so IHubCallerConnectionContext |
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
throw another exception telling you that "The call is ambiguos". |
@jcfiorenzano Your example is the same as the OP's. To summarize the issue:
Will compile to this IL code:
Note that
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. |
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:
In my test code, I set up a mock as follows:
Then when setting the return object for the Data property, it only works if I use:
If I use the more conventional syntax:
The following exception is thrown:
The text was updated successfully, but these errors were encountered: