Skip to content

Commit

Permalink
Enable support for user based wildcard matching devlooped#1199
Browse files Browse the repository at this point in the history
  • Loading branch information
adamfk committed Aug 21, 2021
1 parent 2a70b29 commit a251966
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 7 deletions.
30 changes: 23 additions & 7 deletions src/Moq/MatcherFactory.cs
Expand Up @@ -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<int>`, 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()));
}
}
}
}
Expand Down
205 changes: 205 additions & 0 deletions tests/Moq.Tests/Matchers/AnyValueAttributeTests3.cs
@@ -0,0 +1,205 @@
using Xunit;
using System;

/// <summary>
/// Tests for https://github.com/moq/moq4/issues/1199
/// </summary>

namespace Moq.Tests.Matchers.Wildcard
{
using static AutoIsAny; // note using static to simplify syntax

/// <summary>
/// Helper class provided by user
/// </summary>
public abstract class AutoIsAny
{
public static AnyValue _
{
get
{
It.IsAny<object>(); // 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<object>()`
// with the appropriate `It.IsAny<T>()`.
return new AnyValue();
}
}
}

/// <summary>
/// Helper class provided by user. Interfaces implemented via IDE auto explicit interface implementation
/// or Roslyn analyzer/code fix.
/// </summary>
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<int>();
public static implicit operator byte(AnyValue _) => It.IsAny<byte>();
public static implicit operator GearId(AnyValue _) => It.IsAny<GearId>();
public static implicit operator Animal(AnyValue _) => It.IsAny<Animal>();
public static implicit operator Dolphin(AnyValue _) => It.IsAny<Dolphin>();
}


public class Tests
{
Mock<ISomeService> mock;
ISomeService obj;

public Tests()
{
mock = new Mock<ISomeService>();
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()));
}
}


/// <summary>
/// Example enum
/// </summary>
public enum GearId
{
Reverse,
Neutral,
Gear1,
}


/// <summary>
/// Example interface
/// </summary>
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);
}


/// <summary>
/// just a class that implements interface
/// </summary>
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
{

}
}

0 comments on commit a251966

Please sign in to comment.