Skip to content

[Bug] TUnit.Mocks: mocking a type with required members fails with CS9035 #5678

@thomhurst

Description

@thomhurst

Description

Mocking a class that declares a required member fails to compile because the generated factory uses an object initializer that does not set the required member.

Minimal repro

```csharp
public abstract class RequiredShape
{
public required string Name { get; init; }
public abstract int Compute();
}

var mock = RequiredShape.Mock();
```

Error (in generated *_MockImplFactory.g.cs):
```
error CS9035: Required member 'RequiredShape.Name' must be set in the object initializer or attribute constructor.
```

Expected

Mock construction should succeed without the user having to set required members — the mock is a test double, it does not need the compile-time enforcement of required.

Actual

Factory can't be instantiated at all, so the type can't be mocked.

Suggested fix

Emit [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] on the generated mock constructor (the private one inside the factory). This tells the compiler that the constructor itself takes responsibility for the required members, bypassing the initializer requirement.

Instructions for the fixing agent

Work test-first. Before touching the generator:

  1. Add a dedicated test that reproduces this failure. The entry point already exists: unskip and restore the T17 block in TUnit.Mocks.Tests/KitchenSinkEdgeCasesTests.cs (both the RequiredShape type and the T17_Required_Property_Does_Not_Block_Mock_Instantiation test) and confirm the build fails with the exact CS9035 above in the current codebase.
  2. Implement the fix in the generator (see Suggested fix).
  3. Re-run the test and confirm it passes. The test must cover:
    • Mock instantiation succeeds without the user providing a value for the required member.
    • The abstract/virtual members on the required-carrying type are mockable via Returns(...) and verifiable via WasCalled.
    • Extend the coverage with at least one extra shape: a class that has multiple required members (including a required reference type and a required value type) so the initializer-skip logic is exercised broadly.
  4. Run the full TUnit.Mocks.Tests and TUnit.Mocks.SourceGenerator.Tests suites to confirm no regression.
  5. 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 T17 SKIPPED.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions