Skip to main content

Parameter matching

Setups and verifications take one argument per parameter of the mocked member. Each argument decides which calls the setup applies to, or which calls the verification counts. You can pass:

  • a value, which matches by equality (the same as It.Is(value)), or
  • a matcher from the It class, such as It.IsAny<int>().
dispenser.Mock.Setup.Dispense("Dark", It.IsAny<int>()).Returns(true);
dispenser.Mock.Verify.Dispense(It.IsOneOf("Dark", "Milk"), It.IsInRange(1, 5)).AtLeastOnce();

The same matchers work everywhere: in method and indexer setups, in verifications, and for delegates.

Example types used on this page

Besides IChocolateDispenser, this page uses:

public interface IChocolateKitchen
{
int Process(string recipe);
bool ValidateEmail(string email);
bool TryParse(string input, out int amount);
bool Increment(ref int value);
bool Fill(IEnumerable<int> amounts);
bool Pack(IList<string> types);
bool SetTemperature(double celsius);
bool Scan(Span<byte> data);
}

IChocolateKitchen kitchen = IChocolateKitchen.CreateMock();

Basic matchers​

MatcherMatches
It.IsAny<T>()any value of type T
It.Is<T>(value)a value equal to value
It.IsNot<T>(value)any value not equal to value
It.IsOneOf<T>(params values)any of the given values
It.IsNotOneOf<T>(params values)any value that is not one of the given values
It.IsNull<T>()null
It.IsNotNull<T>()any value except null
It.IsTrue() / It.IsFalse()true / false
It.IsInRange(min, max)a value between min and max (inclusive); append .Exclusive() to exclude both bounds
It.Satisfies<T>(predicate)any value for which the predicate returns true
dispenser.Mock.Setup.Dispense(It.Satisfies<string>(type => type.StartsWith("D")), It.IsInRange(1, 10).Exclusive())
.Returns(true);
Mixing values and matchers

For members with up to 4 parameters, you can freely mix values and matchers in any position. For members with more parameters, Mockolate limits the combinations to avoid an explosion of generated overloads: either pass values for all parameters, or use matchers for all of them (wrap a value in It.Is(value)).

Custom equality​

Use .Using(IEqualityComparer<T>) on It.Is(), It.IsOneOf(), It.Contains() or It.SequenceEquals() to provide a custom equality comparison:

kitchen.Mock.Setup.Process(It.Is("hello").Using(StringComparer.OrdinalIgnoreCase))
.Returns(42);

int result = kitchen.Process("HELLO");
// result == 42

Tolerance​

Use .Within(tolerance) on It.Is() to accept values that differ from the expected value by at most the given tolerance. It is available for double, float and decimal (numeric tolerance) as well as DateTime, DateTimeOffset and TimeSpan (TimeSpan tolerance), and for their nullable counterparts:

// Accept a tempering temperature that differs from 31.5 degrees by at most 0.1
kitchen.Mock.Setup.SetTemperature(It.Is(31.5).Within(0.1))
.Returns(true);

bool result = kitchen.SetTemperature(31.45);
// result == true
Tolerance semantics
  • The bound is inclusive, but for double and float it is subject to the usual binary representation error, so values that lie exactly on the bound may or may not match.
  • The tolerance must not be negative (or NaN); otherwise an ArgumentOutOfRangeException is thrown.
  • NaN and the infinities keep matching themselves, just like without a tolerance.
  • For nullable parameters, null only matches null; a tolerance never bridges the gap between null and a value.
  • .Within(tolerance) cannot be combined with .Using(IEqualityComparer<T>), because a custom comparer would replace the tolerance.

Derived types​

Matchers are covariant in their type argument: when a method declares a parameter of a base type, a matcher for a derived type only matches calls whose argument is an instance of that derived type (or a further-derived type):

public abstract class Chocolate { }
public class DarkChocolate : Chocolate { }
public class MilkChocolate : Chocolate { }

public interface IChocolateBox
{
bool Add(Chocolate chocolate);
}

IChocolateBox box = IChocolateBox.CreateMock();
box.Mock.Setup.Add(It.IsAny<DarkChocolate>()).Returns(true);

bool dark = box.Add(new DarkChocolate()); // true, matched the DarkChocolate setup
bool milk = box.Add(new MilkChocolate()); // false, no setup matched

// Verifications are covariant too: only dark-chocolate additions are counted.
box.Mock.Verify.Add(It.IsAny<DarkChocolate>()).Once();

String matching​

It.Matches(pattern) matches strings with the wildcards * (any number of characters) and ? (a single character). Matching is case-insensitive by default:

kitchen.Mock.Setup.Process(It.Matches("foo*")).Returns(1); // starts with "foo"
kitchen.Mock.Setup.Process(It.Matches("*foo")).Returns(2); // ends with "foo"
kitchen.Mock.Setup.Process(It.Matches("*foo*")).Returns(3); // contains "foo"

Append .CaseSensitive() to require an exact case match, or .AsRegex() to treat the pattern as a regular expression:

kitchen.Mock.Setup.Process(It.Matches("Foo*").CaseSensitive())
.Returns(1);

kitchen.Mock.Setup.ValidateEmail(It.Matches(@"^\w+@\w+\.\w+$").AsRegex())
.Returns(true);

// Case-sensitive regex
kitchen.Mock.Setup.Process(It.Matches("^[A-Z]+$").AsRegex().CaseSensitive())
.Returns(1);

To match a literal * or ?, use a regular expression and escape the text: It.Matches(Regex.Escape("a*b") + "$").AsRegex() matches strings that end with a*b.

Collection matching​

MatcherMatches
It.Contains<T>(item)a collection that contains item
It.SequenceEquals<T>(params values)a collection whose elements equal values in the same order

Both matchers support parameters declared as IEnumerable<T>, ICollection<T>, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, T[], List<T>, Queue<T> or Stack<T>. It.Contains<T> additionally supports ISet<T> and HashSet<T>; It.SequenceEquals<T> does not, because their enumeration order is not guaranteed.

kitchen.Mock.Setup.Fill(It.Contains(5))
.Returns(true);
kitchen.Mock.Setup.Pack(It.SequenceEquals("Dark", "Milk", "White"))
.Returns(true);
kitchen.Mock.Setup.Pack(It.Contains("DARK").Using(StringComparer.OrdinalIgnoreCase))
.Returns(true);

bool filled = kitchen.Fill([1, 2, 5]); // true
bool packed = kitchen.Pack(["Dark", "Milk", "White"]); // true

Ref and out parameters​

MatcherIn a setup
It.IsOut<T>(setter)matches any out parameter and assigns the value returned by setter
It.IsAnyOut<T>()matches any out parameter and assigns default(T)
It.IsRef<T>(setter)matches any ref parameter and replaces its value with setter(value)
It.IsRef<T>(predicate, setter)matches ref parameters that satisfy the predicate and replaces their value
It.IsRef<T>(predicate)matches ref parameters that satisfy the predicate, without changing the value
It.IsAnyRef<T>()matches any ref parameter, without changing the value
kitchen.Mock.Setup.TryParse(It.IsAny<string>(), It.IsOut(() => 42))
.Returns(true);

bool success = kitchen.TryParse("abc", out int amount);
// success == true, amount == 42

kitchen.Mock.Setup.Increment(It.IsRef<int>(v => v + 1))
.Returns(true);

int value = 5;
kitchen.Increment(ref value);
// value == 6

In verifications, use It.IsOut<T>() and It.IsRef<T>() without arguments to match any out or ref parameter:

kitchen.Mock.Verify.TryParse("abc", It.IsOut<int>()).Once();

Span parameters (.NET 8+)​

MatcherMatches
It.IsSpan<T>(predicate)Span<T> parameters that satisfy the predicate
It.IsAnySpan<T>()any Span<T> parameter
It.IsReadOnlySpan<T>(predicate)ReadOnlySpan<T> parameters that satisfy the predicate
It.IsAnyReadOnlySpan<T>()any ReadOnlySpan<T> parameter

Spans cannot be stored, so Mockolate copies the span into an array and passes that array to the predicate:

kitchen.Mock.Setup.Scan(It.IsSpan<byte>(data => data.Length > 0))
.Returns(true);

Span<byte> buffer = new byte[] { 1, 2, 3 };
bool result = kitchen.Scan(buffer);
// result == true

Ref struct parameters (.NET 9+)​

On .NET 9 with C# 13 or later, you can also mock members that take custom ref struct parameters (e.g. Utf8JsonReader or your own ref struct Packet), by value and as out, ref and ref readonly:

MatcherMatches
It.IsAnyRefStruct<T>()any ref struct value of type T
It.IsRefStruct<T>(predicate)ref struct values that satisfy the predicate, evaluated at the time of the call
It.IsRefStructBy<T, TKey>(projection)for indexers keyed by a ref struct: projects the key to a value that can be stored
public readonly ref struct Packet(int id, ReadOnlySpan<byte> payload)
{
public int Id { get; } = id;
public ReadOnlySpan<byte> Payload { get; } = payload;
}

public interface IPacketSink
{
void Consume(Packet packet);
int TryParse(Packet packet);
string this[Packet key] { get; set; }
}

IPacketSink sink = IPacketSink.CreateMock();

// Match on the live ref struct, including its span contents
sink.Mock.Setup.Consume(It.IsRefStruct<Packet>(p => p.Payload.Length > 0 && p.Payload[0] == 0xFF))
.Throws<InvalidOperationException>();

// Return a value from a method with a ref struct parameter
sink.Mock.Setup.TryParse(It.IsAnyRefStruct<Packet>()).Returns(42);

// Indexer keyed by a ref struct: Returns for the getter, OnSet to observe writes
sink.Mock.Setup[It.IsAnyRefStruct<Packet>()]
.Returns("got")
.OnSet(value => Console.WriteLine($"written: {value}"));

// Correlate writes and reads by projecting the key to a storable value
sink.Mock.Setup[It.IsRefStructBy<Packet, int>(p => p.Id)].Returns("fallback");
sink[new Packet(1, [])] = "written";
string a = sink[new Packet(1, [])]; // "written", matched by Id
string b = sink[new Packet(2, [])]; // "fallback", nothing was written under Id 2
Limitations

C# does not allow ref struct values in generic delegates, so members with ref struct parameters offer a narrower API:

  • Setup: only Returns, Throws, OnSet(…) (for indexer setters) and SkippingBaseClass are available. .Do(…) and the advanced callback features (InParallel, When, For, Only, TransitionTo) are not.
  • Verify: verifications count the calls, but cannot match on the ref struct value, because the value is not kept after the call. Use a matcher in the setup to filter at call time.
  • Indexer storage: values written through an indexer keyed by a ref struct are not read back by the getter, unless every ref struct key uses It.IsRefStructBy<T, TKey>(projection).

Members that Mockolate cannot support (for example methods that return a custom ref struct, or targets older than .NET 9) are reported by the Mockolate0003 analyzer.

Matching all parameters at once​

For methods without overloads, the Match class lets you match all parameters together:

MatcherMatches
Match.AnyParameters()any combination of parameters
Match.Parameters(Func<object?[], bool>)parameter combinations for which the predicate returns true
dispenser.Mock.Setup.Dispense(Match.Parameters(args =>
args[0] is string type && type.StartsWith("D") &&
args[1] is int amount && amount > 0))
.Returns(true);

dispenser.Mock.Verify.Dispense(Match.AnyParameters()).Twice();

Access parameter values​

Callbacks​

Use .Do(…) on a matcher to run a callback with the value of that parameter whenever the setup matches:

int lastAmount = 0;
dispenser.Mock.Setup.Dispense("Dark", It.IsAny<int>().Do(amount => lastAmount = amount));

dispenser.Dispense("Dark", 42);
// lastAmount == 42

Monitor​

Use .Monitor(out monitor) on a matcher to collect all values passed for that parameter and inspect them afterwards:

dispenser.Mock.Setup.Dispense("Dark", It.IsAny<int>().Monitor(out IParameterMonitor<int> monitor));

dispenser.Dispense("Dark", 5);
dispenser.Dispense("Dark", 7);
// monitor.Values == [5, 7]

IParameterMonitor<T> lives in the Mockolate.Parameters namespace.