I have a client interface that looks like this:
public interface IDiscosClient
{
public Task<DiscosResponse<T>?> Get<T>(string queryUrl) where T : DiscosModelBase;
// The rest
}
And DiscosResponse<T>
looks like this:
public record DiscosResponse<T> where T: DiscosModelBase
{
public DiscosResponse()
{
}
internal DiscosResponse(T attributes)
{
Attributes = attributes;
}
[JsonPropertyName("type")]
public ResponseType Type { get; init; }
// TODO - This is wrong an probably needs renaming to something like Object
[JsonPropertyName("attributes")]
public T Attributes { get; init; }
[JsonPropertyName("id")]
[JsonConverter(typeof(JsonStringIntConverter))]
public int Id { get; init; }
}
I want to be able to be able to dynamically create a Substitute.For<T>()
of this interface that will always build and return an instance of T
.
So I know how to construct and call a generic method. I also have AutoFixture
set up so that I can create new instances of T
on demand.
What I don't know, however, is how to then go about telling NSubstitute
to return this new instance when this constructed method is called.
For reference, the usual syntax for doing this without reflection would be:
MyType myMock = Substitute.For<MyType>();
myMock.MyMethod().Returns(myInstance);
Edit:
I've had to put a pin in the AutoFix part of this because it was causing recursion issues. However, I've now come up with this, which seems to work right up until I try and set the return value on the invocation:
private IDiscosClient CreateSubstituteClient(Type type)
{
IDiscosClient client = Substitute.For<IDiscosClient>();
MethodInfo getMethod = typeof(IDiscosClient).GetMethod(nameof(client.Get), new [] {typeof(string)}) ?? throw new MethodAccessException();
MethodInfo constructedGetMethod = getMethod.MakeGenericMethod(type);
Type constructedReturnType = typeof(DiscosResponse<>).MakeGenericType(type);
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
CultureInfo culture = CultureInfo.InvariantCulture;
object returnValue = Activator.CreateInstance(constructedReturnType, flags, null, new [] {Activator.CreateInstance(type)}, culture); // Not using AutoFix as it will cause recursion issues
constructedGetMethod.Invoke(client, new object?[] {Arg.Any<string>()}).Returns(Task.FromResult(returnValue));
return client;
}
At which point it throws this error:
NSubstitute.Exceptions.CouldNotSetReturnDueToTypeMismatchException: Can not return value of type Task
1 for IDiscosClient.Get (expected type Task
1).
Which is confusing because the type of returnValue
is:
DISCOSweb_Sdk.Models.DiscosResponse`1[[DISCOSweb_Sdk.Models.ResponseModels.Reentries.Reentry, DISCOSweb-Sdk, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], DISCOSweb-Sdk, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
And constructedGetMethod.ReturnParameter
is:
System.Threading.Tasks.Task
1[DISCOSweb_Sdk.Models.DiscosResponse
1[DISCOSweb_Sdk.Models.ResponseModels.Reentries.Reentry]]
Which, AFIACT match once I wrap the former in a Task.FromResult