From d17c722ce17fc6c63b11a3025a2bb2ac31a0c021 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Thu, 29 Jan 2026 14:59:39 +0000
Subject: [PATCH] fix: session hooks in referenced library projects not
executing
The InfrastructureGenerator was not including project-to-project
references (CompilationReference) when generating Assembly.Load() calls.
During IDE design-time builds, project references are represented as
CompilationReference objects rather than PortableExecutableReference,
causing the library's module initializers (which register hooks) to not
be triggered early enough.
Renamed HasPhysicalLocation to IsLoadableAtRuntime and simplified the
logic to accept any assembly with a corresponding reference, since all
referenced assemblies will be available at runtime.
Fixes #4583
---
.../CodeGenerators/InfrastructureGenerator.cs | 20 ++++++++++++---
TUnit.TestProject.Library/Hooks.cs | 25 +++++++++++++++++++
.../Issue4583/LibraryTestSessionHookTests.cs | 20 +++++++++++++++
3 files changed, 61 insertions(+), 4 deletions(-)
create mode 100644 TUnit.TestProject/Bugs/Issue4583/LibraryTestSessionHookTests.cs
diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs b/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs
index bf25fae8cf..281ea2e7ba 100644
--- a/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs
+++ b/TUnit.Core.SourceGenerator/CodeGenerators/InfrastructureGenerator.cs
@@ -187,8 +187,8 @@ private static bool ShouldLoadAssembly(IAssemblySymbol assembly, Compilation com
return false;
}
- // Only load assemblies with physical locations
- if (!HasPhysicalLocation(assembly, compilation))
+ // Only load assemblies that will be available at runtime
+ if (!IsLoadableAtRuntime(assembly, compilation))
{
return false;
}
@@ -223,12 +223,24 @@ private static bool IsTUnitFrameworkAssembly(IAssemblySymbol assembly)
name.StartsWith("TUnit.Assertions.", StringComparison.Ordinal);
}
- private static bool HasPhysicalLocation(IAssemblySymbol assembly, Compilation compilation)
+ ///
+ /// Determines if an assembly will be loadable at runtime via Assembly.Load().
+ /// Any assembly that has a corresponding reference in the compilation will be available
+ /// at runtime because it will either be:
+ /// - A compiled DLL in the output directory (for project references)
+ /// - A NuGet package assembly in the probing paths (for package references)
+ ///
+ private static bool IsLoadableAtRuntime(IAssemblySymbol assembly, Compilation compilation)
{
+ // Find the MetadataReference that corresponds to this assembly symbol
var correspondingReference = compilation.References.FirstOrDefault(r =>
SymbolEqualityComparer.Default.Equals(compilation.GetAssemblyOrModuleSymbol(r), assembly));
- return correspondingReference is PortableExecutableReference { FilePath: not null and not "" };
+ // If there's a corresponding reference, the assembly will be available at runtime.
+ // This includes:
+ // - PortableExecutableReference: compiled DLLs (NuGet packages, project outputs)
+ // - CompilationReference: project-to-project references (will be compiled to DLLs)
+ return correspondingReference != null;
}
private static string GetAssemblyFullName(IAssemblySymbol assemblySymbol)
diff --git a/TUnit.TestProject.Library/Hooks.cs b/TUnit.TestProject.Library/Hooks.cs
index 217d5b6d0c..1f1ace727c 100644
--- a/TUnit.TestProject.Library/Hooks.cs
+++ b/TUnit.TestProject.Library/Hooks.cs
@@ -14,3 +14,28 @@ public void AfterTests(TestContext testContext)
testContext.StateBag.Items["AfterHit"] = true;
}
}
+
+///
+/// Test session hook in a library project.
+/// This tests the fix for https://github.com/thomhurst/TUnit/issues/4583
+/// where hooks in referenced library projects don't execute.
+///
+public static class LibraryTestSessionHooks
+{
+ public static bool BeforeTestSessionWasExecuted { get; private set; }
+ public static bool AfterTestSessionWasExecuted { get; private set; }
+
+ [Before(TestSession)]
+ public static Task BeforeTestSession()
+ {
+ BeforeTestSessionWasExecuted = true;
+ return Task.CompletedTask;
+ }
+
+ [After(TestSession)]
+ public static Task AfterTestSession()
+ {
+ AfterTestSessionWasExecuted = true;
+ return Task.CompletedTask;
+ }
+}
diff --git a/TUnit.TestProject/Bugs/Issue4583/LibraryTestSessionHookTests.cs b/TUnit.TestProject/Bugs/Issue4583/LibraryTestSessionHookTests.cs
new file mode 100644
index 0000000000..5a038f838e
--- /dev/null
+++ b/TUnit.TestProject/Bugs/Issue4583/LibraryTestSessionHookTests.cs
@@ -0,0 +1,20 @@
+using TUnit.TestProject.Attributes;
+using TUnit.TestProject.Library;
+
+namespace TUnit.TestProject.Bugs.Issue4583;
+
+///
+/// Reproduction test for https://github.com/thomhurst/TUnit/issues/4583
+/// Tests that [Before(TestSession)] hooks in REFERENCED LIBRARY projects are executed.
+/// This is different from issue #4541 which tested hooks in the same project.
+///
+[EngineTest(ExpectedResult.Pass)]
+public class LibraryTestSessionHookTests
+{
+ [Test]
+ public async Task Verify_TestSession_Hook_In_Referenced_Library_Executed()
+ {
+ await Assert.That(LibraryTestSessionHooks.BeforeTestSessionWasExecuted).IsTrue()
+ .Because("[Before(TestSession)] hook in referenced library project should have executed");
+ }
+}