Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fail when implicit conversion operator renders argument matcher unmatchable #900

Merged
merged 3 commits into from Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
}
}
}
}