NSubstitute
NSubstitute copied to clipboard
Get original method info in ReturnsForAll
Is there currently a way to access the original method/property name inside ReturnsForAll?
I know there is a CallInfo parameter you can access to get any arguments, but that doesn't tell you which method or property on the original class was being called.
I can see a bit further up in the chain there is an ICall instance with a MethodInfo property, but that doesn't get passed across into the CallInfo class.
Not at the moment.
Do you have an example of where this would be useful? (I can imagine a few but if you've got a real example that could be helpful.)
What I'm trying to do is to create a mock of an 'unknown' type and then I would want to map different return values to each property of that class using a dictionary that contains key-value pairs for each property (key is the property name, value is the value I want to return in the mock):
public static object Create(Type mockType, Dictionary<string, object> data)
{
var mock = Substitute.For(new [] { mockType }, null);
mock.ReturnsForAll((callInfo) =>
{
// Here is where I would want to get the name and type of the original property/method
var value = data[propertyName]; // get the value from my dictionary
switch (propertyType) // convert the value depending on the expected type
{
case String:
return value.ToString();
case Int32:
return Convert.ToInt32(value);
}
return value;
});
return mock;
}
Please use my extension method and check if it works for you. ICall exposes MethodInfo. Please provide feedback. I will be trying to commit the feature once I get some feedback.
using System;
using NSubstitute.Core;
using NSubstitute.Routing;
namespace NSubstitute.Extensions
{
public static class ReturnsForAllExtensions
{
public static void ReturnsForAll(this object substitute, Func<ICall, object> returnThis)
{
var callRouter = SubstitutionContext.Current.GetCallRouterFor(substitute);
RecurringRoute.SetRoute(callRouter, returnThis);
}
private class RecurringRoute : IRoute
{
private readonly ICallRouter _callRouter;
private readonly Func<ICall, object> _returnThis;
private RecurringRoute(ICallRouter callRouter, Func<ICall, object> returnThis)
{
_callRouter = callRouter;
_returnThis = returnThis;
}
public static void SetRoute(ICallRouter callRouter, Func<ICall, object> returnThis)
{
var route = new RecurringRoute(callRouter, returnThis);
route.SetRoute();
}
public object Handle(ICall call)
{
SetRoute();
return _returnThis(call);
}
private void SetRoute()
{
_callRouter.SetRoute(_ => this);
}
public bool IsRecordReplayRoute { get; } = true;
}
}
}
@zvirja Do you have any feedback on this implementation ☝️ ?
Well, I don't like that way. It's a bad idea for route to pretend being a record/replay one. It demonstrates that our internal design leaks and I'm happy that this property is no longer present in v4 😊
Also custom route means that a lot of NSubstitute's internal logic is disabled. For instance, the property getters/setters will not work. Potentially, the whole Returns() syntax is flawed as well.
I'd suggest approach based on the custom handlers feature, which suits much better for such scenarios. See example here. It's still very simple, but works much more reliably.
I'm not sure though we need to ship this feature within the library. It's too advanced to be used every day and requires very advanced knowledge of product to not potentially break the core. Probably, somebody could create a new package named NSubstitute.Community.XXX and ship it there.. 🤔