Skip to content

Commit

Permalink
Implement It.Is, It.IsIn, It.IsNotIn with a comparer overload (devloo…
Browse files Browse the repository at this point in the history
  • Loading branch information
weitzhandler committed Sep 29, 2020
1 parent bd4787f commit e3978f6
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 7 deletions.
74 changes: 72 additions & 2 deletions src/Moq/It.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static TValue IsAny<TValue>()
else
{
return Match.Create<TValue>(
argument => argument == null || argument is TValue,
argument => argument == null || argument is TValue,
() => It.IsAny<TValue>());
}
}
Expand Down Expand Up @@ -108,7 +108,7 @@ public static TValue IsNotNull<TValue>()
else
{
return Match.Create<TValue>(
argument => argument is TValue,
argument => argument is TValue,
() => It.IsNotNull<TValue>());
}
}
Expand Down Expand Up @@ -175,6 +175,30 @@ public static TValue Is<TValue>(Expression<Func<object, Type, bool>> match)
Expression.Lambda<Func<TValue>>(Expression.Call(thisMethod.MakeGenericMethod(typeof(TValue)), match)));
}

/// <summary>
/// Matches any value that equals the <paramref name="value"/> using the <paramref name="comparer"/>.
/// To use the default comparer for the specified object, specify the value inline,
/// i.e. <code>mock.Verify(service => service.DoWork(value))</code>.
/// <para>
/// Use this overload when you specify a value and a comparer.
/// </para>
/// </summary>
/// <param name="value">The value to match with.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{T}"/> with which the values should be compared with.</param>
/// <typeparam name="TValue">Type of the argument to check.</typeparam>
/// <remarks>
/// Allows the specification of a value to compare to, using an <see cref="IEqualityComparer{T}"/>.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TValue Is<TValue>(TValue value, IEqualityComparer<TValue> comparer)
{
var thisMethod = (MethodInfo)MethodBase.GetCurrentMethod();

return Match.Create<TValue>(actual => comparer.Equals(actual, value),
Expression.Lambda<Func<TValue>>(Expression.Call(
thisMethod.MakeGenericMethod(typeof(TValue)), Expression.Constant(value), Expression.Constant(comparer))));
}

/// <summary>
/// Matches any value that is in the range specified.
/// </summary>
Expand Down Expand Up @@ -234,6 +258,29 @@ public static TValue IsIn<TValue>(IEnumerable<TValue> items)
return Match<TValue>.Create(value => items.Contains(value), () => It.IsIn(items));
}

/// <summary>
/// Matches any value that is present in the sequence specified.
/// </summary>
/// <param name="items">The sequence of possible values.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{T}"/> with which the values should be compared with.</param>
/// <typeparam name="TValue">Type of the argument to check.</typeparam>
/// <example>
/// The following example shows how to expect a method call with an integer argument
/// with value from a list.
/// <code>
/// var values = new List&lt;int&gt; { 1, 2, 3 };
///
/// mock.Setup(x => x.HasInventory(
/// It.IsAny&lt;string&gt;(),
/// It.IsIn(values)))
/// .Returns(false);
/// </code>
/// </example>
public static TValue IsIn<TValue>(IEnumerable<TValue> items, IEqualityComparer<TValue> comparer)
{
return Match<TValue>.Create(value => items.Contains(value, comparer), () => It.IsIn(items, comparer));
}

/// <summary>
/// Matches any value that is present in the sequence specified.
/// </summary>
Expand Down Expand Up @@ -276,6 +323,29 @@ public static TValue IsNotIn<TValue>(IEnumerable<TValue> items)
return Match<TValue>.Create(value => !items.Contains(value), () => It.IsNotIn(items));
}

/// <summary>
/// Matches any value that is not found in the sequence specified.
/// </summary>
/// <param name="items">The sequence of disallowed values.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{T}"/> with which the values should be compared with.</param>
/// <typeparam name="TValue">Type of the argument to check.</typeparam>
/// <example>
/// The following example shows how to expect a method call with an integer argument
/// with value not found from a list.
/// <code>
/// var values = new List&lt;int&gt; { 1, 2, 3 };
///
/// mock.Setup(x => x.HasInventory(
/// It.IsAny&lt;string&gt;(),
/// It.IsNotIn(values)))
/// .Returns(false);
/// </code>
/// </example>
public static TValue IsNotIn<TValue>(IEnumerable<TValue> items, IEqualityComparer<TValue> comparer)
{
return Match<TValue>.Create(value => !items.Contains(value, comparer), () => It.IsNotIn(items, comparer));
}

/// <summary>
/// Matches any value that is not found in the sequence specified.
/// </summary>
Expand Down
65 changes: 64 additions & 1 deletion tests/Moq.Tests/CustomTypeMatchersFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using Xunit;
Expand Down Expand Up @@ -148,7 +150,7 @@ public void It_Is_works_with_custom_matcher()
var invocationCount = 0;
var mock = new Mock<IX>();
mock.Setup(m => m.Method(It.Is<IntOrString>((arg, _) => acceptableArgs.Contains(arg))))
.Callback((object arg) => invocationCount++);
.Callback((object arg) => invocationCount++);

mock.Object.Method(42);
mock.Object.Method(7);
Expand All @@ -160,6 +162,46 @@ public void It_Is_works_with_custom_matcher()
Assert.Equal(2, invocationCount);
}

[Fact]
public void It_Is_works_with_custom_comparer()
{
var acceptableArg = "FOO";

var invocationCount = 0;
var mock = new Mock<IX>();
mock.Setup(m => m.Method(It.Is<string>(acceptableArg, StringComparer.OrdinalIgnoreCase)))
.Callback((object arg) => invocationCount++);

mock.Object.Method("foo");
mock.Object.Method("bar");
Assert.Equal(1, invocationCount);

mock.Object.Method("FOO");
mock.Object.Method("foo");
mock.Object.Method((string)null);
Assert.Equal(3, invocationCount);
}

[Fact]
public void It_Is_object_works_with_custom_comparer()
{
var acceptableArg = "FOO";

var invocationCount = 0;
var mock = new Mock<IX>();
mock.Setup(m => m.Method(It.Is<object>(acceptableArg, new ObjectStringOrdinalIgnoreCaseComparer())))
.Callback((object arg) => invocationCount++);

mock.Object.Method("foo");
mock.Object.Method("bar");
Assert.Equal(1, invocationCount);

mock.Object.Method("FOO");
mock.Object.Method("foo");
mock.Object.Method((string)null);
Assert.Equal(3, invocationCount);
}

public interface IX
{
void Method<T>();
Expand Down Expand Up @@ -229,5 +271,26 @@ public struct AnyStruct : ITypeMatcher
{
public bool Matches(Type typeArgument) => typeArgument.IsValueType;
}

public class ObjectStringOrdinalIgnoreCaseComparer : IEqualityComparer<object>
{
private static IEqualityComparer<string> InternalComparer => StringComparer.OrdinalIgnoreCase;

public new bool Equals(object x, object y)
{
var xType = x?.GetType();
var yType = y?.GetType();
Debug.Assert(xType == yType && xType == typeof(string));

return InternalComparer.Equals((string)x, (string)y);
}

public int GetHashCode(object obj)
{
Debug.Assert(obj is string);

return InternalComparer.GetHashCode((string)obj);
}
}
}
}
8 changes: 4 additions & 4 deletions tests/Moq.Tests/ItIsAnyTypeFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void Setup_with_It_IsAnyType_and_Returns()
{
var mock = new Mock<IY>();
mock.Setup(m => m.Method<It.IsAnyType>((It.IsAnyType)It.IsAny<object>()))
.Returns(new Func<object, object>(arg => arg));
.Returns(new Func<object, object>(arg => arg));

Assert.Equal(42, mock.Object.Method<int>(42));
Assert.Equal("42", mock.Object.Method<string>("42"));
Expand Down Expand Up @@ -133,7 +133,7 @@ public void Setup_with_It_IsAny_It_IsAnyType()
object received = null;
var mock = new Mock<IY>();
mock.Setup(m => m.Method(It.IsAny<It.IsAnyType>()))
.Callback((object arg) => received = arg);
.Callback((object arg) => received = arg);

_ = mock.Object.Method<int>(42);
Assert.Equal(42, received);
Expand Down Expand Up @@ -174,7 +174,7 @@ public void Setup_with_It_Is_It_IsAnyType()
var invocationCount = 0;
var mock = new Mock<IY>();
mock.Setup(m => m.Method(It.Is<It.IsAnyType>((x, _) => acceptableArgs.Contains(x))))
.Callback((object arg) => invocationCount++);
.Callback((object arg) => invocationCount++);

_ = mock.Object.Method<string>("42");
Assert.Equal(1, invocationCount);
Expand Down Expand Up @@ -207,7 +207,7 @@ public void Setup_with_It_Ref_It_IsAnyType_IsAny()
object received = null;
var mock = new Mock<IY>();
mock.Setup(m => m.ByRefMethod(ref It.Ref<It.IsAnyType>.IsAny))
.Callback(new ByRefMethodCallback<object>((ref object arg) => received = arg));
.Callback(new ByRefMethodCallback<object>((ref object arg) => received = arg));

var i = 42;
_ = mock.Object.ByRefMethod<int>(ref i);
Expand Down
39 changes: 39 additions & 0 deletions tests/Moq.Tests/MatchersFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ public void MatchesIsInEnumerable()
Assert.Equal(2, mock.Object.Echo(9));
}

[Fact]
public void MatchesIsInEnumerableWithCustomComparer()
{
var acceptableArgs = Enumerable.Repeat("foo", 1);
var unacceptableArgs = Enumerable.Repeat("bar", 1);

var mock = new Mock<IFoo>();

mock.Setup(x => x.Execute(It.IsIn(acceptableArgs, StringComparer.OrdinalIgnoreCase))).Returns("foo");
mock.Setup(x => x.Execute(It.IsIn(unacceptableArgs, StringComparer.OrdinalIgnoreCase))).Returns("bar");

Assert.Equal("foo", mock.Object.Execute("foo"));
Assert.Equal("foo", mock.Object.Execute("FOO"));
Assert.Equal("foo", mock.Object.Execute("FoO"));

Assert.Equal("bar", mock.Object.Execute("bar"));
Assert.Equal("bar", mock.Object.Execute("BAR"));
}

[Fact]
public void MatchesIsInVariadicParameters()
{
Expand Down Expand Up @@ -112,6 +131,26 @@ public void MatchesIsNotInEnumerable()
Assert.Equal(1, mock.Object.Echo(9));
}

[Fact]
public void MatchesIsNotInEnumerableWithCustomComparer()
{
var acceptableArgs = new[] { "foo", "bar" };

var mock = new Mock<IFoo>();

mock.Setup(x => x.Execute(It.IsNotIn(acceptableArgs, StringComparer.OrdinalIgnoreCase))).Returns("foo");

Assert.Equal("foo", mock.Object.Execute("baz"));
Assert.Equal("foo", mock.Object.Execute("alpha"));

Assert.Equal(default, mock.Object.Execute("foo"));
Assert.Equal(default, mock.Object.Execute("FOO"));
Assert.Equal(default, mock.Object.Execute("FoO"));
Assert.Equal(default, mock.Object.Execute("Bar"));
Assert.Equal(default, mock.Object.Execute("BAR"));
Assert.Equal(default, mock.Object.Execute("bar"));
}

[Fact]
public void MatchesIsNotInVariadicParameters()
{
Expand Down

0 comments on commit e3978f6

Please sign in to comment.