Skip to content

Configuring two returns on the same generic method with inheritance-related type arguments throws InvalidCastException #974

Description

@OllieOptix

Describe the bug
When two returns are configured on the same generic method, and the two closed type arguments are related by inheritance, configuring the second call throws an InvalidCastException. NSubstitute appears to hand back the value stored for the first configuration while recording the second call, then fails to cast it to the second call's return type.

This works in 5.3.0 and breaks in 6.0.0.

This was found with MediatR's IMediator.Send<TResponse>(IRequest<TResponse>), where different request types resolve TResponse to IResult vs IResult<T>.

To Reproduce
Create and run this console application. When NSubstitute is on version 5.3.0 the application writes "No Errors" to the console. When NSubstitute is on version 6.0.0 the application throws a System.InvalidCastException.

program.cs

using NSubstitute;

var sub = Substitute.For<ISender>();

sub.Send(Arg.Any<NonGenericRequest>()).Returns(Substitute.For<IResult>());
sub.Send(Arg.Any<GenericRequest>()).Returns(Substitute.For<IResult<int>>());
Console.WriteLine("No errors");

public interface IResult { }
public interface IResult<T> : IResult { }

public interface IRequest<TResponse> { }

public class NonGenericRequest : IRequest<IResult> { }
public class GenericRequest : IRequest<IResult<int>> { }

public interface ISender
{
    Task<TResponse> Send<TResponse>(IRequest<TResponse> request);
}

csproj

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net10.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
<!--        <PackageReference Include="NSubstitute" Version="6.0.0" />-->
        <PackageReference Include="NSubstitute" Version="5.3.0" />
    </ItemGroup>

</Project>

Expected behaviour
I would expect this to behave like 5.3.0 and not throw an exception.

Environment:

  • NSubstitute version: 6.0.0
  • Platform: net10 project on Windows

Workaround

Use Arg.Is so it doesn't match the null recording placeholder:

sub.Send(Arg.Is<NonGenericRequest>(r => r != null)).Returns(Substitute.For<IResult>());
sub.Send(Arg.Is<GenericRequest>(r => r != null)).Returns(Substitute.For<IResult<int>>());

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugReported problem with NSubstitute behaviour

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions