Description
Mocking a class that implements IEquatable<T> (with T = the mocked type itself) produces unusable setup: mock.Equals(other).Returns(...) fails to compile because the compiler resolves mock.Equals(...) to object.Equals(object?) (inherited by Mock<T>) instead of to the generator-emitted setup extension method.
Minimal repro
```csharp
public class SelfEquatable : IEquatable
{
public virtual bool Equals(SelfEquatable? other) => ReferenceEquals(this, other);
public override int GetHashCode() => 0;
}
var mock = SelfEquatable.Mock();
var other = new SelfEquatable();
mock.Equals(other).Returns(true); // CS1061 — bool has no Returns
```
Error:
```
error CS1061: 'bool' does not contain a definition for 'Returns' ...
```
Expected
mock.Equals(other).Returns(...) should bind to the generated setup extension and let the user configure and verify the virtual Equals implementation just like any other virtual method.
Actual
mock.Equals(other) binds to the instance method bool object.Equals(object?), which returns bool. The setup extension is never reached, so Equals can't be configured or verified.
Suggested fix
Two options for the generator:
- Emit a disambiguating helper name for members that collide with
object's virtual surface (Equals, GetHashCode, ToString) — e.g. mock.EqualsOf(...).
- Generate an instance method on
Mock<T> (not an extension) with a signature more specific than object.Equals(object?), taking Arg<SelfEquatable?> so overload resolution picks it.
Instructions for the fixing agent
Work test-first. Before touching the generator:
- Add a dedicated test that reproduces this failure. The entry point already exists: unskip and restore the
T8 block in TUnit.Mocks.Tests/KitchenSinkEdgeCasesTests.cs (both the SelfEquatable type and the T8_Self_Referential_IEquatable_Mockable test) and confirm it fails with the exact error above in the current codebase.
- Implement the fix in the generator (see Suggested fix).
- Re-run the test and confirm it passes. The test must cover:
Returns(...) actually intercepts the Equals call (setup works).
WasCalled/WasNeverCalled tracks the call (verification works).
- Both the direct
mock.Object.Equals(other) path and the IEquatable<T>.Equals interface-cast path route to the same setup.
- Run the full
TUnit.Mocks.Tests and TUnit.Mocks.SourceGenerator.Tests suites to confirm no regression.
- The test must stay in the suite so any future regression is caught in CI.
Context
Found while adding KitchenSink edge-case coverage in #5674. Tracked in KitchenSinkEdgeCasesTests.cs as T8 SKIPPED.
Description
Mocking a class that implements
IEquatable<T>(withT= the mocked type itself) produces unusable setup:mock.Equals(other).Returns(...)fails to compile because the compiler resolvesmock.Equals(...)toobject.Equals(object?)(inherited byMock<T>) instead of to the generator-emitted setup extension method.Minimal repro
```csharp
public class SelfEquatable : IEquatable
{
public virtual bool Equals(SelfEquatable? other) => ReferenceEquals(this, other);
public override int GetHashCode() => 0;
}
var mock = SelfEquatable.Mock();
var other = new SelfEquatable();
mock.Equals(other).Returns(true); // CS1061 — bool has no Returns
```
Error:
```
error CS1061: 'bool' does not contain a definition for 'Returns' ...
```
Expected
mock.Equals(other).Returns(...)should bind to the generated setup extension and let the user configure and verify the virtual Equals implementation just like any other virtual method.Actual
mock.Equals(other)binds to the instance methodbool object.Equals(object?), which returnsbool. The setup extension is never reached, so Equals can't be configured or verified.Suggested fix
Two options for the generator:
object's virtual surface (Equals,GetHashCode,ToString) — e.g.mock.EqualsOf(...).Mock<T>(not an extension) with a signature more specific thanobject.Equals(object?), takingArg<SelfEquatable?>so overload resolution picks it.Instructions for the fixing agent
Work test-first. Before touching the generator:
T8block inTUnit.Mocks.Tests/KitchenSinkEdgeCasesTests.cs(both theSelfEquatabletype and theT8_Self_Referential_IEquatable_Mockabletest) and confirm it fails with the exact error above in the current codebase.Returns(...)actually intercepts the Equals call (setup works).WasCalled/WasNeverCalledtracks the call (verification works).mock.Object.Equals(other)path and theIEquatable<T>.Equalsinterface-cast path route to the same setup.TUnit.Mocks.TestsandTUnit.Mocks.SourceGenerator.Testssuites to confirm no regression.Context
Found while adding KitchenSink edge-case coverage in #5674. Tracked in
KitchenSinkEdgeCasesTests.csas T8 SKIPPED.