0

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 Task1 for IDiscosClient.Get (expected type Task1).

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.Task1[DISCOSweb_Sdk.Models.DiscosResponse1[DISCOSweb_Sdk.Models.ResponseModels.Reentries.Reentry]]

Which, AFIACT match once I wrap the former in a Task.FromResult

ScottishTapWater
  • 3,656
  • 4
  • 38
  • 81

1 Answers1

0

Task.FromResult(returnValue) results in runtime type of Task<object> while your method expects Task<DiscosResponse<T>?>. NSubstitute checks compatibility of returned type with(among others) IsAssignableFrom so it throws exception. In this particular case you need to do sth like this

var methodInfo = typeof(Task).GetMethod(nameof(Task.FromResult), BindingFlags.Static | BindingFlags.Public);
var fromResult = methodInfo.MakeGenericMethod(constructedReturnType).Invoke(null, new []{ returnValue});

constructedGetMethod.Invoke(client, new object?[] {Arg.Any<string>()}).Returns(fromResult);

in order for runtime types to be the same.

tpodolak
  • 101
  • 4