From f1c388e69c8d95b8ab5b912bdc8bee4f8ee49c5b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 27 Jan 2026 22:16:29 +0000
Subject: [PATCH 1/5] Initial plan
From d7900147cb3d30a5d2d193b655bf417b7b784cb8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 27 Jan 2026 22:21:13 +0000
Subject: [PATCH 2/5] Fix TestOutputHelper lifetime issue by guarding WriteLine
calls
Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com>
---
Directory.Packages.props | 1 -
src/FsCheck.Xunit/CheckExtensions.fs | 26 +++++++----
src/FsCheck.Xunit/PropertyAttribute.fs | 14 +++++-
src/FsCheck.Xunit/Runner.fs | 19 ++++++--
.../FsCheck.Test.CSharp.csproj | 9 ++++
.../TestOutputHelperLifetimeTests.cs | 43 +++++++++++++++++++
6 files changed, 97 insertions(+), 15 deletions(-)
create mode 100644 tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
diff --git a/Directory.Packages.props b/Directory.Packages.props
index cba689493..6eeced38c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,6 @@
-
diff --git a/src/FsCheck.Xunit/CheckExtensions.fs b/src/FsCheck.Xunit/CheckExtensions.fs
index e3c48b333..b83f2f75a 100644
--- a/src/FsCheck.Xunit/CheckExtensions.fs
+++ b/src/FsCheck.Xunit/CheckExtensions.fs
@@ -6,29 +6,39 @@ open FsCheck
open Xunit.Abstractions
module private Helper =
+ // Helper to safely write to output, handling case where test may have completed
+ let private safeWriteLine (testOutputHelper: ITestOutputHelper) (message: string) =
+ try
+ testOutputHelper.WriteLine(message)
+ with
+ | :? InvalidOperationException ->
+ // Test has completed, TestOutputHelper is no longer active
+ // Silently ignore as this is expected when runners outlive test lifetime
+ ()
+
let private runner (testOutputHelper: ITestOutputHelper) =
{ new IRunner with
member __.OnStartFixture t =
- Runner.onStartFixtureToString t |> testOutputHelper.WriteLine
+ Runner.onStartFixtureToString t |> safeWriteLine testOutputHelper
member __.OnArguments (ntest,args, every) =
- every ntest args |> testOutputHelper.WriteLine
+ every ntest args |> safeWriteLine testOutputHelper
member __.OnShrink(args, everyShrink) =
- everyShrink args |> testOutputHelper.WriteLine
+ everyShrink args |> safeWriteLine testOutputHelper
member __.OnFinished(name,testResult) =
- Runner.onFinishedToString name testResult |> testOutputHelper.WriteLine
+ Runner.onFinishedToString name testResult |> safeWriteLine testOutputHelper
}
let private throwingRunner (testOutputHelper: ITestOutputHelper) =
{ new IRunner with
member __.OnStartFixture t =
- testOutputHelper.WriteLine (Runner.onStartFixtureToString t)
+ safeWriteLine testOutputHelper (Runner.onStartFixtureToString t)
member __.OnArguments (ntest,args, every) =
- testOutputHelper.WriteLine (every ntest args)
+ safeWriteLine testOutputHelper (every ntest args)
member __.OnShrink(args, everyShrink) =
- testOutputHelper.WriteLine (everyShrink args)
+ safeWriteLine testOutputHelper (everyShrink args)
member __.OnFinished(name,testResult) =
match testResult with
- | TestResult.Passed _ -> testOutputHelper.WriteLine (Runner.onFinishedToString name testResult)
+ | TestResult.Passed _ -> safeWriteLine testOutputHelper (Runner.onFinishedToString name testResult)
| _ -> failwithf "%s" (Runner.onFinishedToString name testResult)
}
diff --git a/src/FsCheck.Xunit/PropertyAttribute.fs b/src/FsCheck.Xunit/PropertyAttribute.fs
index 9bf940491..080df1f69 100644
--- a/src/FsCheck.Xunit/PropertyAttribute.fs
+++ b/src/FsCheck.Xunit/PropertyAttribute.fs
@@ -80,6 +80,16 @@ module internal PropertyConfig =
{ Rnd = Rnd (seed,gamma); Size = size }
let toConfig (output : TestOutputHelper) propertyConfig =
+ // Helper to safely write to output, handling case where test may have completed
+ let safeWriteLine (message: string) =
+ try
+ output.WriteLine(message)
+ with
+ | :? InvalidOperationException ->
+ // Test has completed, TestOutputHelper is no longer active
+ // Silently ignore as this is expected when closures outlive test lifetime
+ ()
+
Config.Default
.WithReplay(
propertyConfig.Replay
@@ -100,13 +110,13 @@ module internal PropertyConfig =
.WithRunner(XunitRunner())
.WithEvery(
if propertyConfig.Verbose |> Option.exists id then
- fun n args -> output.WriteLine (Config.Verbose.Every n args); ""
+ fun n args -> safeWriteLine (Config.Verbose.Every n args); ""
else
Config.Quick.Every
)
.WithEveryShrink(
if propertyConfig.Verbose |> Option.exists id then
- fun args -> output.WriteLine (Config.Verbose.EveryShrink args); ""
+ fun args -> safeWriteLine (Config.Verbose.EveryShrink args); ""
else
Config.Quick.EveryShrink
)
diff --git a/src/FsCheck.Xunit/Runner.fs b/src/FsCheck.Xunit/Runner.fs
index ab682dc34..cdc4a842e 100644
--- a/src/FsCheck.Xunit/Runner.fs
+++ b/src/FsCheck.Xunit/Runner.fs
@@ -1,20 +1,31 @@
namespace FsCheck.Xunit
open FsCheck
+open System
/// A runner for FsCheck (i.e. that you can use as Config.Runner) which outputs
/// to Xunit's given ITestOutputHelper.
/// For example, { Config.QuickThrowOnFailure with Runner = TestOutputRunner(output) }
type TestOutputRunner(output: Xunit.Abstractions.ITestOutputHelper) =
+ // Helper to safely write to output, handling case where test may have completed
+ let safeWriteLine (message: string) =
+ try
+ output.WriteLine(message)
+ with
+ | :? InvalidOperationException ->
+ // Test has completed, TestOutputHelper is no longer active
+ // Silently ignore as this is expected when runner outlives test lifetime
+ ()
+
interface IRunner with
member _.OnStartFixture t =
- output.WriteLine (Runner.onStartFixtureToString t)
+ safeWriteLine (Runner.onStartFixtureToString t)
member _.OnArguments (ntest, args, every) =
- output.WriteLine (every ntest args)
+ safeWriteLine (every ntest args)
member _.OnShrink(args, everyShrink) =
- output.WriteLine (everyShrink args)
+ safeWriteLine (everyShrink args)
member _.OnFinished(name,testResult) =
let resultText = Runner.onFinishedToString name testResult
match testResult with
- | TestResult.Passed _ -> resultText |> output.WriteLine
+ | TestResult.Passed _ -> resultText |> safeWriteLine
| _ -> failwithf "%s" resultText
\ No newline at end of file
diff --git a/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj b/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
index 1e3bf162d..e1928ef60 100644
--- a/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
+++ b/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
@@ -6,4 +6,13 @@
false
+
+
+
+
+
+
+
+
+
diff --git a/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs b/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
new file mode 100644
index 000000000..afbd6cb5f
--- /dev/null
+++ b/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
@@ -0,0 +1,43 @@
+using Xunit;
+using Xunit.Abstractions;
+
+namespace FsCheck.Test.CSharp
+{
+ ///
+ /// Reproduction test for GitHub issue: Lifetime problem with Xunit: InvalidOperationException: There is no currently active test.
+ /// This test class verifies that mixing Property and Fact tests with ITestOutputHelper doesn't cause lifetime issues.
+ ///
+ public class TestOutputHelperLifetimeTests
+ {
+ private readonly ITestOutputHelper _testOutputHelper;
+
+ public TestOutputHelperLifetimeTests(ITestOutputHelper testOutputHelper)
+ {
+ _testOutputHelper = testOutputHelper;
+ }
+
+ [FsCheck.Xunit.Property]
+ public void Test1()
+ {
+ _testOutputHelper.WriteLine($"{nameof(Test1)}");
+ }
+
+ [Fact]
+ public void Test2()
+ {
+ _testOutputHelper.WriteLine($"{nameof(Test2)}");
+ }
+
+ [FsCheck.Xunit.Property]
+ public void Test3()
+ {
+ _testOutputHelper.WriteLine($"{nameof(Test3)}");
+ }
+
+ [Fact]
+ public void Test4()
+ {
+ _testOutputHelper.WriteLine($"{nameof(Test4)}");
+ }
+ }
+}
From afa63378b85399b224c7c30ff87a270970c7ea5a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 27 Jan 2026 22:22:42 +0000
Subject: [PATCH 3/5] Add test runner reference and verify fix works
Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com>
---
tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj b/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
index e1928ef60..1074563b4 100644
--- a/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
+++ b/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
@@ -13,6 +13,10 @@
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
From 4318c3cd818cdfb13274eec82bbab735a8fee310 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 27 Jan 2026 22:26:00 +0000
Subject: [PATCH 4/5] Improve test to exercise property-based testing and
verbose mode
Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com>
---
.../TestOutputHelperLifetimeTests.cs | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs b/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
index afbd6cb5f..08714a655 100644
--- a/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
+++ b/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
@@ -17,9 +17,9 @@ public TestOutputHelperLifetimeTests(ITestOutputHelper testOutputHelper)
}
[FsCheck.Xunit.Property]
- public void Test1()
+ public void Test1(int x)
{
- _testOutputHelper.WriteLine($"{nameof(Test1)}");
+ _testOutputHelper.WriteLine($"{nameof(Test1)}: {x}");
}
[Fact]
@@ -29,9 +29,9 @@ public void Test2()
}
[FsCheck.Xunit.Property]
- public void Test3()
+ public void Test3(string s)
{
- _testOutputHelper.WriteLine($"{nameof(Test3)}");
+ _testOutputHelper.WriteLine($"{nameof(Test3)}: {s ?? "null"}");
}
[Fact]
@@ -39,5 +39,16 @@ public void Test4()
{
_testOutputHelper.WriteLine($"{nameof(Test4)}");
}
+
+ ///
+ /// This test specifically exercises the Every and EveryShrink callbacks by enabling Verbose mode.
+ /// These callbacks capture the TestOutputHelper in closures, which was the root cause of the lifetime issue.
+ ///
+ [FsCheck.Xunit.Property(Verbose = true, MaxTest = 5)]
+ public bool Test5_VerboseMode(int x, int y)
+ {
+ _testOutputHelper.WriteLine($"{nameof(Test5_VerboseMode)}: x={x}, y={y}");
+ return true; // Always pass
+ }
}
}
From 81dad6e22beba82be00df4972702a1c269ceea39 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 14:13:49 +0000
Subject: [PATCH 5/5] Refactor: Create Helpers module and move tests to F#
- Created Helpers.fs with centralized safeWriteLine function
- Updated PropertyAttribute.fs, CheckExtensions.fs, and Runner.fs to use the helper
- Moved TestOutputHelperLifetimeTests from C# to F# in PropertyAttributeTests.fs
- Reverted unnecessary C# test project changes
Co-authored-by: kurtschelfthout <164917+kurtschelfthout@users.noreply.github.com>
---
src/FsCheck.Xunit/CheckExtensions.fs | 26 +++------
src/FsCheck.Xunit/FsCheck.Xunit.fsproj | 1 +
src/FsCheck.Xunit/Helpers.fs | 19 +++++++
src/FsCheck.Xunit/PropertyAttribute.fs | 14 +----
src/FsCheck.Xunit/Runner.fs | 18 ++-----
.../FsCheck.Test.CSharp.csproj | 13 -----
.../TestOutputHelperLifetimeTests.cs | 54 -------------------
.../Fscheck.XUnit/PropertyAttributeTests.fs | 33 ++++++++++++
8 files changed, 67 insertions(+), 111 deletions(-)
create mode 100644 src/FsCheck.Xunit/Helpers.fs
delete mode 100644 tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
diff --git a/src/FsCheck.Xunit/CheckExtensions.fs b/src/FsCheck.Xunit/CheckExtensions.fs
index b83f2f75a..20632106e 100644
--- a/src/FsCheck.Xunit/CheckExtensions.fs
+++ b/src/FsCheck.Xunit/CheckExtensions.fs
@@ -6,39 +6,29 @@ open FsCheck
open Xunit.Abstractions
module private Helper =
- // Helper to safely write to output, handling case where test may have completed
- let private safeWriteLine (testOutputHelper: ITestOutputHelper) (message: string) =
- try
- testOutputHelper.WriteLine(message)
- with
- | :? InvalidOperationException ->
- // Test has completed, TestOutputHelper is no longer active
- // Silently ignore as this is expected when runners outlive test lifetime
- ()
-
let private runner (testOutputHelper: ITestOutputHelper) =
{ new IRunner with
member __.OnStartFixture t =
- Runner.onStartFixtureToString t |> safeWriteLine testOutputHelper
+ Runner.onStartFixtureToString t |> Helpers.safeWriteLine testOutputHelper
member __.OnArguments (ntest,args, every) =
- every ntest args |> safeWriteLine testOutputHelper
+ every ntest args |> Helpers.safeWriteLine testOutputHelper
member __.OnShrink(args, everyShrink) =
- everyShrink args |> safeWriteLine testOutputHelper
+ everyShrink args |> Helpers.safeWriteLine testOutputHelper
member __.OnFinished(name,testResult) =
- Runner.onFinishedToString name testResult |> safeWriteLine testOutputHelper
+ Runner.onFinishedToString name testResult |> Helpers.safeWriteLine testOutputHelper
}
let private throwingRunner (testOutputHelper: ITestOutputHelper) =
{ new IRunner with
member __.OnStartFixture t =
- safeWriteLine testOutputHelper (Runner.onStartFixtureToString t)
+ Helpers.safeWriteLine testOutputHelper (Runner.onStartFixtureToString t)
member __.OnArguments (ntest,args, every) =
- safeWriteLine testOutputHelper (every ntest args)
+ Helpers.safeWriteLine testOutputHelper (every ntest args)
member __.OnShrink(args, everyShrink) =
- safeWriteLine testOutputHelper (everyShrink args)
+ Helpers.safeWriteLine testOutputHelper (everyShrink args)
member __.OnFinished(name,testResult) =
match testResult with
- | TestResult.Passed _ -> safeWriteLine testOutputHelper (Runner.onFinishedToString name testResult)
+ | TestResult.Passed _ -> Helpers.safeWriteLine testOutputHelper (Runner.onFinishedToString name testResult)
| _ -> failwithf "%s" (Runner.onFinishedToString name testResult)
}
diff --git a/src/FsCheck.Xunit/FsCheck.Xunit.fsproj b/src/FsCheck.Xunit/FsCheck.Xunit.fsproj
index 9f41ecf07..d3fe51a75 100644
--- a/src/FsCheck.Xunit/FsCheck.Xunit.fsproj
+++ b/src/FsCheck.Xunit/FsCheck.Xunit.fsproj
@@ -14,6 +14,7 @@
+
diff --git a/src/FsCheck.Xunit/Helpers.fs b/src/FsCheck.Xunit/Helpers.fs
new file mode 100644
index 000000000..6bf444dae
--- /dev/null
+++ b/src/FsCheck.Xunit/Helpers.fs
@@ -0,0 +1,19 @@
+namespace FsCheck.Xunit
+
+open System
+open Xunit.Abstractions
+
+module internal Helpers =
+ ///
+ /// Safely writes to a TestOutputHelper, handling cases where the test may have completed
+ /// and the helper is no longer active. This prevents InvalidOperationException when
+ /// closures that capture the TestOutputHelper are called after the test lifetime ends.
+ ///
+ let safeWriteLine (output: ITestOutputHelper) (message: string) =
+ try
+ output.WriteLine(message)
+ with
+ | :? InvalidOperationException ->
+ // Test has completed, TestOutputHelper is no longer active
+ // Silently ignore as this is expected when closures outlive test lifetime
+ ()
diff --git a/src/FsCheck.Xunit/PropertyAttribute.fs b/src/FsCheck.Xunit/PropertyAttribute.fs
index 080df1f69..19d141cf4 100644
--- a/src/FsCheck.Xunit/PropertyAttribute.fs
+++ b/src/FsCheck.Xunit/PropertyAttribute.fs
@@ -80,16 +80,6 @@ module internal PropertyConfig =
{ Rnd = Rnd (seed,gamma); Size = size }
let toConfig (output : TestOutputHelper) propertyConfig =
- // Helper to safely write to output, handling case where test may have completed
- let safeWriteLine (message: string) =
- try
- output.WriteLine(message)
- with
- | :? InvalidOperationException ->
- // Test has completed, TestOutputHelper is no longer active
- // Silently ignore as this is expected when closures outlive test lifetime
- ()
-
Config.Default
.WithReplay(
propertyConfig.Replay
@@ -110,13 +100,13 @@ module internal PropertyConfig =
.WithRunner(XunitRunner())
.WithEvery(
if propertyConfig.Verbose |> Option.exists id then
- fun n args -> safeWriteLine (Config.Verbose.Every n args); ""
+ fun n args -> Helpers.safeWriteLine output (Config.Verbose.Every n args); ""
else
Config.Quick.Every
)
.WithEveryShrink(
if propertyConfig.Verbose |> Option.exists id then
- fun args -> safeWriteLine (Config.Verbose.EveryShrink args); ""
+ fun args -> Helpers.safeWriteLine output (Config.Verbose.EveryShrink args); ""
else
Config.Quick.EveryShrink
)
diff --git a/src/FsCheck.Xunit/Runner.fs b/src/FsCheck.Xunit/Runner.fs
index cdc4a842e..1f9c6f503 100644
--- a/src/FsCheck.Xunit/Runner.fs
+++ b/src/FsCheck.Xunit/Runner.fs
@@ -7,25 +7,15 @@ open System
/// to Xunit's given ITestOutputHelper.
/// For example, { Config.QuickThrowOnFailure with Runner = TestOutputRunner(output) }
type TestOutputRunner(output: Xunit.Abstractions.ITestOutputHelper) =
- // Helper to safely write to output, handling case where test may have completed
- let safeWriteLine (message: string) =
- try
- output.WriteLine(message)
- with
- | :? InvalidOperationException ->
- // Test has completed, TestOutputHelper is no longer active
- // Silently ignore as this is expected when runner outlives test lifetime
- ()
-
interface IRunner with
member _.OnStartFixture t =
- safeWriteLine (Runner.onStartFixtureToString t)
+ Helpers.safeWriteLine output (Runner.onStartFixtureToString t)
member _.OnArguments (ntest, args, every) =
- safeWriteLine (every ntest args)
+ Helpers.safeWriteLine output (every ntest args)
member _.OnShrink(args, everyShrink) =
- safeWriteLine (everyShrink args)
+ Helpers.safeWriteLine output (everyShrink args)
member _.OnFinished(name,testResult) =
let resultText = Runner.onFinishedToString name testResult
match testResult with
- | TestResult.Passed _ -> resultText |> safeWriteLine
+ | TestResult.Passed _ -> resultText |> Helpers.safeWriteLine output
| _ -> failwithf "%s" resultText
\ No newline at end of file
diff --git a/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj b/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
index 1074563b4..1e3bf162d 100644
--- a/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
+++ b/tests/FsCheck.Test.CSharp/FsCheck.Test.CSharp.csproj
@@ -6,17 +6,4 @@
false
-
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
diff --git a/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs b/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
deleted file mode 100644
index 08714a655..000000000
--- a/tests/FsCheck.Test.CSharp/TestOutputHelperLifetimeTests.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using Xunit;
-using Xunit.Abstractions;
-
-namespace FsCheck.Test.CSharp
-{
- ///
- /// Reproduction test for GitHub issue: Lifetime problem with Xunit: InvalidOperationException: There is no currently active test.
- /// This test class verifies that mixing Property and Fact tests with ITestOutputHelper doesn't cause lifetime issues.
- ///
- public class TestOutputHelperLifetimeTests
- {
- private readonly ITestOutputHelper _testOutputHelper;
-
- public TestOutputHelperLifetimeTests(ITestOutputHelper testOutputHelper)
- {
- _testOutputHelper = testOutputHelper;
- }
-
- [FsCheck.Xunit.Property]
- public void Test1(int x)
- {
- _testOutputHelper.WriteLine($"{nameof(Test1)}: {x}");
- }
-
- [Fact]
- public void Test2()
- {
- _testOutputHelper.WriteLine($"{nameof(Test2)}");
- }
-
- [FsCheck.Xunit.Property]
- public void Test3(string s)
- {
- _testOutputHelper.WriteLine($"{nameof(Test3)}: {s ?? "null"}");
- }
-
- [Fact]
- public void Test4()
- {
- _testOutputHelper.WriteLine($"{nameof(Test4)}");
- }
-
- ///
- /// This test specifically exercises the Every and EveryShrink callbacks by enabling Verbose mode.
- /// These callbacks capture the TestOutputHelper in closures, which was the root cause of the lifetime issue.
- ///
- [FsCheck.Xunit.Property(Verbose = true, MaxTest = 5)]
- public bool Test5_VerboseMode(int x, int y)
- {
- _testOutputHelper.WriteLine($"{nameof(Test5_VerboseMode)}: x={x}, y={y}");
- return true; // Always pass
- }
- }
-}
diff --git a/tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs b/tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs
index ca998d2f0..50dca47d4 100644
--- a/tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs
+++ b/tests/FsCheck.Test/Fscheck.XUnit/PropertyAttributeTests.fs
@@ -91,3 +91,36 @@ module ``when type implements IAsyncLifetime`` =
[]
member this.``then InitializeAsync() is invoked``() =
executed = true
+
+/// Reproduction test for GitHub issue: Lifetime problem with Xunit: InvalidOperationException: There is no currently active test.
+/// This test class verifies that mixing Property and Fact tests with ITestOutputHelper doesn't cause lifetime issues.
+module ``when mixing Property and Fact tests with ITestOutputHelper`` =
+ open Xunit.Abstractions
+
+ type TestOutputHelperLifetimeTests(output: ITestOutputHelper) =
+
+ []
+ member _.``Property test with parameter writes to output`` (x: int) =
+ output.WriteLine($"Property test with parameter: {x}")
+ true
+
+ []
+ member _.``Fact test writes to output`` () =
+ output.WriteLine("Fact test")
+
+ []
+ member _.``Property test with string parameter writes to output`` (s: string) =
+ let str = if isNull s then "null" else s
+ output.WriteLine($"Property test with string: {str}")
+ true
+
+ []
+ member _.``Another fact test writes to output`` () =
+ output.WriteLine("Another fact test")
+
+ /// This test specifically exercises the Every and EveryShrink callbacks by enabling Verbose mode.
+ /// These callbacks capture the TestOutputHelper in closures, which was the root cause of the lifetime issue.
+ []
+ member _.``Verbose property test exercises Every and EveryShrink callbacks`` (x: int) (y: int) =
+ output.WriteLine($"Verbose mode test: x={x}, y={y}")
+ true