Skip to content

feat(mocks): argument-aware Returns for Task/ValueTask members - #7404

Merged
thomhurst merged 1 commit into
mainfrom
issue-7301-void-task-returns
Nov 2, 2026
Merged

thomhurst merged 1 commit into
mainfrom
issue-7301-void-task-returns

Conversation

@thomhurst

@thomhurst thomhurst commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Members returning non-generic Task / ValueTask only had parameterless task factories (Returns(Func<Task>), ReturnsAsync(Func<Task>)), so a mock could not return a task built from the call's arguments. Moq supports this, so it comes up in Moq migrations.

  • The source generator now emits typed ReturnsAsync(Func<T1..Tn, Task|ValueTask>) and a Returns(...) alias with the same shape on wrappers for bare Task/ValueTask members. This matches what Task<T> members already get. The factory's task is returned unchanged, so an async lambda stays pending until it completes.
  • VoidMethodSetupBuilder gets an [EditorBrowsable(Never)] ReturnsRaw(Func<object?[], object?>), which mirrors the existing one on MethodSetupBuilder<TReturn>.
  • These wrappers have no synchronous Returns(Func<...>) sibling, so the alias doesn't need the generic/ORP shape that Task<T> members use.
  • Added a note to the Moq migration docs.
mock.SaveAsync(Any(), Any()).ReturnsAsync((string u, CancellationToken ct) => Task.CompletedTask);
mock.SendAsync(Any(), Any()).Returns((string u, Func<string, ValueTask> send) => send(u));

Validation

  • tests/TUnit.Mocks.Tests (new Issue7301Tests plus the full suite): 4339 passed on net8.0, net9.0 and net10.0.
  • tests/TUnit.Mocks.SourceGenerator.Tests: 582 passed. The two updated snapshots (Interface_With_Async_Methods, Delegates_Returning_Task_And_ValueTask) only add the new overloads.
  • tests/TUnit.PublicAPI: 74 passed. TUnit.Mocks isn't covered by those snapshots.
  • I didn't run net472 locally; CI covers it.

Closes #7301

Summary by CodeRabbit

  • New Features
    • Configure mocks for asynchronous methods returning Task or ValueTask with factories that receive the call’s arguments. The factory’s task is returned unchanged, allowing asynchronous work to remain pending until it completes.
  • Documentation
    • Clarified how argument-aware Returns and ReturnsAsync factories work for asynchronous methods without a result.

…sk members (#7301)

Members returning non-generic Task/ValueTask only had parameterless task
factories, so the returned task could not depend on the call's arguments.
Generate typed Returns/ReturnsAsync(Func<params..., Task|ValueTask>)
overloads on their wrappers, backed by a new args-aware ReturnsRaw on
VoidMethodSetupBuilder.
@thomhurst

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 190dfe22-e2bc-4b0b-b5cc-59b3408d7b22
Commits

Reviewing files that changed from the base of the PR and between 0db8edc and 895c348.

Files selected for processing: 6
  • docs/docs/migration/mocking/moq.md
  • src/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs
  • src/TUnit.Mocks/Setup/VoidMethodSetupBuilder.cs
  • tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Delegates_Returning_Task_And_ValueTask.verified.txt
  • tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt
  • tests/TUnit.Mocks.Tests/Issue7301Tests.cs

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 3 remain after this review.

Walkthrough

Generated mocks now support argument-aware Returns and ReturnsAsync factories for methods returning non-generic Task or ValueTask. The factories pass through the task they produce. Regression tests and migration documentation cover this behavior.

Changes: Argument-aware async returns

Layer / File(s) Summary
Raw return support and generated overloads
src/TUnit.Mocks/Setup/VoidMethodSetupBuilder.cs, src/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs
Void method setups register raw factories that receive call arguments. Generated async wrappers emit typed Returns and ReturnsAsync factories for non-generic Task and ValueTask methods.
Regression coverage and migration guidance
tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/*, tests/TUnit.Mocks.Tests/Issue7301Tests.cs, docs/docs/migration/mocking/moq.md
Snapshots and tests cover argument passing, task handoff, pending completion, callback invocation, and factory exceptions. The migration guide documents these factories.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant GeneratedMockCall
  participant VoidMethodSetupBuilder
  participant ComputedRawReturnWithArgsBehavior
  Caller->>GeneratedMockCall: configure Returns or ReturnsAsync factory
  GeneratedMockCall->>VoidMethodSetupBuilder: register factory through ReturnsRaw
  VoidMethodSetupBuilder->>ComputedRawReturnWithArgsBehavior: store factory
  Caller->>GeneratedMockCall: call mocked method
  GeneratedMockCall->>ComputedRawReturnWithArgsBehavior: pass call arguments
  ComputedRawReturnWithArgsBehavior-->>Caller: return factory-produced Task or ValueTask
Loading

Suggested reviewers: quattordici

Merge Risk: Minimal · up to 895c3

The new factories pass through their tasks. No merge-blocking issue was established.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding argument-aware Returns overloads for members that return Task or ValueTask.
Linked Issues check Passed Issue #7301 is open and directly linked. The generator now emits typed ReturnsAsync(Func<... , Task|ValueTask>) and matching Returns(Func<... , Task|ValueTask>) overloads for non-generic Task an…
Out of Scope Changes check Passed The changes stay within Issue #7301. The source-generator changes implement the required overloads. VoidMethodSetupBuilder.ReturnsRaw supports the generated argument-aware setup path. Snapshot updat…
Full details: Linked Issues check

Explanation

Issue #7301 is open and directly linked. The generator now emits typed ReturnsAsync(Func&lt;... , Task|ValueTask&gt;) and matching Returns(Func&lt;... , Task|ValueTask&gt;) overloads for non-generic Task and ValueTask members. The overloads pass actual call arguments to the factory and return the produced task unchanged through ReturnsRaw. The new Issue7301Tests cover argument flow, callback invocation, pending async tasks, task identity, and factory exceptions. The snapshots and migration documentation also cover the new API.

Full details: Out of Scope Changes check

Explanation

The changes stay within Issue #7301. The source-generator changes implement the required overloads. VoidMethodSetupBuilder.ReturnsRaw supports the generated argument-aware setup path. Snapshot updates, regression tests, and the Moq migration note document and validate the same feature. No unrelated implementation is shown in the pull-request diff.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 2 functions across 3 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

A rabbit taps a task into the night
Call arguments guide it on its way
A ValueTask waits until its gate
Then callback paws spring into play
The factory hands its task back whole
And tests record each hop and stay

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T10:02:19.632292Z 895c348 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Review: argument-aware Returns for Task/ValueTask members

Looks good. I found nothing blocking.

  • Generator: EmitVoidTypedOverloads is a clean extraction. It keeps the #if NET9_0_OR_GREATER ref-struct branches consistent, and the new overloads are emitted only for Task/ValueTask members (via asyncTaskType).
  • Runtime: VoidMethodSetupBuilder.ReturnsRaw(Func<object?[], object?>) mirrors the existing MethodSetupBuilder<TReturn> overload and reuses ComputedRawReturnWithArgsBehavior, so there is no new behavior path.
  • Overloads: I didn't find an ambiguity between the new Func<T1..Tn, Task> overloads and the parameterless Func<Task> factories, because the arities differ. The Returns alias being non-generic is reasonable given there is no sync Returns(Func<...>) sibling on these wrappers.
  • Tests: they cover argument passing, task identity, a pending async lambda, and exception propagation. The two snapshots only add the new overloads. The docs note is accurate.

Minor suggestions, not blocking:

  1. VoidMockMethodCall exposes ReturnsRaw(object?) and ReturnsRaw(Func<object?>) but not the new args-aware overload. That is consistent with generated wrappers calling EnsureSetup() directly. If MockMethodCall<TReturn> has the args overload, adding it here would keep the two symmetric.
  2. The two emitted lines share one body. You could factor the body into a local helper if more aliases are added later.

I couldn't run the tests here. The PR reports a full pass on net8.0, net9.0 and net10.0. net472 is left to CI.

Approve.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 895c34875c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@greptile-apps

greptile-apps Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] This PR appears safe to merge.

Summary

Adds argument-aware factories for members returning Task or ValueTask without a result.

  • Bare Task and ValueTask mocks can build their returned task from call arguments.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Returns / ReturnsAsync factory"] --> B["ReturnsRaw"]
  B --> C["ComputedRawReturnWithArgsBehavior"]
  D["Mock member called"] --> E["Engine passes call arguments"]
  E --> C
  C --> F["Factory returns Task / ValueTask"]
  F --> G["Generated member returns task unchanged"]
Loading

Reviews (1) · Last reviewed commit: "feat(mocks): argument-aware Returns/Retu..." · Reviewed by Greptile

@thomhurst
thomhurst marked this pull request as ready for review November 2, 2026 10:00
@thomhurst
thomhurst enabled auto-merge (squash) November 2, 2026 10:00
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Review

This looks good. I found nothing blocking.

  • Generator: the new ReturnsAsync/Returns(Func<T1..Tn, Task|ValueTask>) overloads are emitted for bare Task/ValueTask members only. They match the existing Task<T> shape and reuse BuildCastArgs, so ref-struct and #if NET9_0_OR_GREATER handling stays consistent. Pulling the emission into EmitVoidTypedOverloads removes the duplicated callback/throws calls in the three branches.
  • Runtime: VoidMethodSetupBuilder.ReturnsRaw(Func<object?[], object?>) mirrors MethodSetupBuilder<TReturn>. It reuses the existing ComputedRawReturnWithArgsBehavior, which wraps the result in RawReturn, so the task goes back unchanged and an async lambda stays pending.
  • Tests: they cover argument passing, task identity, pending async lambdas, and exceptions surfacing from the task. The snapshot diffs only add the new overloads.

Optional:

  • Returns(Func<string, Task>) and ReturnsAsync(Func<string, Task>) now have identical bodies. That is fine for Moq-migration ergonomics. If the aliases grow more, a single shared emitted helper would keep them from drifting.
  • Overload resolution could be ambiguous when a lambda is passed to Returns on a wrapper that also has the typed Callback/Throws overloads. The existing tests compile with explicit lambda parameter types, so this is covered in practice.

Approve from my side.

This was referenced Nov 4, 2026

This branch was previously deployed

1 inactive deployment
Pull Requests — 895c3487 Deployed Nov 2, 2026 by thomhurst via modularpipeline (windows-latest) #20681
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUnit.Mocks: no argument-aware Returns for methods returning non-generic Task/ValueTask

1 participant