diff --git a/Source/Mockolate/It.IsNot.cs b/Source/Mockolate/It.IsNot.cs
new file mode 100644
index 00000000..d77e116e
--- /dev/null
+++ b/Source/Mockolate/It.IsNot.cs
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using Mockolate.Parameters;
+
+namespace Mockolate;
+
+#pragma warning disable S3453 // This class can't be instantiated; make its constructor 'public'.
+#pragma warning disable S3218 // Inner class members should not shadow outer class "static" or type members
+public partial class It
+{
+ ///
+ /// Matches a parameter that is not equal to .
+ ///
+ public static IIsNotParameter IsNot(T value,
+ [CallerArgumentExpression(nameof(value))]
+ string doNotPopulateThisValue = "")
+ => new ParameterEqualsNotMatch(value, doNotPopulateThisValue);
+
+ ///
+ /// An used for equality comparison.
+ ///
+ public interface IIsNotParameter : IParameter
+ {
+ ///
+ /// Use the specified comparer to determine equality.
+ ///
+ IIsNotParameter Using(IEqualityComparer comparer,
+ [CallerArgumentExpression(nameof(comparer))]
+ string doNotPopulateThisValue = "");
+ }
+
+ [DebuggerNonUserCode]
+ private sealed class ParameterEqualsNotMatch : TypedMatch, IIsNotParameter
+ {
+ private readonly T _value;
+ private readonly string _valueExpression;
+ private IEqualityComparer? _comparer;
+ private string? _comparerExpression;
+
+ public ParameterEqualsNotMatch(T value, string valueExpression)
+ {
+ _value = value;
+ _valueExpression = valueExpression;
+ }
+
+ ///
+ public IIsNotParameter Using(IEqualityComparer comparer, string doNotPopulateThisValue = "")
+ {
+ _comparer = comparer;
+ _comparerExpression = doNotPopulateThisValue;
+ return this;
+ }
+
+ ///
+ protected override bool Matches(T value)
+ {
+ if (_comparer is not null)
+ {
+ return !_comparer.Equals(value, _value);
+ }
+
+ return !EqualityComparer.Default.Equals(value, _value);
+ }
+
+ public override bool Matches(object? value)
+ {
+ if (value is T typedValue)
+ {
+ return Matches(typedValue);
+ }
+
+ return value is not null || Matches(default!);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (_comparer is not null)
+ {
+ return $"It.IsNot({_valueExpression}).Using({_comparerExpression})";
+ }
+
+ return $"It.IsNot({_valueExpression})";
+ }
+ }
+}
+#pragma warning restore S3218 // Inner class members should not shadow outer class "static" or type members
+#pragma warning restore S3453 // This class can't be instantiated; make its constructor 'public'.
diff --git a/Source/Mockolate/It.cs b/Source/Mockolate/It.cs
index 479e449c..1aa9c1d5 100644
--- a/Source/Mockolate/It.cs
+++ b/Source/Mockolate/It.cs
@@ -56,7 +56,7 @@ private abstract class TypedMatch : IParameter, IParameter
/// , if the is a matching parameter
/// of type ; otherwise .
///
- public bool Matches(object? value)
+ public virtual bool Matches(object? value)
{
if (value is T typedValue)
{
diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt
index 4655390c..c4a900da 100644
--- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt
+++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt
@@ -102,6 +102,7 @@ namespace Mockolate
public static Mockolate.Parameters.IParameter IsFalse() { }
public static Mockolate.It.IInRangeParameter IsInRange(T minimum, T maximum, [System.Runtime.CompilerServices.CallerArgumentExpression("minimum")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("maximum")] string doNotPopulateThisValue2 = "")
where T : System.IComparable { }
+ public static Mockolate.It.IIsNotParameter IsNot(T value, [System.Runtime.CompilerServices.CallerArgumentExpression("value")] string doNotPopulateThisValue = "") { }
public static Mockolate.Parameters.IParameter IsNotNull(string? toString = null) { }
public static Mockolate.It.IIsNotOneOfParameter IsNotOneOf(System.Collections.Generic.IEnumerable values) { }
public static Mockolate.Parameters.IParameter IsNull(string? toString = null) { }
@@ -126,6 +127,10 @@ namespace Mockolate
{
Mockolate.It.IIsNotOneOfParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
}
+ public interface IIsNotParameter : Mockolate.Parameters.IParameter
+ {
+ Mockolate.It.IIsNotParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
+ }
public interface IIsOneOfParameter : Mockolate.Parameters.IParameter
{
Mockolate.It.IIsOneOfParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt
index 2e040193..acc48652 100644
--- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt
+++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt
@@ -101,6 +101,7 @@ namespace Mockolate
public static Mockolate.Parameters.IParameter IsFalse() { }
public static Mockolate.It.IInRangeParameter IsInRange(T minimum, T maximum, [System.Runtime.CompilerServices.CallerArgumentExpression("minimum")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("maximum")] string doNotPopulateThisValue2 = "")
where T : System.IComparable { }
+ public static Mockolate.It.IIsNotParameter IsNot(T value, [System.Runtime.CompilerServices.CallerArgumentExpression("value")] string doNotPopulateThisValue = "") { }
public static Mockolate.Parameters.IParameter IsNotNull(string? toString = null) { }
public static Mockolate.It.IIsNotOneOfParameter IsNotOneOf(System.Collections.Generic.IEnumerable values) { }
public static Mockolate.Parameters.IParameter IsNull(string? toString = null) { }
@@ -125,6 +126,10 @@ namespace Mockolate
{
Mockolate.It.IIsNotOneOfParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
}
+ public interface IIsNotParameter : Mockolate.Parameters.IParameter
+ {
+ Mockolate.It.IIsNotParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
+ }
public interface IIsOneOfParameter : Mockolate.Parameters.IParameter
{
Mockolate.It.IIsOneOfParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt
index 852c09c5..7343d1f4 100644
--- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt
+++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt
@@ -90,6 +90,7 @@ namespace Mockolate
public static Mockolate.Parameters.IParameter IsFalse() { }
public static Mockolate.It.IInRangeParameter IsInRange(T minimum, T maximum, [System.Runtime.CompilerServices.CallerArgumentExpression("minimum")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("maximum")] string doNotPopulateThisValue2 = "")
where T : System.IComparable { }
+ public static Mockolate.It.IIsNotParameter IsNot(T value, [System.Runtime.CompilerServices.CallerArgumentExpression("value")] string doNotPopulateThisValue = "") { }
public static Mockolate.Parameters.IParameter IsNotNull(string? toString = null) { }
public static Mockolate.It.IIsNotOneOfParameter IsNotOneOf(System.Collections.Generic.IEnumerable values) { }
public static Mockolate.Parameters.IParameter IsNull(string? toString = null) { }
@@ -112,6 +113,10 @@ namespace Mockolate
{
Mockolate.It.IIsNotOneOfParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
}
+ public interface IIsNotParameter : Mockolate.Parameters.IParameter
+ {
+ Mockolate.It.IIsNotParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
+ }
public interface IIsOneOfParameter : Mockolate.Parameters.IParameter
{
Mockolate.It.IIsOneOfParameter Using(System.Collections.Generic.IEqualityComparer comparer, [System.Runtime.CompilerServices.CallerArgumentExpression("comparer")] string doNotPopulateThisValue = "");
diff --git a/Tests/Mockolate.Tests/ItTests.IsNotTests.cs b/Tests/Mockolate.Tests/ItTests.IsNotTests.cs
new file mode 100644
index 00000000..801ec57c
--- /dev/null
+++ b/Tests/Mockolate.Tests/ItTests.IsNotTests.cs
@@ -0,0 +1,136 @@
+using Mockolate.Parameters;
+
+namespace Mockolate.Tests;
+
+public sealed partial class ItTests
+{
+ public sealed class IsNotTests
+ {
+ [Fact]
+ public async Task ShouldCorrectlyHandleNull()
+ {
+ IMyService sut = IMyService.CreateMock();
+ MyImplementation value1 = new();
+ sut.Mock.Setup.DoSomething(It.IsNot(null!))
+ .Returns(3);
+
+ int result1 = sut.DoSomething(value1);
+ int result2 = sut.DoSomething(null!);
+
+ await That(result1).IsEqualTo(3);
+ await That(result2).IsEqualTo(0);
+ }
+
+ [Fact]
+ public async Task ShouldCorrectlyHandleNullWithCovariance()
+ {
+ IMyService sut = IMyService.CreateMock();
+ MyOtherImplementation value1 = new();
+ sut.Mock.Setup.DoSomething(It.IsNot(null!))
+ .Returns(3);
+
+ int result1 = sut.DoSomething(value1);
+ int result2 = sut.DoSomething(null!);
+
+ await That(result1).IsEqualTo(3);
+ await That(result2).IsEqualTo(0);
+ }
+
+ [Theory]
+ [InlineData(1, true)]
+ [InlineData(5, false)]
+ [InlineData(-5, true)]
+ [InlineData(42, true)]
+ public async Task ShouldMatchWhenNotEqual(int value, bool expectMatch)
+ {
+ IParameter sut = It.IsNot(5);
+
+ bool result = ((IParameter)sut).Matches(value);
+
+ await That(result).IsEqualTo(expectMatch);
+ }
+
+ [Fact]
+ public async Task ShouldSupportCovarianceInSetup()
+ {
+ IMyService sut = IMyService.CreateMock();
+ MyImplementation value1 = new();
+ MyOtherImplementation value2 = new();
+ sut.Mock.Setup.DoSomething(It.IsNot(value1))
+ .Returns(3);
+
+ int result1 = sut.DoSomething(value1);
+ int result2 = sut.DoSomething(value2);
+
+ await That(result1).IsEqualTo(0);
+ await That(result2).IsEqualTo(3);
+ }
+
+ [Fact]
+ public async Task ToString_ShouldReturnExpectedValue()
+ {
+ IParameter sut = It.IsNot("foo");
+ string expectedValue = "It.IsNot(\"foo\")";
+
+ string? result = sut.ToString();
+
+ await That(result).IsEqualTo(expectedValue);
+ }
+
+ [Fact]
+ public async Task ToString_WithComparer_ShouldReturnExpectedValue()
+ {
+ IParameter sut = It.IsNot(4).Using(new AllEqualComparer());
+ string expectedValue = "It.IsNot(4).Using(new AllEqualComparer())";
+
+ string? result = sut.ToString();
+
+ await That(result).IsEqualTo(expectedValue);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(5)]
+ [InlineData(-42)]
+ public async Task WithComparer_ShouldUseComparer(int value)
+ {
+ IParameter sut = It.IsNot(5).Using(new AllEqualComparer());
+
+ bool result = ((IParameter)sut).Matches(value);
+
+ await That(result).IsFalse();
+ }
+
+ public interface IMyBase
+ {
+ int DoWork();
+ }
+
+ public class MyImplementation : IMyBase
+ {
+ public int Progress { get; private set; }
+
+ public int DoWork()
+ {
+ Progress++;
+ return Progress;
+ }
+ }
+
+ public class MyOtherImplementation : IMyBase
+ {
+ public string Output { get; private set; } = "";
+
+ public int DoWork()
+ {
+ Output += "did something\n";
+ return 1;
+ }
+ }
+
+ public interface IMyService
+ {
+ int DoSomething(IMyBase value);
+ }
+ }
+}