From a2519662e81c2d011680689b0dee49195f6166c5 Mon Sep 17 00:00:00 2001 From: adamfk Date: Fri, 20 Aug 2021 22:06:22 -0500 Subject: [PATCH] Enable support for user based wildcard matching #1199 --- src/Moq/MatcherFactory.cs | 30 ++- .../Matchers/AnyValueAttributeTests3.cs | 205 ++++++++++++++++++ 2 files changed, 228 insertions(+), 7 deletions(-) create mode 100644 tests/Moq.Tests/Matchers/AnyValueAttributeTests3.cs diff --git a/src/Moq/MatcherFactory.cs b/src/Moq/MatcherFactory.cs index 62612c348..748985e7f 100644 --- a/src/Moq/MatcherFactory.cs +++ b/src/Moq/MatcherFactory.cs @@ -95,14 +95,30 @@ internal static class MatcherFactory var convertExpression = (UnaryExpression)argument; if (convertExpression.Method?.Name == "op_Implicit") { - if (!parameter.ParameterType.IsAssignableFrom(convertExpression.Operand.Type) && convertExpression.Operand.IsMatch(out _)) + if (convertExpression.IsMatch(out var match)) { - throw new ArgumentException( - string.Format( - Resources.ArgumentMatcherWillNeverMatch, - convertExpression.Operand.ToStringFixed(), - convertExpression.Operand.Type.GetFormattedName(), - parameter.ParameterType.GetFormattedName())); + Type foundType; + + if (match.GetType().IsGenericType) + { + // If match type is `Match`, foundType set to `int` + // Fix for https://github.com/moq/moq4/issues/1199 + foundType = match.GetType().GenericTypeArguments[0]; + } + else + { + foundType = convertExpression.Operand.Type; + } + + if (!parameter.ParameterType.IsAssignableFrom(foundType)) + { + throw new ArgumentException( + string.Format( + Resources.ArgumentMatcherWillNeverMatch, + convertExpression.Operand.ToStringFixed(), + convertExpression.Operand.Type.GetFormattedName(), + parameter.ParameterType.GetFormattedName())); + } } } } diff --git a/tests/Moq.Tests/Matchers/AnyValueAttributeTests3.cs b/tests/Moq.Tests/Matchers/AnyValueAttributeTests3.cs new file mode 100644 index 000000000..1f2f91b82 --- /dev/null +++ b/tests/Moq.Tests/Matchers/AnyValueAttributeTests3.cs @@ -0,0 +1,205 @@ +using Xunit; +using System; + +/// +/// Tests for https://github.com/moq/moq4/issues/1199 +/// + +namespace Moq.Tests.Matchers.Wildcard +{ + using static AutoIsAny; // note using static to simplify syntax + + /// + /// Helper class provided by user + /// + public abstract class AutoIsAny + { + public static AnyValue _ + { + get + { + It.IsAny(); // This allows it to work for interface types. It doesn't hit the implicit conversion guard, because no implicit conversion + // is required for interfaces. Implicit interface conversions also aren't allowed in C#. + // For non-interface types, the AnyValue implicit conversion below will overwrite `It.IsAny()` + // with the appropriate `It.IsAny()`. + return new AnyValue(); + } + } + } + + /// + /// Helper class provided by user. Interfaces implemented via IDE auto explicit interface implementation + /// or Roslyn analyzer/code fix. + /// + public class AnyValue : ISomeService + { + int ISomeService.Calc(int a, int b, int c, int d) + { + throw new NotImplementedException(); + } + + int ISomeService.DoSomething(ISomeService a, GearId b, int c) + { + throw new NotImplementedException(); + } + + int ISomeService.Echo(int a) + { + throw new NotImplementedException(); + } + + int ISomeService.UseAnimal(Animal a) + { + throw new NotImplementedException(); + } + + int ISomeService.UseDolphin(Dolphin a) + { + throw new NotImplementedException(); + } + + int ISomeService.UseInterface(ISomeService a) + { + throw new NotImplementedException(); + } + + public static implicit operator int(AnyValue _) => It.IsAny(); + public static implicit operator byte(AnyValue _) => It.IsAny(); + public static implicit operator GearId(AnyValue _) => It.IsAny(); + public static implicit operator Animal(AnyValue _) => It.IsAny(); + public static implicit operator Dolphin(AnyValue _) => It.IsAny(); + } + + + public class Tests + { + Mock mock; + ISomeService obj; + + public Tests() + { + mock = new Mock(); + obj = mock.Object; + } + + [Fact] + public void Echo_1Primitive() + { + mock.Setup(obj => obj.Echo(_)).Returns(777); + Assert.Equal(777, obj.Echo(1)); + } + + [Fact] + public void Calc_4Primitives() + { + mock.Setup(obj => obj.Calc(_, _, _, _)).Returns(999); + Assert.Equal(999, obj.Calc(1, 2, 3, 4)); + } + + [Fact] + public void UseInterface() + { + mock.Setup(obj => obj.UseInterface(_)).Returns(555); + Assert.Equal(555, obj.UseInterface(null)); + + var realService = new SomeService(); + Assert.Equal(555, obj.UseInterface(realService)); + } + + [Fact] + public void DoSomething_MixedTypes() + { + mock.Setup(obj => obj.DoSomething(_, _, _)).Returns(444); + Assert.Equal(444, obj.DoSomething(null, GearId.Neutral, 1)); + } + + [Fact] + public void UseAnimal() + { + mock.Setup(obj => obj.UseAnimal(_)).Returns(777); + Assert.Equal(777, obj.UseAnimal(new Animal())); + Assert.Equal(777, obj.UseAnimal(new Dolphin())); + } + + [Fact] + public void UseDolphin() + { + mock.Setup(obj => obj.UseDolphin(_)).Returns(888); + Assert.Equal(888, obj.UseDolphin(new Dolphin())); + } + } + + + /// + /// Example enum + /// + public enum GearId + { + Reverse, + Neutral, + Gear1, + } + + + /// + /// Example interface + /// + public interface ISomeService + { + int Echo(int a); + int Calc(int a, int b, int c, int d); + int UseInterface(ISomeService a); + int DoSomething(ISomeService a, GearId b, int c); + int UseAnimal(Animal a); + int UseDolphin(Dolphin a); + } + + + /// + /// just a class that implements interface + /// + public class SomeService : ISomeService + { + public int Calc(int a, int b, int c, int d) + { + throw new NotImplementedException(); + } + + public int DoSomething(ISomeService a, GearId b, int c) + { + throw new NotImplementedException(); + } + + public int Echo(int a) + { + throw new NotImplementedException(); + } + + public int UseAnimal(Animal a) + { + throw new NotImplementedException(); + } + + public int UseDolphin(Dolphin a) + { + throw new NotImplementedException(); + } + + public int UseInterface(ISomeService a) + { + throw new NotImplementedException(); + } + } + + + public class Animal + { + + } + + + public class Dolphin : Animal + { + + } +}