Skip to content

TUnit.Mocks: generic-method setups with explicit type arguments do not bind on Mock.Of<IGeneric<X>>() #7302

Open

Description

@thomhurst

Description

On a Mock<T> returned by Mock.Of<IGeneric<X>>(), a setup for a generic method fails with CS1061 when the call passes explicit method type arguments. The same call compiles on the typed wrapper that IGeneric<X>.Mock() returns. It also compiles on Mock.Of when the method type arguments can be inferred.

Found while working on #7299. It happens on main without that change, with no type parameter name collision.

Repro (TUnit.Mocks.Tests, net10.0)

Uses the existing IGenericKitchenSink<TItem, TValue> in tests/TUnit.Mocks.Tests/KitchenSinkGenericTests.cs, which declares TState RoundTrip<TState>(TState state) where TState : struct:

var mock = Mock.Of<IGenericKitchenSink<GenericPayload, int>>();
mock.RoundTrip<Guid>(Arg.Any<Guid>());   // CS1061
mock.RoundTrip(Arg.Any<Guid>());         // compiles

var wrapper = IGenericKitchenSink<GenericPayload, int>.Mock();
wrapper.RoundTrip<Guid>(Arg.Any<Guid>()); // compiles
error CS1061: 'Mock<IGenericKitchenSink<GenericPayload, int>>' does not contain a definition for 'RoundTrip' and no accessible extension method 'RoundTrip' accepting a first argument of type 'Mock<IGenericKitchenSink<GenericPayload, int>>' could be found

The generator emits the member in both extension blocks with the same shape:

extension<TItem, TValue>(global::TUnit.Mocks.Mock<global::TUnit.Mocks.Tests.IGenericKitchenSink<TItem, TValue>> mock) where TItem : class
{
    public ..._RoundTrip_M9_MockCall<TState, TItem, TValue> RoundTrip<TState>(global::TUnit.Mocks.Arguments.Arg<TState> state) where TState : struct

and

extension<TItem, TValue>(global::TUnit.Mocks.Tests.IGenericKitchenSink_TItem_TValue_Mock<TItem, TValue> mock) where TItem : class

Only the Mock<...> receiver fails to bind when explicit type arguments are present. A method whose type parameter appears only in the return type, such as T Invoke<T>(object message), therefore cannot be set up through Mock.Of<IGeneric<X>>() at all.

Expected

Mock.Of<IGeneric<X>>().Method<Y>(...) binds the same way as it does on the typed wrapper. If this is a compiler limitation of extension-block type inference, the generator should emit a shape that binds, for example a non-generic-receiver overload per closed registration, or the docs should steer users to .Mock().

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium: edge case, mode divergence, flaky test, or workaround existsbugSomething isn't workingin-progressWork is in progress

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions