feat(aspire): add TUnit.Aspire.Core without TUnit metapackage dependency (#5471)#6243
Conversation
…ncy (#5471) Referencing TUnit.Aspire pulled in the TUnit metapackage, which sets IsTestProject/OutputType=Exe via build/TUnit.props. Any shared test-infrastructure library referencing TUnit.Aspire was therefore wrongly flagged as a test project. Mirror the existing TUnit.AspNetCore.Core split: - New TUnit.Aspire.Core holds all code, references only TUnit.Core + TUnit.OpenTelemetry. RootNamespace stays TUnit.Aspire. - TUnit.Aspire becomes a thin metapackage: references TUnit + TUnit.Aspire.Core (no source). Non-breaking for existing consumers. - TUnit.OpenTelemetry grants InternalsVisibleTo to TUnit.Aspire.Core (AspireFixture uses internal OtlpReceiver). - Register TUnit.Aspire.Core in all three solution files. - Document that shared infra libs should reference TUnit.Aspire.Core.
Up to standards ✅🟢 Issues
|
There was a problem hiding this comment.
Code Review
This is a clean, well-structured fix that faithfully mirrors the existing TUnit.AspNetCore / TUnit.AspNetCore.Core split. The approach is correct and the implementation is tight. A few minor observations and one potential gap worth noting:
What's done well
- The 5 source files are pure renames with zero content change — a good sanity check.
RootNamespaceis preserved onTUnit.Aspire.Core, so all public types keep their existing namespace and no consuming code needs changes.- The
InternalsVisibleTograph is correct:TUnit.OpenTelemetrynow grants access toTUnit.Aspire.Core(instead ofTUnit.Aspire), andTUnit.Aspire.Coregrants access to bothTUnit.AspireandTUnit.Aspire.Tests. - The
SupportedPlatform Remove="browser"block has been correctly moved toTUnit.Aspire.Coresince that's where the server-side Aspire code now lives. TUnit.Aspirecorrectly retains theTUnit.Analyzersanalyzer reference (as a thin metapackage that wires up the full toolchain for test projects), whileTUnit.Aspire.Coreonly shipsTUnit.Core.SourceGenerator— consistent with howTUnit.AspNetCore.Corehandles this.- All three
.slnxfiles are updated. - Documentation is updated with a clear tip and the right guidance.
One gap: TUnit.Aspire.Core has no package <Description>
TUnit.OpenTelemetry has a <Description> element in its csproj that shows up on NuGet. TUnit.Aspire.Core has none. This is a minor discoverability issue — a new package without a description is harder to find and evaluate on NuGet. Consider adding something like:
<Description>Core Aspire integration for TUnit — holds the fixtures and hooks without pulling in the TUnit metapackage. Use this in shared test-infrastructure libraries; use TUnit.Aspire in actual test projects.</Description>This is not a blocker, but worth doing since the split introduces a new published package.
Confirmed non-issue: InternalsVisibleTo on TUnit.Aspire
TUnit.Aspire.Core grants InternalsVisibleTo to TUnit.Aspire. Since TUnit.Aspire is now a source-free metapackage (no .cs files, just project references), this entry has no practical effect today. It's harmless to leave it in — future-proofing if TUnit.Aspire ever grows its own source — but a comment like <!-- reserved; TUnit.Aspire is currently source-free --> would save the next reader from wondering whether it's load-bearing.
Follow-up noted in the PR description
The PR correctly calls out that TUnit.Playwright and TUnit.FsCheck have the same latent issue. The deeper fix (gating IsTestProject in build/TUnit.props to not propagate transitively) is the right long-term solution and deferring it is reasonable given the scope of that packaging-contract change.
Overall this is a solid, minimal change that solves the reported problem without any breaking surface changes. The pattern is already proven by the TUnit.AspNetCore split.
…ore IVT The Aspire Core split (#5471) changed TUnit.OpenTelemetry's InternalsVisibleTo from TUnit.Aspire to TUnit.Aspire.Core, shifting the scrubbed/sorted assembly attribute list. Refresh the three OTel verified snapshots accordingly.
There was a problem hiding this comment.
Code Review
This is a clean, well-executed PR. The implementation precisely mirrors the established TUnit.AspNetCore / TUnit.AspNetCore.Core split pattern, and the core structure is correct. Reviewing against the previous claude[bot] feedback to check what's been addressed.
Previous review follow-up
<Description> in TUnit.Aspire.Core.csproj — the prior review suggested adding a NuGet description; it wasn't added. However, comparing against the pattern being mirrored: TUnit.AspNetCore.Core also lacks a <Description> element, so omitting it is internally consistent. That said, TUnit.OpenTelemetry does have one, and this is a new user-facing package. I'd lean toward adding one — e.g.:
<Description>Core Aspire integration for TUnit. References only TUnit.Core (not the TUnit metapackage), so shared test-infrastructure libraries can use Aspire fixtures without being flagged as test projects. Use TUnit.Aspire in actual test projects.</Description>Not a blocker, but worth doing for discoverability since this is the package users would explicitly add to a shared lib.
What's correct and well-done
- Pure git renames for all 5 source files — zero content drift, so there's no risk of divergence between the new location and what was tested in the old one.
RootNamespaceremainsTUnit.AspireinTUnit.Aspire.Core.csproj, so all public types keep their existing namespace. Zero breaking change for consumers.InternalsVisibleTograph is correctly rewired:TUnit.OpenTelemetrynow grants access toTUnit.Aspire.Core(whereAspireFixtureconsumes the internalOtlpReceiver), andTUnit.Aspire.Coregrants access to bothTUnit.Aspire(future-proofing) andTUnit.Aspire.Tests.TUnit.Aspire.Testsstill retainsInternalsVisibleTofromTUnit.OpenTelemetry(the unchanged line), so test coverage of OTel internals is unaffected.SupportedPlatform Remove="browser"correctly moved toTUnit.Aspire.Core— the server-side Aspire code is now there.- All three
.slnxfiles updated, public API snapshot files updated correctly. - Documentation updated with clear guidance on when to use
TUnit.Aspire.CorevsTUnit.Aspire. TUnit.Core.SourceGeneratorincluded as an Analyzer inTUnit.Aspire.Core— correct since the relocated types use TUnit attributes.
Minor note: harmless no-op InternalsVisibleTo
TUnit.Aspire.Core grants InternalsVisibleTo Include="TUnit.Aspire", but TUnit.Aspire is now source-free (no .cs files). This has no effect today and is fine as forward-compatibility plumbing — a brief comment (as suggested previously) would save a future reader from wondering if it's load-bearing, but it's not required.
Overall
The PR solves a real problem cleanly, without any observable breaking surface changes. The structure is proven by the AspNetCore split. Approving — the only open item (missing <Description>) is a nice-to-have that can be addressed in a follow-up alongside TUnit.AspNetCore.Core for consistency.
PR #6243 split TUnit.Aspire.Core out and made the TUnit.Aspire metapackage depend on it, but TUnit.Aspire.Core was never added to GetPackageProjectsModule's pack/push allowlist. So TUnit.Aspire 1.55.0 shipped a dependency on a package that was never published, breaking restore. Add TUnit_Aspire_Core to the list, mirroring the TUnit.AspNetCore / TUnit.AspNetCore.Core split which lists both.
Problem
Referencing
TUnit.Aspirepulls in theTUnitmetapackage, which setsIsTestProject/OutputType=Exevia the convention-packedbuild/TUnit.props. Those properties propagate to any consumer, so a shared test-infrastructure library that referencesTUnit.Aspiregets wrongly flagged as a test project. Reported in discussion #5471 (and matches the earlierTUnit.AspNetCorefix in #5474).Fix
Mirror the existing
TUnit.AspNetCore/TUnit.AspNetCore.Coresplit:TUnit.Aspire.Core— holds all code, references onlyTUnit.Core+TUnit.OpenTelemetry.RootNamespacestaysTUnit.Aspire, so public types keep their namespace.TUnit.Aspirebecomes a thin metapackage — referencesTUnit+TUnit.Aspire.Core, no source. Non-breaking: existing consumers keep the same public surface and transitively get.Core.TUnit.OpenTelemetrygrantsInternalsVisibleTotoTUnit.Aspire.Core(AspireFixtureconsumes the internalOtlpReceiver).TUnit.Aspire.Corein all three.slnxfiles.docs/examples/aspire.md: shared infra libs should referenceTUnit.Aspire.Core.How to use
Verification
TUnit.Aspire,TUnit.Aspire.Core, andTUnit.Aspire.Testsall build clean on net10.0 (0 warn / 0 err). The test project exercises theInternalsVisibleTowiring (OtlpReceiverfrom OTel,OtlpEndpointEnvironmentfrom.Core)..csfiles are pure git renames (0 content change).Follow-ups (not in this PR)
TUnit.PlaywrightandTUnit.FsCheck(both reference theTUnitmetapackage with no.Coretwin).IsTestProjectblock inbuild/TUnit.propsso it doesn't propagate transitively, so only direct consumers ofTUnitbecome test projects. Larger change to the packaging contract — deferred.