Fix item-at Satisfies source typing#5764
Conversation
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| BestPractice | 1 minor |
🟢 Metrics 38 complexity
Metric Results Complexity 38
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Code Review: Fix item-at Satisfies source typing (#5706)
The fix is correct and clearly addresses the root cause: Satisfies was constructing a ValueAssertion<TItem> (a bare wrapper over IAssertionSource<TItem>) rather than a richly-typed assertion source, so users of collection-type items lost all the specialized assertion API.
Architecture concern: combinatorial overload explosion
ListItemAtSatisfiesExtensions.cs is 311 lines covering 11 concrete collection types × 2 source types = 22 overloads. This approach works, but it won't scale. Every time a new assertion type is added to TUnit (e.g. SortedSetAssertion, ImmutableArrayAssertion, or any user-defined collection assertion type), this file must be updated with 2 more overloads. The pattern also doesn't compose — it says nothing about IList<IList<IList<T>>> or other nested structures.
A more maintainable design would expose a single generic overload that accepts a source factory:
// Single entry point — no enumeration of concrete types needed
public static ListItemAtSatisfiesAssertion<TList, TItem> Satisfies<TList, TItem, TSource>(
this ListItemAtSource<TList, TItem> source,
Func<TItem, string, TSource> sourceFactory,
Func<TSource, IAssertion?> assertion,
[CallerArgumentExpression(nameof(assertion))] string? expression = null)
where TList : IList<TItem>
where TSource : IAssertionSource<TItem>
{
// one implementation
}Usage becomes:
await Assert.That(items).ItemAt(0)
.Satisfies(
(item, label) => new CollectionAssertion<int>(item, label),
a => a.Count().IsEqualTo(3));This pushes the factory responsibility to the call site, but TUnit could still ship convenience helpers (e.g. static factory methods on each assertion type) rather than enumerating every combination. The key benefit: users can bring their own assertion source types and they'll work automatically — the current approach gives them no way to do that.
internal accessors as a design smell
// ListAssertions.cs (and ReadOnlyListAssertions.cs)
internal AssertionContext<TList> InternalListContext => _listContext;
internal int InternalIndex => _index;These were private before; they're now exposed internal solely to allow the extension class (in the same assembly) to bypass normal construction. The Internal prefix signals this awkwardness. A cleaner alternative: a factory/builder method directly on ListItemAtSource<TList, TItem> that creates the assertion given a raw factory:
internal ListItemAtSatisfiesAssertion<TList, TItem> CreateSatisfiesAssertion(
Func<TItem, int, IAssertion?> factory)
{
_listContext.ExpressionBuilder.Append(...)
return new ListItemAtSatisfiesAssertion<TList, TItem>(_listContext, _index, factory);
}This hides the internals and keeps the responsibility for mutating ExpressionBuilder inside the source type, where it belongs.
The internal assertion constructor is unguarded on the expression side
The new internal constructor of ListItemAtSatisfiesAssertion skips the expression-appending that SatisfiesSpecialised does manually:
// Extension helper appends expression here...
source.InternalListContext.ExpressionBuilder.Append($".Satisfies({expression})");
// ...then constructs via internal ctor which does NOT append anything
return new ListItemAtSatisfiesAssertion<TList, TItem>(
source.InternalListContext,
source.InternalIndex,
assertionFactory);If anyone constructs ListItemAtSatisfiesAssertion via the internal ctor without calling ExpressionBuilder.Append first, the expression will be silently incomplete. The factory-method approach above would eliminate this risk by co-locating both steps.
Test coverage gaps
The regression tests cover String, Collection, Dictionary, and Set for both IList and IReadOnlyList — solid baseline. Missing:
T[](theArrayAssertionoverload is present but untested)HashSet<T>(theHashSetAssertionoverload exists)Dictionary<K,V>concrete type (there's aMutableDictionaryAssertionoverload for it)
Given this is a regression fix, having at least a smoke test for each overload variant that was added would make future regressions much easier to catch.
Summary
The core fix is sound — changing the internal delegate from Func<IAssertionSource<TItem>, Assertion<TItem>?> to Func<TItem, int, IAssertion?> and building the specialized source at dispatch time is exactly right. The backward-compatible forwarding constructor preserves the existing public API cleanly.
The concern is that the mechanism chosen (enumerating all known concrete types as overloads) trades one problem (missing specialization) for another (a maintenance tax on every new assertion type). Worth considering whether a single generic sourceFactory parameter would be a better long-term design before this pattern spreads to other assertion combinators.
|
Addressed the review feedback in ce722f9:
Verification rerun:
|
There was a problem hiding this comment.
Code Review: Fix item-at Satisfies source typing (#5706)
The PR correctly identifies and fixes the root cause: ListItemAtSatisfiesAssertion was storing Func<IAssertionSource<TItem>, Assertion<TItem>?> which forced the lambda return type to be Assertion<TItem>? — making it impossible to use collection assertions that chain to a different Assertion<T> (e.g., Count().IsEqualTo(3) returns Assertion<int>, not Assertion<IEnumerable<int>>).
What's done well
- The internal refactor to
Func<TItem, int, IAssertion?>is clean and separates item retrieval from assertion creation correctly. - Constructor chaining preserves backward compatibility elegantly.
- The
CreateSatisfiesAssertionbridge approach (following feedback from the previous review) correctly avoids exposing internal index/context accessors. - Good test coverage across diverse concrete types.
- Public API snapshots updated properly.
Architectural Concern: The Root Fix May Be Simpler
The 303-line extension file is working around a return-type covariance constraint on the existing overload. The original signature requires Func<IAssertionSource<TItem>, Assertion<TItem>?>, but collection assertion chains return Assertion<int> (not Assertion<IEnumerable<int>>), so the lambda won't compile.
If most collection assertion extension methods (like Count()) are already defined on IAssertionSource<IEnumerable<T>> — rather than only on the concrete CollectionAssertion<T> — then adding a single overload with a relaxed return type directly on the Satisfies method would handle the majority of cases without any per-type dispatch:
// One new overload on ListItemAtSource<TList, TItem>
public ListItemAtSatisfiesAssertion<TList, TItem> Satisfies(
Func<IAssertionSource<TItem>, IAssertion?> assertion,
[CallerArgumentExpression(nameof(assertion))] string? expression = null)
{
return CreateSatisfiesAssertion(
(item, index) => assertion(new ValueAssertion<TItem>(item, $"item[{index}]")),
expression);
}With this, users can write item => item.Count().IsEqualTo(3) and the lambda compiles because IAssertion? is satisfied by the Assertion<int> return. The full 303-line extension file would only be needed if users specifically need the concrete assertion type (e.g., CollectionAssertion<int> has methods not available through IAssertionSource<IEnumerable<int>>).
Is the extension file truly necessary, or is the IAssertion? overload sufficient for all the test cases?
Scalability of the Extension File
If the per-type extension methods are genuinely needed, the static dispatch approach has inherent growth pressure:
- Missing today:
ICollection<T>,Queue<T>,Stack<T>,LinkedList<T>,SortedList<TKey,TValue>,ImmutableArray<T>,ImmutableList<T>, etc. - Any user-defined assertion source type will never work with this approach.
A more future-proof alternative for strongly-typed sources would be a factory overload:
public ListItemAtSatisfiesAssertion<TList, TItem> Satisfies<TSource>(
Func<TItem, string, TSource> sourceFactory,
Func<TSource, IAssertion?> assertion,
[CallerArgumentExpression(nameof(assertion))] string? expression = null)
where TSource : IAssertionSource<TItem>Usage:
.Satisfies(
(item, name) => new CollectionAssertion<int>(item, name),
col => col.Count().IsEqualTo(3))This covers all current and future assertion source types without adding overloads, at the cost of a slightly more verbose call site. The per-type extension methods could still be provided as ergonomic wrappers for the most common types.
Minor
- The
stringitem path works correctly through the existingFunc<IAssertionSource<TItem>, Assertion<TItem>?>overload (string assertions returnAssertion<string>). The behavior asymmetry between string items and collection items is worth a code comment noting why no specialized overload is needed forstring. #if NET5_0_OR_GREATERonIReadOnlySet<T>is correct; no issues there.
Overall the implementation is functionally correct and the previous review feedback has been addressed. The main open question is whether the simpler single-overload approach (Func<IAssertionSource<TItem>, IAssertion?>) would suffice, which would let you drop most of the extension file. If the specialized concrete types are genuinely needed for their extra methods, the factory overload pattern would scale better than the current per-type enumeration.
Replace per-type Satisfies overload bodies with delegators through a new static-abstract factory interface. Interface-shape overloads collapse to one-liners; concrete-type overloads (List<T>, Dictionary<K,V>) keep direct construction since their assertion sources implement IAssertionSourceFor against the matching interface, not the concrete type. Pattern is reusable for the remaining #5703-5707 family (Member, Count, etc.) — each new target needs only the generic Satisfies entry plus delegators, no per-shape construction logic. netstandard2.0 drops the specialised overloads (no static abstracts); net8+/net9+/net10+ get the full surface.
There was a problem hiding this comment.
Code Review
Previous feedback (constructor chaining, CreateSatisfiesAssertion encapsulation, test coverage gaps) has been addressed. The architecture is sound.
What's working well
IAssertionSourceFor<TItem, TSelf> CRTP interface — The right foundation. Using C# 11 static abstract members for a per-item factory enables fully generic dispatch without per-type overloads at the core level. The constraint where TSource : IAssertionSourceFor<TItem, TSource> correctly closes the loop.
CreateSatisfiesAssertion encapsulation — Resolves the previous concern about leaking InternalListContext/InternalIndex. The extension methods call an internal method from within the same assembly, which is the correct scope.
Backward-compatible constructor chaining — The old public ListItemAtSatisfiesAssertion(context, index, Func<IAssertionSource<TItem>, Assertion<TItem>?>) constructor chains to the new internal one. No binary breaking change, and the wrapping via new ValueAssertion<TItem> is transparent to callers.
Func<TItem, int, IAssertion?> — Passing index alongside item is essential for constructing the label $"item[{index}]" in the right place, rather than deferring label construction to CheckAsync. This is the key fix for #5706.
Remaining design concern: List<T> / Dictionary<TKey,TValue> bridging asymmetry
The comment explains the why well, but there is a cleanable root cause. ListAssertion<T> implements IAssertionSourceFor<IList<T>, ListAssertion<T>> (interface-typed), not IAssertionSourceFor<List<T>, ListAssertion<T>> (concrete-typed). So the generic Satisfies<TSource> path can't bind when the item is List<T>, forcing the explicit bridge:
// Current — two code paths
=> source.Satisfies<ListAssertion<TInner>>(assertion, expression); // via IAssertionSourceFor
=> source.CreateSatisfiesAssertion( // manual bridge
(item, index) => assertion(new ListAssertion<TInner>(item, ...)), expression);ListAssertion<T> could implement both interface variants without conflict, since the Create static methods differ in parameter type (IList<T> vs List<T>):
public class ListAssertion<TItem>
: ListAssertionBase<IList<TItem>, TItem>
#if !NETSTANDARD2_0
, IAssertionSourceFor<IList<TItem>, ListAssertion<TItem>> // existing
, IAssertionSourceFor<List<TItem>, ListAssertion<TItem>> // new — eliminates bridge
#endif
{
public static ListAssertion<TItem> Create(IList<TItem> item, string label) => new(item, label);
public static ListAssertion<TItem> Create(List<TItem> item, string label) => new(item, label);
}C# can implement two parameterizations of the same generic interface when the static abstract member signatures are distinct (different TItem → different parameter type). With this, the concrete List<T> extension overloads would reduce to simple Satisfies<ListAssertion<TInner>> calls, eliminating the asymmetric CreateSatisfiesAssertion bridge and making the code more consistent. Same pattern applies to MutableDictionaryAssertion. Not a blocker, but worth a follow-up — the inconsistency will likely surface again when users try to add their own concrete-type overloads.
Minor: extension duplication between list / readonly-list source types
Each of the 11 logical type mappings appears twice in ListItemAtSatisfiesExtensions.cs — once for ListItemAtSource<TList, TItem> and once for ReadOnlyListItemAtSource<TList, TItem>. This is intrinsic because the return types differ (ListItemAtSatisfiesAssertion vs ReadOnlyListItemAtSatisfiesAssertion). A shared interface over both source types exposing CreateSatisfiesAssertion could halve this, but the return-type variance makes that non-trivial. Noting for visibility rather than blocking the PR.
Test coverage note
Tests cover IEnumerable<int>, int[], IDictionary<string,int>, Dictionary<string,int>, ISet<int>, HashSet<int>, IReadOnlyDictionary<string,int>, IReadOnlySet<int>, and string. The IList<T> and IReadOnlyList<T> item overloads (which are among the cleanest — pure Satisfies<TSource> delegation) don't appear to have dedicated test cases. Low risk, but worth adding for completeness.
Summary
Functionally correct, all previous feedback has been addressed, and the IAssertionSourceFor CRTP foundation is genuinely extensible — users can write Satisfies<MyCustomAssertion>(...) for types TUnit doesn't know about. The main open item is the List<T>/Dictionary<TKey,TValue> concrete-type bridging asymmetry, which is cosmetic now but becomes a maintenance tax as the pattern propagates.
ListAssertion and MutableDictionaryAssertion now implement two IAssertionSourceFor parameterisations each (interface + concrete item types), enabling the concrete List<T> and Dictionary<K,V> Satisfies overloads to delegate through the generic Satisfies<TSource> path instead of bypassing it via direct CreateSatisfiesAssertion calls. This eliminates the per-type construction asymmetry that would otherwise propagate to every future combinator following the same pattern. IAssertionSourceFor no longer extends IAssertionSource<TItem> — the framework only ever calls TSource.Create(...), and the multi-variant implementation is what required dropping the base (an IAssertionSource<List<T>> Context property cannot be derived from AssertionContext<IList<T>> without invariance breakage). Concrete classes still expose IAssertionSource<TItem> through their existing base hierarchies, so user lambdas see no surface change. Adds nested-list test coverage: IList<IList<T>>, IList<IReadOnlyList<T>>, IReadOnlyList<IList<T>>, IReadOnlyList<IReadOnlyList<T>>.
There was a problem hiding this comment.
Code Review: Fix item-at Satisfies source typing — iteration 4
All prior feedback has been fully addressed. This review covers only the new commit (0080687e).
What changed since the last review
The latest commit does three things:
IAssertionSourceFor<TItem, TSelf>no longer extendsIAssertionSource<TItem>— required to allowListAssertion<T>andMutableDictionaryAssertion<T>to implement two parameterizations of the interface simultaneously.ListAssertion<T>andMutableDictionaryAssertion<TKey,TValue>implement both the interface-typed and concrete-typed variants (IAssertionSourceFor<IList<T>>andIAssertionSourceFor<List<T>>), so the concrete overloads in the extension file now delegate through the genericSatisfies<TSource>path without anyCreateSatisfiesAssertionbypass.- Test coverage added for
IList<IList<T>>,IList<IReadOnlyList<T>>,IReadOnlyList<IList<T>>, andIReadOnlyList<IReadOnlyList<T>>— directly addresses the gap flagged in the last review.
All three of these were explicitly flagged as open items in the previous review, so well done on the quick turnaround.
Removing IAssertionSourceFor : IAssertionSource<TItem>
This is the right call. The commit message explains the invariance problem precisely, and the <remarks> doc on the interface explains the design rationale for future maintainers. The public API snapshot reflects the change correctly. Since IAssertionSourceFor was never shipped in a release, removing the inheritance is not a breaking change.
One small thing to verify: the API snapshots show that ArrayAssertion<TItem>, CollectionAssertion<TItem>, ReadOnlyListAssertion<TItem>, etc. now list fewer interfaces in their type hierarchy (the transitively-inherited IAssertionSource and IAssertionSource<TItem> that came from IAssertionSourceFor are no longer shown). The underlying implementations are unaffected — those interfaces are still satisfied through the base class hierarchy — so this is a snapshot artifact, not a behavior regression.
Minor gap: IList<List<T>> case not tested
The refactored List<T> overloads now route through Satisfies<ListAssertion<TInner>> (via the new IAssertionSourceFor<List<T>, ListAssertion<T>> implementation). This is the code path most changed by this commit, yet it has no dedicated test. All existing tests use IList<IList<T>>, not IList<List<T>>. A minimal test case:
[Test]
public async Task List_ItemAt_Satisfies_Preserves_ConcreteList_Item_Source()
{
IList<List<int>> items = new List<List<int>> { new() { 1, 2, 3 } };
await Assert.That(items).ItemAt(0).Satisfies(item => item.Count().IsEqualTo(3));
}Same applies to IList<Dictionary<string,int>> (the concrete dictionary path). Low risk since the types are equivalent at the IList<T> / IDictionary<K,V> level, but a regression test for exactly the code path changed this round would be good practice.
Summary
The implementation is clean, the internal Satisfies<TSource> path is now used consistently for both concrete and interface item types, the CreateSatisfiesAssertion bypass is gone, and the test coverage is substantially improved. The only remaining suggestion is smoke tests for the List<T> and Dictionary<K,V> concrete-item paths that are specifically enabled by this commit. No blockers.
Adds the IList<List<int>> case the previous round missed — this is the concrete-list path now routed through Satisfies<ListAssertion<TInner>> via the new IAssertionSourceFor<List<T>, ListAssertion<T>> implementation. Also documents the why behind the interface- and concrete-shaped overload coexistence on ListItemAtSatisfiesExtensions: C# overload resolution is exact on TItem, so IList<List<int>> binds the List<TInner> overload and never the IList<TInner> one.
Code Review: Fix item-at Satisfies source typing (#5764)This is a well-structured fix for a real usability problem. The core issue is that What the PR does wellBackward compatibility is preserved correctly. The original public constructor
Expression string is correctly passed through. The
Issues and observations1.
|
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.40.5 to 1.45.0. <details> <summary>Release notes</summary> _Sourced from [TUnit's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.45.0 <!-- Release notes generated using configuration in .github/release.yml at v1.45.0 --> ## What's Changed ### Other Changes * fix(generator): place CallerArgumentExpression before params in [GenerateAssertion] emit by @JohnVerheij in thomhurst/TUnit#5940 * fix(sourcegen): drop covariant TActual when [GenerateAssertion] method has its own type parameters by @JohnVerheij in thomhurst/TUnit#5935 * feat(assertions): add CancellationToken overload to WaitsFor and Eventually by @JohnVerheij in thomhurst/TUnit#5938 * fix(html-report): extract categories using MTP Key=name convention by @thomhurst in thomhurst/TUnit#5946 * feat(html-report): rewrite as split-pane design template by @thomhurst in thomhurst/TUnit#5947 ### Dependencies * chore(deps): update microsoft.testing to 2.2.3 by @thomhurst in thomhurst/TUnit#5927 * chore(deps): update mstest to 4.2.3 by @thomhurst in thomhurst/TUnit#5928 * chore(deps): update tunit to 1.44.39 by @thomhurst in thomhurst/TUnit#5929 * chore(deps): update aspire to 13.3.3 by @thomhurst in thomhurst/TUnit#5933 * chore(deps): update dependency dompurify to v3.4.4 by @thomhurst in thomhurst/TUnit#5944 * chore(deps): update dependency qs to v6.15.2 by @thomhurst in thomhurst/TUnit#5941 **Full Changelog**: thomhurst/TUnit@v1.44.39...v1.45.0 ## 1.44.39 <!-- Release notes generated using configuration in .github/release.yml at v1.44.39 --> ## What's Changed ### Other Changes * fix(tests): retry trx read to dodge MTP post-exit flush race on Windows by @thomhurst in thomhurst/TUnit#5888 * fix(pipeline): timeout + retry InstallPlaywrightModule so a hung download fails fast by @thomhurst in thomhurst/TUnit#5889 * fix(otel): require two consecutive idle windows in DrainAsync to catch in-transit POSTs by @thomhurst in thomhurst/TUnit#5890 * test(assertions): drop flaky wall-clock upper bound on WaitsFor timeout test by @thomhurst in thomhurst/TUnit#5886 * fix(sourcegen): drop spurious ')' in MethodAssertionGenerator Task<bool> emit by @JohnVerheij in thomhurst/TUnit#5920 * fix(sourcegen): merge generic parameter lists in [AssertionExtension] emit by @JohnVerheij in thomhurst/TUnit#5921 * fix(aspnetcore): scope correlation processor per-factory to stop cross-factory tag leak by @thomhurst in thomhurst/TUnit#5891 * Changed FSharp.Core version to 10.1.300 by @licon4812 in thomhurst/TUnit#5909 * feat(mocks): add Mock.HttpClientFactory() helper by @thomhurst in thomhurst/TUnit#5894 * Harden WaitsFor timeout test by @thomhurst in thomhurst/TUnit#5926 * fix(sourcegen): emit `default` literal for value-type assertion parameters by @JohnVerheij in thomhurst/TUnit#5919 ### Dependencies * chore(deps): update dependency nunit to 4.6.0 by @thomhurst in thomhurst/TUnit#5826 * chore(deps): update tunit to 1.44.0 by @thomhurst in thomhurst/TUnit#5882 * chore(deps): update dependency mockolate to 3.2.0 by @thomhurst in thomhurst/TUnit#5892 * chore(deps): update dependency yaml to v2.9.0 by @thomhurst in thomhurst/TUnit#5887 * chore(deps): update dependency nuget.protocol to 7.6.0 by @thomhurst in thomhurst/TUnit#5897 * chore(deps): update dependency microsoft.entityframeworkcore to 10.0.8 by @thomhurst in thomhurst/TUnit#5898 * chore(deps): update dependency microsoft.templateengine.authoring.cli to v10.0.300 by @thomhurst in thomhurst/TUnit#5899 * chore(deps): update microsoft.extensions by @thomhurst in thomhurst/TUnit#5905 * chore(deps): update microsoft.aspnetcore to 10.0.8 by @thomhurst in thomhurst/TUnit#5904 * chore(deps): update dependency microsoft.templateengine.authoring.templateverifier to 10.0.300 by @thomhurst in thomhurst/TUnit#5902 * chore(deps): update aspire to 13.3.1 by @thomhurst in thomhurst/TUnit#5900 * chore(deps): update dependency system.commandline to 2.0.8 by @thomhurst in thomhurst/TUnit#5903 * chore(deps): update dependency azure.storage.blobs to 12.28.0 by @thomhurst in thomhurst/TUnit#5910 * chore(deps): update dependency dotnet-sdk to v10.0.300 by @thomhurst in thomhurst/TUnit#5901 * chore(deps): update dependency stackexchange.redis to 2.13.1 by @thomhurst in thomhurst/TUnit#5906 * chore(deps): update aspire to 13.3.2 by @thomhurst in thomhurst/TUnit#5924 * chore(deps): bump mermaid from 11.12.2 to 11.15.0 in /docs by @dependabot[bot] in thomhurst/TUnit#5893 * chore(deps): update dependency streamjsonrpc to 2.24.92 by @thomhurst in thomhurst/TUnit#5915 * chore(deps): update dependency dompurify to v3.4.3 by @thomhurst in thomhurst/TUnit#5913 * chore(deps): update microsoft.build to 18.6.3 by @thomhurst in thomhurst/TUnit#5914 **Full Changelog**: thomhurst/TUnit@v1.44.0...v1.44.39 ## 1.44.0 <!-- Release notes generated using configuration in .github/release.yml at v1.44.0 --> ## What's Changed ### Other Changes * Generated mocks live in the same namespace as the mocked type by @thomhurst in thomhurst/TUnit#5870 * Show multi-step test spans in class timeline, align report ordering with execution, and correlate linked OTel activities by @Copilot in thomhurst/TUnit#5847 * fix: don't leak RUC onto Should-style comparer overloads (#5857) by @thomhurst in thomhurst/TUnit#5873 * Fix culture-dependent timestamp in HTML test report (#5868) by @thomhurst in thomhurst/TUnit#5872 * fix(mocks-http): auto-prepend `/` to partial endpoint paths (#5838) by @thomhurst in thomhurst/TUnit#5874 * Replace Report.ExpandClassTimeline with [ClassTimeline] attribute by @thomhurst in thomhurst/TUnit#5875 * feat(assertions): make ShouldAssertion<T> implement IAssertion (#5824) by @thomhurst in thomhurst/TUnit#5876 * feat(mocks): support non-span ref struct out/ref params by @thomhurst in thomhurst/TUnit#5878 * fix(core): fill optional params when invoking MethodDataSource via reflection by @thomhurst in thomhurst/TUnit#5880 * Mocks: structural fix for Mock<T> / mocked-member name collisions by @thomhurst in thomhurst/TUnit#5881 * chore(mocks): promote TUnit.Mocks packages to stable by @thomhurst in thomhurst/TUnit#5877 ### Dependencies * chore(deps): update tunit to 1.43.41 by @thomhurst in thomhurst/TUnit#5863 * chore(deps): update dependency tunit.assertions.fsharp to 1.43.41 by @thomhurst in thomhurst/TUnit#5865 * chore(deps): bump @babel/plugin-transform-modules-systemjs from 7.28.5 to 7.29.4 in /docs by @dependabot[bot] in thomhurst/TUnit#5867 * chore(deps): bump fast-uri from 3.1.0 to 3.1.2 in /docs by @dependabot[bot] in thomhurst/TUnit#5862 **Full Changelog**: thomhurst/TUnit@v1.43.41...v1.44.0 ## 1.43.41 <!-- Release notes generated using configuration in .github/release.yml at v1.43.41 --> ## What's Changed ### Other Changes * feat(playwright): expose default Context/Launch options on settings by @thomhurst in thomhurst/TUnit#5861 * fix(hooks): resolve TestDiscovery hook context type by attribute kind, not method name by @thomhurst in thomhurst/TUnit#5860 ### Dependencies * chore(deps): update tunit to 1.43.38 by @thomhurst in thomhurst/TUnit#5858 **Full Changelog**: thomhurst/TUnit@v1.43.38...v1.43.41 ## 1.43.38 <!-- Release notes generated using configuration in .github/release.yml at v1.43.38 --> ## What's Changed ### Other Changes * feat(playwright): add TUnitPlaywrightSettings defaults by @thomhurst in thomhurst/TUnit#5859 **Full Changelog**: thomhurst/TUnit@v1.43.37...v1.43.38 ## 1.43.37 <!-- Release notes generated using configuration in .github/release.yml at v1.43.37 --> ## What's Changed ### Other Changes * docs: clarify MethodDataSourceAttribute.Factory is source-generator-managed by @Copilot in thomhurst/TUnit#5835 * fix(assertions): skip ref-struct members in IsEquivalentTo (#5841) by @thomhurst in thomhurst/TUnit#5842 * feat(playwright): add composition-based fixtures by @thomhurst in thomhurst/TUnit#5840 ### Dependencies * chore(deps): update tunit to 1.43.11 by @thomhurst in thomhurst/TUnit#5821 * chore(deps): update dependency polyfill to 10.4.0 by @thomhurst in thomhurst/TUnit#5830 * chore(deps): update dependency polyfill to 10.4.0 by @thomhurst in thomhurst/TUnit#5829 * chore(deps): update react to ^19.2.6 by @thomhurst in thomhurst/TUnit#5839 * chore(deps): update dependency polyfill to 10.5.0 by @thomhurst in thomhurst/TUnit#5848 * chore(deps): update dependency polyfill to 10.5.0 by @thomhurst in thomhurst/TUnit#5849 * chore(deps): update aspire to 13.3.0 by @thomhurst in thomhurst/TUnit#5851 * chore(deps): update dependency brace-expansion to v5.0.6 by @thomhurst in thomhurst/TUnit#5853 * chore(deps): update dependency polyfill to 10.5.1 by @thomhurst in thomhurst/TUnit#5854 * chore(deps): update dependency polyfill to 10.5.1 by @thomhurst in thomhurst/TUnit#5855 * chore(deps): update verify to 31.16.3 by @thomhurst in thomhurst/TUnit#5856 **Full Changelog**: thomhurst/TUnit@v1.43.11...v1.43.37 ## 1.43.11 <!-- Release notes generated using configuration in .github/release.yml at v1.43.11 --> ## What's Changed ### Other Changes * perf(engine): skip execution ledger for independent tests by @thomhurst in thomhurst/TUnit#5813 * perf(engine): skip tracked object graph rediscovery by @thomhurst in thomhurst/TUnit#5814 * fix: suppress XML doc warnings in generated sources by @mvanhorn in thomhurst/TUnit#5797 * Fix xUnit Throws migration docs by @thomhurst in thomhurst/TUnit#5819 * Fix Should optional overload generation by @thomhurst in thomhurst/TUnit#5820 ### Dependencies * chore(deps): update dependency mockolate to 3.1.0 by @thomhurst in thomhurst/TUnit#5811 * chore(deps): update tunit to 1.43.2 by @thomhurst in thomhurst/TUnit#5809 * chore(deps): update dependency nunit.analyzers to 4.13.0 by @thomhurst in thomhurst/TUnit#5815 * chore(deps): update dependency yaml to v2.8.4 by @thomhurst in thomhurst/TUnit#5812 ## New Contributors * @mvanhorn made their first contribution in thomhurst/TUnit#5797 **Full Changelog**: thomhurst/TUnit@v1.43.2...v1.43.11 ## 1.43.2 <!-- Release notes generated using configuration in .github/release.yml at v1.43.2 --> ## What's Changed ### Other Changes * fix(assertions): emit auto-generated header in MethodAssertionGenerator output by @JohnVerheij in thomhurst/TUnit#5796 * fix(engine): enforce runtime exclusion for global [NotInParallel] (#5800) by @thomhurst in thomhurst/TUnit#5805 * feat(mocks): add Arg.AnyArgs() shortcut for setup/verify by @thomhurst in thomhurst/TUnit#5792 * fix(should): add specialized assertion sources by @thomhurst in thomhurst/TUnit#5806 ### Dependencies * chore(deps): update tunit to 1.41.0 by @thomhurst in thomhurst/TUnit#5789 * chore(deps): update microsoft.testing to 2.2.2 by @thomhurst in thomhurst/TUnit#5793 * chore(deps): update mstest to 4.2.2 by @thomhurst in thomhurst/TUnit#5794 * chore(deps): update docusaurus by @thomhurst in thomhurst/TUnit#5798 * chore(deps): update dependency dompurify to v3.4.2 by @thomhurst in thomhurst/TUnit#5799 * chore(deps): update dependency mockolate to v3 by @thomhurst in thomhurst/TUnit#5801 **Full Changelog**: thomhurst/TUnit@v1.41.0...v1.43.2 ## 1.41.0 <!-- Release notes generated using configuration in .github/release.yml at v1.41.0 --> ## TUnit Should() Assertions (beta) This versions comes with a beta version of TUnit.Assertions.Should - meaning that you can do: `myCode.Should().BeEqualTo(fluentShouldSyntax);` For more information see the docs: https://tunit.dev/docs/assertions/should-syntax ## What's Changed ### Other Changes * fix(reporters): unwrap TestFailedException for failure grouping by @thomhurst in thomhurst/TUnit#5776 * Fix item-at Satisfies source typing by @thomhurst in thomhurst/TUnit#5764 * ci(docs): add link-check automation and tighten Docusaurus strictness by @thomhurst in thomhurst/TUnit#5779 * feat: add TUnit.Assertions.Should package by @thomhurst in thomhurst/TUnit#5785 ### Dependencies * chore(deps): update tunit to 1.40.10 by @thomhurst in thomhurst/TUnit#5775 * chore(deps): update actions/cache action to v5 by @thomhurst in thomhurst/TUnit#5780 * chore(deps): update dependency microsoft.net.test.sdk to 18.5.1 by @thomhurst in thomhurst/TUnit#5784 **Full Changelog**: thomhurst/TUnit@v1.40.10...v1.41.0 ## 1.40.10 <!-- Release notes generated using configuration in .github/release.yml at v1.40.10 --> ## What's Changed ### Other Changes * refactor(opentelemetry): depend on TUnit.Core instead of umbrella TUnit by @thomhurst in thomhurst/TUnit#5774 ### Dependencies * chore(deps): update tunit to 1.40.5 by @thomhurst in thomhurst/TUnit#5769 **Full Changelog**: thomhurst/TUnit@v1.40.5...v1.40.10 Commits viewable in [compare view](thomhurst/TUnit@v1.40.5...v1.45.0). </details> Updated [TUnit.AspNetCore](https://github.com/thomhurst/TUnit) from 1.40.5 to 1.45.0. <details> <summary>Release notes</summary> _Sourced from [TUnit.AspNetCore's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.45.0 <!-- Release notes generated using configuration in .github/release.yml at v1.45.0 --> ## What's Changed ### Other Changes * fix(generator): place CallerArgumentExpression before params in [GenerateAssertion] emit by @JohnVerheij in thomhurst/TUnit#5940 * fix(sourcegen): drop covariant TActual when [GenerateAssertion] method has its own type parameters by @JohnVerheij in thomhurst/TUnit#5935 * feat(assertions): add CancellationToken overload to WaitsFor and Eventually by @JohnVerheij in thomhurst/TUnit#5938 * fix(html-report): extract categories using MTP Key=name convention by @thomhurst in thomhurst/TUnit#5946 * feat(html-report): rewrite as split-pane design template by @thomhurst in thomhurst/TUnit#5947 ### Dependencies * chore(deps): update microsoft.testing to 2.2.3 by @thomhurst in thomhurst/TUnit#5927 * chore(deps): update mstest to 4.2.3 by @thomhurst in thomhurst/TUnit#5928 * chore(deps): update tunit to 1.44.39 by @thomhurst in thomhurst/TUnit#5929 * chore(deps): update aspire to 13.3.3 by @thomhurst in thomhurst/TUnit#5933 * chore(deps): update dependency dompurify to v3.4.4 by @thomhurst in thomhurst/TUnit#5944 * chore(deps): update dependency qs to v6.15.2 by @thomhurst in thomhurst/TUnit#5941 **Full Changelog**: thomhurst/TUnit@v1.44.39...v1.45.0 ## 1.44.39 <!-- Release notes generated using configuration in .github/release.yml at v1.44.39 --> ## What's Changed ### Other Changes * fix(tests): retry trx read to dodge MTP post-exit flush race on Windows by @thomhurst in thomhurst/TUnit#5888 * fix(pipeline): timeout + retry InstallPlaywrightModule so a hung download fails fast by @thomhurst in thomhurst/TUnit#5889 * fix(otel): require two consecutive idle windows in DrainAsync to catch in-transit POSTs by @thomhurst in thomhurst/TUnit#5890 * test(assertions): drop flaky wall-clock upper bound on WaitsFor timeout test by @thomhurst in thomhurst/TUnit#5886 * fix(sourcegen): drop spurious ')' in MethodAssertionGenerator Task<bool> emit by @JohnVerheij in thomhurst/TUnit#5920 * fix(sourcegen): merge generic parameter lists in [AssertionExtension] emit by @JohnVerheij in thomhurst/TUnit#5921 * fix(aspnetcore): scope correlation processor per-factory to stop cross-factory tag leak by @thomhurst in thomhurst/TUnit#5891 * Changed FSharp.Core version to 10.1.300 by @licon4812 in thomhurst/TUnit#5909 * feat(mocks): add Mock.HttpClientFactory() helper by @thomhurst in thomhurst/TUnit#5894 * Harden WaitsFor timeout test by @thomhurst in thomhurst/TUnit#5926 * fix(sourcegen): emit `default` literal for value-type assertion parameters by @JohnVerheij in thomhurst/TUnit#5919 ### Dependencies * chore(deps): update dependency nunit to 4.6.0 by @thomhurst in thomhurst/TUnit#5826 * chore(deps): update tunit to 1.44.0 by @thomhurst in thomhurst/TUnit#5882 * chore(deps): update dependency mockolate to 3.2.0 by @thomhurst in thomhurst/TUnit#5892 * chore(deps): update dependency yaml to v2.9.0 by @thomhurst in thomhurst/TUnit#5887 * chore(deps): update dependency nuget.protocol to 7.6.0 by @thomhurst in thomhurst/TUnit#5897 * chore(deps): update dependency microsoft.entityframeworkcore to 10.0.8 by @thomhurst in thomhurst/TUnit#5898 * chore(deps): update dependency microsoft.templateengine.authoring.cli to v10.0.300 by @thomhurst in thomhurst/TUnit#5899 * chore(deps): update microsoft.extensions by @thomhurst in thomhurst/TUnit#5905 * chore(deps): update microsoft.aspnetcore to 10.0.8 by @thomhurst in thomhurst/TUnit#5904 * chore(deps): update dependency microsoft.templateengine.authoring.templateverifier to 10.0.300 by @thomhurst in thomhurst/TUnit#5902 * chore(deps): update aspire to 13.3.1 by @thomhurst in thomhurst/TUnit#5900 * chore(deps): update dependency system.commandline to 2.0.8 by @thomhurst in thomhurst/TUnit#5903 * chore(deps): update dependency azure.storage.blobs to 12.28.0 by @thomhurst in thomhurst/TUnit#5910 * chore(deps): update dependency dotnet-sdk to v10.0.300 by @thomhurst in thomhurst/TUnit#5901 * chore(deps): update dependency stackexchange.redis to 2.13.1 by @thomhurst in thomhurst/TUnit#5906 * chore(deps): update aspire to 13.3.2 by @thomhurst in thomhurst/TUnit#5924 * chore(deps): bump mermaid from 11.12.2 to 11.15.0 in /docs by @dependabot[bot] in thomhurst/TUnit#5893 * chore(deps): update dependency streamjsonrpc to 2.24.92 by @thomhurst in thomhurst/TUnit#5915 * chore(deps): update dependency dompurify to v3.4.3 by @thomhurst in thomhurst/TUnit#5913 * chore(deps): update microsoft.build to 18.6.3 by @thomhurst in thomhurst/TUnit#5914 **Full Changelog**: thomhurst/TUnit@v1.44.0...v1.44.39 ## 1.44.0 <!-- Release notes generated using configuration in .github/release.yml at v1.44.0 --> ## What's Changed ### Other Changes * Generated mocks live in the same namespace as the mocked type by @thomhurst in thomhurst/TUnit#5870 * Show multi-step test spans in class timeline, align report ordering with execution, and correlate linked OTel activities by @Copilot in thomhurst/TUnit#5847 * fix: don't leak RUC onto Should-style comparer overloads (#5857) by @thomhurst in thomhurst/TUnit#5873 * Fix culture-dependent timestamp in HTML test report (#5868) by @thomhurst in thomhurst/TUnit#5872 * fix(mocks-http): auto-prepend `/` to partial endpoint paths (#5838) by @thomhurst in thomhurst/TUnit#5874 * Replace Report.ExpandClassTimeline with [ClassTimeline] attribute by @thomhurst in thomhurst/TUnit#5875 * feat(assertions): make ShouldAssertion<T> implement IAssertion (#5824) by @thomhurst in thomhurst/TUnit#5876 * feat(mocks): support non-span ref struct out/ref params by @thomhurst in thomhurst/TUnit#5878 * fix(core): fill optional params when invoking MethodDataSource via reflection by @thomhurst in thomhurst/TUnit#5880 * Mocks: structural fix for Mock<T> / mocked-member name collisions by @thomhurst in thomhurst/TUnit#5881 * chore(mocks): promote TUnit.Mocks packages to stable by @thomhurst in thomhurst/TUnit#5877 ### Dependencies * chore(deps): update tunit to 1.43.41 by @thomhurst in thomhurst/TUnit#5863 * chore(deps): update dependency tunit.assertions.fsharp to 1.43.41 by @thomhurst in thomhurst/TUnit#5865 * chore(deps): bump @babel/plugin-transform-modules-systemjs from 7.28.5 to 7.29.4 in /docs by @dependabot[bot] in thomhurst/TUnit#5867 * chore(deps): bump fast-uri from 3.1.0 to 3.1.2 in /docs by @dependabot[bot] in thomhurst/TUnit#5862 **Full Changelog**: thomhurst/TUnit@v1.43.41...v1.44.0 ## 1.43.41 <!-- Release notes generated using configuration in .github/release.yml at v1.43.41 --> ## What's Changed ### Other Changes * feat(playwright): expose default Context/Launch options on settings by @thomhurst in thomhurst/TUnit#5861 * fix(hooks): resolve TestDiscovery hook context type by attribute kind, not method name by @thomhurst in thomhurst/TUnit#5860 ### Dependencies * chore(deps): update tunit to 1.43.38 by @thomhurst in thomhurst/TUnit#5858 **Full Changelog**: thomhurst/TUnit@v1.43.38...v1.43.41 ## 1.43.38 <!-- Release notes generated using configuration in .github/release.yml at v1.43.38 --> ## What's Changed ### Other Changes * feat(playwright): add TUnitPlaywrightSettings defaults by @thomhurst in thomhurst/TUnit#5859 **Full Changelog**: thomhurst/TUnit@v1.43.37...v1.43.38 ## 1.43.37 <!-- Release notes generated using configuration in .github/release.yml at v1.43.37 --> ## What's Changed ### Other Changes * docs: clarify MethodDataSourceAttribute.Factory is source-generator-managed by @Copilot in thomhurst/TUnit#5835 * fix(assertions): skip ref-struct members in IsEquivalentTo (#5841) by @thomhurst in thomhurst/TUnit#5842 * feat(playwright): add composition-based fixtures by @thomhurst in thomhurst/TUnit#5840 ### Dependencies * chore(deps): update tunit to 1.43.11 by @thomhurst in thomhurst/TUnit#5821 * chore(deps): update dependency polyfill to 10.4.0 by @thomhurst in thomhurst/TUnit#5830 * chore(deps): update dependency polyfill to 10.4.0 by @thomhurst in thomhurst/TUnit#5829 * chore(deps): update react to ^19.2.6 by @thomhurst in thomhurst/TUnit#5839 * chore(deps): update dependency polyfill to 10.5.0 by @thomhurst in thomhurst/TUnit#5848 * chore(deps): update dependency polyfill to 10.5.0 by @thomhurst in thomhurst/TUnit#5849 * chore(deps): update aspire to 13.3.0 by @thomhurst in thomhurst/TUnit#5851 * chore(deps): update dependency brace-expansion to v5.0.6 by @thomhurst in thomhurst/TUnit#5853 * chore(deps): update dependency polyfill to 10.5.1 by @thomhurst in thomhurst/TUnit#5854 * chore(deps): update dependency polyfill to 10.5.1 by @thomhurst in thomhurst/TUnit#5855 * chore(deps): update verify to 31.16.3 by @thomhurst in thomhurst/TUnit#5856 **Full Changelog**: thomhurst/TUnit@v1.43.11...v1.43.37 ## 1.43.11 <!-- Release notes generated using configuration in .github/release.yml at v1.43.11 --> ## What's Changed ### Other Changes * perf(engine): skip execution ledger for independent tests by @thomhurst in thomhurst/TUnit#5813 * perf(engine): skip tracked object graph rediscovery by @thomhurst in thomhurst/TUnit#5814 * fix: suppress XML doc warnings in generated sources by @mvanhorn in thomhurst/TUnit#5797 * Fix xUnit Throws migration docs by @thomhurst in thomhurst/TUnit#5819 * Fix Should optional overload generation by @thomhurst in thomhurst/TUnit#5820 ### Dependencies * chore(deps): update dependency mockolate to 3.1.0 by @thomhurst in thomhurst/TUnit#5811 * chore(deps): update tunit to 1.43.2 by @thomhurst in thomhurst/TUnit#5809 * chore(deps): update dependency nunit.analyzers to 4.13.0 by @thomhurst in thomhurst/TUnit#5815 * chore(deps): update dependency yaml to v2.8.4 by @thomhurst in thomhurst/TUnit#5812 ## New Contributors * @mvanhorn made their first contribution in thomhurst/TUnit#5797 **Full Changelog**: thomhurst/TUnit@v1.43.2...v1.43.11 ## 1.43.2 <!-- Release notes generated using configuration in .github/release.yml at v1.43.2 --> ## What's Changed ### Other Changes * fix(assertions): emit auto-generated header in MethodAssertionGenerator output by @JohnVerheij in thomhurst/TUnit#5796 * fix(engine): enforce runtime exclusion for global [NotInParallel] (#5800) by @thomhurst in thomhurst/TUnit#5805 * feat(mocks): add Arg.AnyArgs() shortcut for setup/verify by @thomhurst in thomhurst/TUnit#5792 * fix(should): add specialized assertion sources by @thomhurst in thomhurst/TUnit#5806 ### Dependencies * chore(deps): update tunit to 1.41.0 by @thomhurst in thomhurst/TUnit#5789 * chore(deps): update microsoft.testing to 2.2.2 by @thomhurst in thomhurst/TUnit#5793 * chore(deps): update mstest to 4.2.2 by @thomhurst in thomhurst/TUnit#5794 * chore(deps): update docusaurus by @thomhurst in thomhurst/TUnit#5798 * chore(deps): update dependency dompurify to v3.4.2 by @thomhurst in thomhurst/TUnit#5799 * chore(deps): update dependency mockolate to v3 by @thomhurst in thomhurst/TUnit#5801 **Full Changelog**: thomhurst/TUnit@v1.41.0...v1.43.2 ## 1.41.0 <!-- Release notes generated using configuration in .github/release.yml at v1.41.0 --> ## TUnit Should() Assertions (beta) This versions comes with a beta version of TUnit.Assertions.Should - meaning that you can do: `myCode.Should().BeEqualTo(fluentShouldSyntax);` For more information see the docs: https://tunit.dev/docs/assertions/should-syntax ## What's Changed ### Other Changes * fix(reporters): unwrap TestFailedException for failure grouping by @thomhurst in thomhurst/TUnit#5776 * Fix item-at Satisfies source typing by @thomhurst in thomhurst/TUnit#5764 * ci(docs): add link-check automation and tighten Docusaurus strictness by @thomhurst in thomhurst/TUnit#5779 * feat: add TUnit.Assertions.Should package by @thomhurst in thomhurst/TUnit#5785 ### Dependencies * chore(deps): update tunit to 1.40.10 by @thomhurst in thomhurst/TUnit#5775 * chore(deps): update actions/cache action to v5 by @thomhurst in thomhurst/TUnit#5780 * chore(deps): update dependency microsoft.net.test.sdk to 18.5.1 by @thomhurst in thomhurst/TUnit#5784 **Full Changelog**: thomhurst/TUnit@v1.40.10...v1.41.0 ## 1.40.10 <!-- Release notes generated using configuration in .github/release.yml at v1.40.10 --> ## What's Changed ### Other Changes * refactor(opentelemetry): depend on TUnit.Core instead of umbrella TUnit by @thomhurst in thomhurst/TUnit#5774 ### Dependencies * chore(deps): update tunit to 1.40.5 by @thomhurst in thomhurst/TUnit#5769 **Full Changelog**: thomhurst/TUnit@v1.40.5...v1.40.10 Commits viewable in [compare view](thomhurst/TUnit@v1.40.5...v1.45.0). </details> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
Verification
Closes #5706