Parameter Matching
Mockolate provides flexible parameter matching for method setups and verifications:
Parameter Matchers
Basic Matchers
It.IsAny<T>(): Matches any value of typeT.It.Is<T>(value): Matches a specific value. Fordouble,float,decimal,DateTime,DateTimeOffsetandTimeSpan(and their nullable counterparts) you can append.Within(tolerance)to accept values within the given tolerance.It.IsNot<T>(value): Matches any value not equal tovalue.It.IsOneOf<T>(params IEnumerable<T> values): Matches any of the given values.It.IsNotOneOf<T>(params IEnumerable<T> values): Matches any value that is not in the given set.It.IsNull<T>(): Matches null.It.IsNotNull<T>(): Matches any non-null value.It.IsTrue()/It.IsFalse(): Matches boolean true/false.It.IsInRange(min, max): Matches a number within the given range. You can append.Exclusive()to exclude the minimum and maximum value.It.Satisfies<T>(predicate): Matches values based on a predicate.
Note:
You can also directly use a value (equivalent to wrapping it in It.Is<T>(value)). For methods and indexers with
up to 4 parameters, you can freely mix matchers with direct values in any position. For members with more than
4 parameters, Mockolate still supports explicit values, but limits arbitrary per-parameter mixing to avoid a
combinatorial explosion of overloads. You may need to use direct values for all explicit-value-capable parameters
and wrap the remaining ones, or use matchers for all parameters.
String Matching
It.Matches(pattern): Matches strings using wildcard patterns (*and?).
Regular Expressions
Use .AsRegex() to enable regular expression matching for It.Matches():
// Example: Match email addresses
sut.Mock.Setup.ValidateEmail(It.Matches(@"^\w+@\w+\.\w+$").AsRegex())
.Returns(true);
bool result = sut.ValidateEmail("user@example.com");
// Case-sensitive regex
sut.Mock.Setup.Process(It.Matches("^[A-Z]+$").AsRegex().CaseSensitive())
.Returns(1);
Ref and Out Parameters
It.IsRef<T>(setter): Matches anyrefparameter and sets a new value using the setter function.It.IsRef<T>(predicate, setter): Matchesrefparameters that satisfy the predicate and sets a new value.It.IsRef<T>(predicate): Matchesrefparameters that satisfy the predicate without changing the value.It.IsAnyRef<T>(): Matches anyrefparameter without restrictions.It.IsOut<T>(setter): Matches anyoutparameter and sets a value using the setter function.It.IsAnyOut<T>(): Matches anyoutparameter without restrictions and sets the parameter to the default value ofT.
// Example: Setup with out parameter
sut.Mock.Setup.TryParse(It.IsAny<string>(), It.IsOut(() => 42))
.Returns(true);
int result;
bool success = sut.TryParse("abc", out result);
// result == 42, success == true
// Example: Setup with ref parameter
sut.Mock.Setup.Increment(It.IsRef<int>(v => v + 1))
.Returns(true);
int value = 5;
sut.Increment(ref value);
// value == 6
Collection Matchers
It.Contains<T>(item): Matches a collection parameter that containsitem.It.SequenceEquals<T>(params IEnumerable<T> values): Matches a collection parameter whose elements equalvaluesin the same order.
Both matchers support method 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 the unordered shapes ISet<T> and HashSet<T>; It.SequenceEquals<T> intentionally
does not, because their enumeration order is not guaranteed.
Append .Using(IEqualityComparer<T>) to either matcher to control element equality.
// Example: Match a list that contains a specific item
sut.Mock.Setup.Process(It.Contains(5))
.Returns(true);
bool result = sut.Process(new[] { 1, 2, 5 });
// result == true
// Example: Match a sequence of items in order
sut.Mock.Setup.Process(It.SequenceEquals("a", "b", "c"))
.Returns(true);
bool match = sut.Process(new[] { "a", "b", "c" });
// match == true
// Example: Case-insensitive containment
sut.Mock.Setup.Process(It.Contains("HELLO").Using(StringComparer.OrdinalIgnoreCase))
.Returns(true);
Custom Equality Comparers
Use .Using(IEqualityComparer<T>) to provide custom equality comparison for It.Is() and It.IsOneOf():
// Example: Case-insensitive string comparison
var comparer = StringComparer.OrdinalIgnoreCase;
sut.Mock.Setup.Process(It.Is("hello").Using(comparer))
.Returns(42);
int result = sut.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:
// Example: Accept a tempering temperature that differs from 31.5 degrees by at most 0.1
sut.Mock.Setup.SetTemperature(It.Is(31.5).Within(0.1))
.Returns(true);
bool result = sut.SetTemperature(31.45);
// result == true
- The bound is inclusive, but for
doubleandfloatit 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 anArgumentOutOfRangeExceptionis thrown. NaNand the infinities keep matching themselves, just like without a tolerance.- For nullable parameters,
nullonly matchesnull; a tolerance never bridges the gap betweennulland a value. .Within(tolerance)cannot be combined with.Using(IEqualityComparer<T>), because a custom comparer would replace the tolerance.
Covariant Type Matching
Parameter matchers are covariant in their type argument: when a method declares a parameter of a base type, you can narrow the match by supplying a matcher for a derived type. Only calls whose actual argument is an instance of that derived type (or a further-derived type) match the setup; calls passing other runtime types fall through.
public abstract class Chocolate { }
public class DarkChocolate : Chocolate { }
public class MilkChocolate : Chocolate { }
public interface IChocolateBox
{
bool Add(Chocolate chocolate);
}
IChocolateBox sut = IChocolateBox.CreateMock();
// Narrow the match to DarkChocolate, even though Add accepts any Chocolate.
sut.Mock.Setup.Add(It.IsAny<DarkChocolate>()).Returns(true);
bool dark = sut.Add(new DarkChocolate()); // true, matched the DarkChocolate setup
bool milk = sut.Add(new MilkChocolate()); // false, no setup matched -> default
// Verifications are covariant too: only dark-chocolate additions are counted.
sut.Mock.Verify.Add(It.IsAny<DarkChocolate>()).Once();
Span Parameters (.NET 8+)
It.IsSpan<T>(predicate): MatchesSpan<T>parameters that satisfy the predicate.It.IsAnySpan<T>(): Matches anySpan<T>parameter.It.IsReadOnlySpan<T>(predicate): MatchesReadOnlySpan<T>parameters that satisfy the predicate.It.IsAnyReadOnlySpan<T>(): Matches anyReadOnlySpan<T>parameter.
Note:
As ref struct types cannot be stored directly, it is converted to an array internally and the predicate receives
this array for evaluation.
// Example: Setup with Span parameter
sut.Mock.Setup.Process(It.IsSpan<byte>(data => data.Length > 0))
.Returns(true);
Span<byte> buffer = new byte[] { 1, 2, 3 };
bool result = sut.Process(buffer);
// result == true
Ref Struct Parameters (.NET 9+)
You can mock methods and indexers that take custom ref struct parameters (e.g. Utf8JsonReader
or your own ref struct Packet) using these matchers:
It.IsAnyRefStruct<T>(): Matches any ref-struct value of typeT.It.IsRefStruct<T>(predicate): Matches ref-struct values that satisfy the predicate. The predicate can read the struct's fields at the time the call is made.It.IsRefStructBy<T, TKey>(projection)/It.IsRefStructBy<T, TKey>(projection, predicate): For ref-struct-keyed indexers, projects the key to an equatable value so writes and reads can be correlated. Works at any arity - apply it to every ref-struct slot and non-ref-struct slots contribute their raw value to the composite dispatch key (see Indexer storage in the remarks).
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; }
}
// Match on the live ref struct, including its Span contents.
sut.Mock.Setup.Consume(It.IsRefStruct<Packet>(p =>
p.Payload.Length > 0 && p.Payload[0] == 0xFF))
.Throws<InvalidOperationException>();
// Return a value from a ref-struct-parameter method.
sut.Mock.Setup.TryParse(It.IsAnyRefStruct<Packet>()).Returns(42);
// Ref-struct-keyed indexer: Returns for get, OnSet for observed writes.
sut.Mock.Setup[It.IsAnyRefStruct<Packet>()]
.Returns("got")
.OnSet(value => { /* observed write */ });
// Correlate writes and reads by projecting the key to an equatable value.
sut.Mock.Setup[It.IsRefStructBy<Packet, int>(p => p.Id)].Returns("fallback");
sut[new Packet(1, [])] = "written";
string a = sut[new Packet(1, [])]; // "written" matched by Id
string b = sut[new Packet(2, [])]; // "fallback" no write under Id=2
Remarks
The ref-struct pipeline uses a narrower API than the rest of Mockolate. A handful of fluent
features are unavailable because the C# language does not let ref struct values flow through
generic delegates:
- Setup surface. Only
Returns(value),Returns(factory),Throws*,OnSet(Action<TValue>)(indexer setters), andSkippingBaseClassare available. The.Do(...)callback and theCallbacks<T>builder (InParallel,When,For,Only,TransitionTo) are not offered for ref-struct parameters. - Verify.
Verifycounts calls to the method but cannot match on the parameter value after the fact - the ref-struct value isn't retained past the call. Use a setup-time matcher to filter at call time. - Indexer storage. By default, values written through a ref-struct-keyed indexer setter are
not read back by the getter. Apply
It.IsRefStructBy<T, TKey>(projection)to every ref-struct slot to enable write-then-read correlation keyed by the projections; non-ref-struct slots contribute their raw value as part of the composite dispatch key. If any ref-struct slot is matched without a projection, storage stays inactive for that setup.
On .NET 9 with C# 13 or later, ref-struct parameters are supported by value and as out, ref
and ref readonly, on interface and class members alike.
The following cases are flagged with diagnostic Mockolate0003:
- Targeting older than .NET 9, or pinning
<LangVersion>below 13 (the feature relies onallows ref struct, a .NET 9 / C# 13 feature). - A custom ref struct in a value position: a method or delegate return, a property type, or an
indexer value. (
Span<T>/ReadOnlySpan<T>are supported in those positions.) - Ref-struct parameters on delegate types,
ref readonly Span<T>/ref readonly ReadOnlySpan<T>included - that ref kind is the one span shape without a wrapper-based emit branch, so it routes through the ref-struct pipeline that delegates do not have.
Members in the latter two groups still compile - the mock keeps the member but emits no setup or
verify surface for it. A virtual class member is not flagged at all, because the mock forwards it
to the wrapped instance or to base and it keeps working. See
Mockolate0003 for what the member does at runtime.
Parameter Predicates
When the method name is unique (no overloads), you can use argument matchers from the Match class for more flexible parameters matching:
Match.AnyParameters(): Matches any parameter combination.Match.Parameters(Func<object?[], bool> predicate): Matches parameters based on a custom predicate.
// Example: Custom parameter predicate
sut.Mock.Setup.Process(Match.Parameters(args =>
args.Length == 2 &&
args[0] is string s && s.StartsWith("test") &&
args[1] is int i && i > 0))
.Returns(true);
bool result = sut.Process("test123", 5);
// result == true
Parameter Interaction
Callbacks
With .Do, you can register a callback for individual parameters of a method setup. This allows you to implement side
effects or checks directly when the method or indexer is called.
Example: Do for method parameter
int lastAmount = 0;
sut.Mock.Setup.Dispense(It.Is("Dark"), It.IsAny<int>().Do(amount => lastAmount = amount));
sut.Dispense("Dark", 42);
// lastAmount == 42
Monitor
With .Monitor(out monitor), you can track the actual
values passed during test execution and analyze them afterward.
Example: Monitor for method parameter
Mockolate.ParameterMonitor<int> monitor;
sut.Mock.Setup.Dispense(It.Is("Dark"), It.IsAny<int>().Monitor(out monitor));
sut.Dispense("Dark", 5);
sut.Dispense("Dark", 7);
// monitor.Values == [5, 7]