Skip to content

Commit

Permalink
Unable to match arguments whose type is generic, when their concrete…
Browse files Browse the repository at this point in the history
… type is not known (#786)
  • Loading branch information
Mihnea Rădulescu committed May 15, 2024
1 parent da6c174 commit d44759e
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 4 deletions.
35 changes: 31 additions & 4 deletions src/NSubstitute/Core/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,44 @@ public static bool IsCompatibleWith(this object? instance, Type type)
}

var instanceType = instance.GetType();
var compatibleInstanceTypes = GetCompatibleTypes(instanceType);

if (instanceType.IsGenericType && type.IsGenericType
&& instanceType.GetGenericTypeDefinition() == type.GetGenericTypeDefinition())
foreach (var aCompatibleInstanceType in compatibleInstanceTypes)
{
// both are the same generic type. If their GenericTypeArguments match then they are equivalent
return CallSpecification.TypesAreAllEquivalent(instanceType.GenericTypeArguments, type.GenericTypeArguments);
if (aCompatibleInstanceType.IsGenericType &&
type.IsGenericType &&
aCompatibleInstanceType.GetGenericTypeDefinition() == type.GetGenericTypeDefinition())
{
// both are the same generic type. If their GenericTypeArguments match then they are equivalent
return CallSpecification.TypesAreAllEquivalent(
aCompatibleInstanceType.GenericTypeArguments, type.GenericTypeArguments);
}
}

return requiredType.IsInstanceOfType(instance);
}

private static IReadOnlyList<Type> GetCompatibleTypes(Type type)
{
var baseType = type.BaseType;
var interfacesOfType = type.GetInterfaces();

List<Type> compatibleTypes = [type, ..interfacesOfType];

if (baseType is not null)
{
compatibleTypes.AddRange(GetCompatibleTypes(baseType));
}

foreach (var anInterfaceOfType in interfacesOfType)
{
compatibleTypes.AddRange(GetCompatibleTypes(anInterfaceOfType));
}

var distinctCompatibleTypes = compatibleTypes.Distinct().ToList();
return distinctCompatibleTypes;
}

/// <summary>
/// Join the <paramref name="strings"/> using <paramref name="separator"/>.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,25 @@ public void Supports_custom_argument_matcher_descriptions()
Assert.That(ex.Message, Contains.Substring("24 is not forty two"));
}

public interface IMyService
{
void MyMethod<T>(IMyArgument<T> argument);
}
public interface IMyArgument<T> { }
// Suppose I don't have access to this type at compile time, so I could not have written Arg.Any<MyStringArgument>()
public class MyStringArgument : IMyArgument<string> { }

[Test]
public void Supports_matching_covariant_argument()
{
IMyService service = Substitute.For<IMyService>();
var argument = new MyStringArgument();

service.MyMethod(argument);

service.Received().MyMethod(Arg.Any<IMyArgument<Arg.AnyType>>());
}

[SetUp]
public void SetUp()
{
Expand Down

0 comments on commit d44759e

Please sign in to comment.