Skip to content

Commit

Permalink
Merge pull request #900 from stakx/unmatchable-matchers-recognition
Browse files Browse the repository at this point in the history
Fail when implicit conversion operator renders argument matcher unmatchable
  • Loading branch information
stakx committed Aug 23, 2019
2 parents b3e26cb + 52eaecd commit 433c3ed
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -17,6 +17,8 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1

* Subscription to mocked events used to be handled less strictly than subscription to regular CLI events. As with the latter, subscribing to mocked events now also requires all handlers to have the same delegate type. (@stakx, #891)

* Moq will throw when it detects that an argument matcher will never match anything due to the presence of an implicit conversion operator. (@michelcedric, #897, #898)

#### Added

* Added support for setup and verification of the event handlers through `Setup[Add|Remove]` and `Verify[Add|Remove|All]` (@lepijohnny, #825)
Expand Down
20 changes: 18 additions & 2 deletions src/Moq/MatcherFactory.cs
Expand Up @@ -11,6 +11,8 @@
using Moq.Matchers;
using Moq.Properties;

using TypeNameFormatter;

namespace Moq
{
internal static class MatcherFactory
Expand Down Expand Up @@ -88,10 +90,24 @@ internal static class MatcherFactory
}
return new Pair<IMatcher, Expression>(new ParamArrayMatcher(matchers), Expression.NewArrayInit(elementType, initializers));
}
else
else if (argument.NodeType == ExpressionType.Convert)
{
return MatcherFactory.CreateMatcher(argument);
var convertExpression = (UnaryExpression)argument;
if (convertExpression.Method?.Name == "op_Implicit")
{
if (!parameter.ParameterType.IsAssignableFrom(convertExpression.Operand.Type) && convertExpression.Operand.IsMatch(out _))
{
throw new ArgumentException(
string.Format(
Resources.ArgumentMatcherWillNeverMatch,
convertExpression.Operand.ToStringFixed(),
convertExpression.Operand.Type.GetFormattedName(),
parameter.ParameterType.GetFormattedName()));
}
}
}

return MatcherFactory.CreateMatcher(argument);
}

public static Pair<IMatcher, Expression> CreateMatcher(Expression expression)
Expand Down
9 changes: 9 additions & 0 deletions src/Moq/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Moq/Properties/Resources.resx
Expand Up @@ -363,4 +363,7 @@ This could be caused by an unrecognized type conversion, coercion, narrowing, or
<data name="SetupNotEventRemove" xml:space="preserve">
<value>Expression is not an event remove: {0}</value>
</data>
<data name="ArgumentMatcherWillNeverMatch" xml:space="preserve">
<value>Matcher '{0}' is unmatchable: An implicit conversion operator will convert arguments of type '{1}' to the parameter's type '{2}', which is assignment-incompatible.</value>
</data>
</root>
40 changes: 40 additions & 0 deletions tests/Moq.Tests/Regressions/IssueReportsFixture.cs
Expand Up @@ -2804,6 +2804,46 @@ public interface IFoo

#endregion

#region 897

public class Issue897
{
private readonly List<int> data;

public Issue897()
{
this.data = new List<int> { 1, 2, 3 };
}

[Fact]
public void DateTimeOffset()
{
var serviceMock = new Mock<IMeteringDataServiceAgent>();
serviceMock.Setup(s => s.GetDataList(It.IsAny<DateTimeOffset>())).Returns(this.data);

var result = serviceMock.Object.GetDataList(DateTime.Now);

Assert.Equal(this.data, result);
}

[Fact]
public void DateTimeNotWorking()
{
var serviceMock = new Mock<IMeteringDataServiceAgent>();

Action setup = () => serviceMock.Setup(s => s.GetDataList(It.IsAny<DateTime>()));

Assert.Throws<ArgumentException>(setup);
}

public interface IMeteringDataServiceAgent
{
List<int> GetDataList(DateTimeOffset date);
}
}

#endregion

// Old @ Google Code

#region #47
Expand Down
61 changes: 61 additions & 0 deletions tests/Moq.Tests/UnmatchableMatchersFixture.cs
@@ -0,0 +1,61 @@
// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD.
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.

using System;

using Xunit;

namespace Moq.Tests
{
public class UnmatchableMatchersFixture
{
[Fact]
public void Matchers_that_are_unmatchable_due_to_implicit_conversion_operator_cause_setup_failure()
{
var mock = new Mock<IX>();
var ex = Assert.Throws<ArgumentException>(() => mock.Setup(x => x.UseB(It.IsAny<A>())));
Assert.Contains("'It.IsAny<UnmatchableMatchersFixture.A>()' is unmatchable", ex.Message);
}

[Fact]
public void Matchers_with_explicit_primitive_type_cast_are_not_considered_unmatchable()
{
var mock = new Mock<IX>();
mock.Setup(x => x.UseLong((int)It.IsAny<long>()));
}

[Fact]
public void Matchers_with_implicit_primitive_type_coercions_are_not_considered_unmatchable_1()
{
var mock = new Mock<IX>();
mock.Setup(x => x.UseLong(It.IsAny<int>()));
}

[Fact]
public void Matchers_with_implicit_primitive_nullable_type_coercions_are_not_considered_unmatchable_2()
{
var mock = new Mock<IX>();
mock.Setup(x => x.UseNullableLong(It.IsAny<long>()));
}

public interface IX
{
void UseB(B arg);
void UseInt(int arg);
void UseLong(long arg);
void UseNullableLong(long? arg);
}

public readonly struct A
{
}

public readonly struct B
{
public static implicit operator B(A a)
{
return new B();
}
}
}
}

0 comments on commit 433c3ed

Please sign in to comment.