Skip to content

TUnit.Mocks: mocking one type through both Mock() and Mock.Wrap aborts the generator (duplicate hintName) #6834

Closed

Description

@thomhurst

Description

Reaching the same type through both a regular mock (T.Mock() / Mock.Of<T>()) and Mock.Wrap(new T()) in the same compilation aborts the source generator:

warning CS8785: Generator 'MockGenerator' failed to generate source. It will not contribute to the
output and compilation errors may occur as a result. Exception was of type 'ArgumentException' with
message 'The hintName 'MyNamespace_MyType_MockImplFactory.g.cs' of the added source file must be
unique within a generator. (Parameter 'hintName')'

Because the generator aborts, every mock in the compilation disappears and the build fails with a
cascade of CS1061 "does not contain a definition for ..." errors on unrelated mocks. The real cause
is only visible in the CS8785 warning.

Reproduction

public class Service
{
    public virtual int Value { get; set; } = 7;
}

public class Tests
{
    [Test]
    public async Task Partial()
    {
        var mock = Service.Mock();
        await Assert.That(mock.Object.Value).IsEqualTo(7);
    }

    [Test]
    public async Task Wrap()
    {
        var mock = Mock.Wrap(new Service());
        await Assert.That(mock.Object.Value).IsEqualTo(7);
    }
}

Either test on its own compiles. Together they do not.

Cause

Mock.Of<T>() and Mock.Wrap(instance) produce two MockTypeModels for the same type that differ
only in IsWrapMock. Model equality includes that flag, so both survive dedup in
MockGenerator.Initialize, and both emission paths then call AddSource with the same hint names:

  • GenerateWrapMock and GenerateSingleTypeMock both route through
    GenerateImplFactoryMembersAndEvents, which adds {name}_MockImplFactory.g.cs,
    {name}_MockMembers.g.cs and, when the type has events, {name}_MockEvents.g.cs.

The generated types do not collide — the wrap impl and factory are already named
{name}WrapMockImpl / {name}WrapMockFactory and are file-scoped — but the hint names do. The
member surface ({name}_MockMemberExtensions) is identical for both models and is not file-scoped,
so emitting it twice would also be a duplicate type.

GeneratedNameCollisionDetector already recognises this situation ("Same target mocked in more than
one mode (Mock.Of and Mock.Wrap of one type, say) reaches this point as separate models sharing an
identity") and deliberately does not report TM008 for it, but nothing downstream prevents the
duplicate emission.

Expected

Both mock kinds can be generated for the same type in one compilation: one impl/factory per kind
under distinct hint names, and the shared setup/verification surface emitted once.

Environment

Found on main at ad3a4e0. Not specific to a TFM.

Activity

  1. added a commit that references this issue on Sep 20, 2026
    c4840c3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions