diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9c97b932e9..5210ddf284 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -13,6 +13,7 @@
+
diff --git a/TUnit.Example.FsCheck.TestProject/PropertyTests.cs b/TUnit.Example.FsCheck.TestProject/PropertyTests.cs
new file mode 100644
index 0000000000..27193b2b26
--- /dev/null
+++ b/TUnit.Example.FsCheck.TestProject/PropertyTests.cs
@@ -0,0 +1,91 @@
+using FsCheck;
+using FsCheck.Fluent;
+using TUnit.FsCheck;
+
+namespace TUnit.Example.FsCheck.TestProject;
+
+public class PropertyTests
+{
+ [Test, FsCheckProperty]
+ public bool ReverseReverseIsOriginal(int[] array)
+ {
+ var reversed = array.AsEnumerable().Reverse().Reverse().ToArray();
+ return array.SequenceEqual(reversed);
+ }
+
+ [Test, FsCheckProperty]
+ public bool AbsoluteValueIsNonNegative(int value)
+ {
+ return Math.Abs((long)value) >= 0;
+ }
+
+ [Test, FsCheckProperty]
+ public bool StringConcatenationLength(string a, string b)
+ {
+ if (a == null || b == null)
+ {
+ return true; // Skip null cases
+ }
+
+ return (a + b).Length == a.Length + b.Length;
+ }
+
+ [Test, FsCheckProperty(MaxTest = 50)]
+ public bool ListConcatenationPreservesElements(int[] first, int[] second)
+ {
+ var combined = first.Concat(second).ToArray();
+ return combined.Length == first.Length + second.Length;
+ }
+
+ [Test, FsCheckProperty]
+ public void AdditionIsCommutative(int a, int b)
+ {
+ var result1 = a + b;
+ var result2 = b + a;
+
+ if (result1 != result2)
+ {
+ throw new InvalidOperationException($"Addition is not commutative: {a} + {b} = {result1}, {b} + {a} = {result2}");
+ }
+ }
+
+ [Test, FsCheckProperty]
+ public async Task AsyncPropertyTest(int value)
+ {
+ await Task.Delay(1); // Simulate async work
+
+ if (value * 0 != 0)
+ {
+ throw new InvalidOperationException("Multiplication by zero should always be zero");
+ }
+ }
+
+ [Test, FsCheckProperty]
+ public bool MultiplicationIsAssociative(int a, int b, int c)
+ {
+ // Using long to avoid overflow
+ var left = (long)a * ((long)b * c);
+ var right = ((long)a * b) * c;
+ return left == right;
+ }
+
+ [Test, FsCheckProperty]
+ public bool SumOfFourNumbersIsCommutative(int a, int b, int c, int d)
+ {
+ var sum1 = a + b + c + d;
+ var sum2 = d + c + b + a;
+ return sum1 == sum2;
+ }
+
+ [Test, FsCheckProperty]
+ public Property StringReversalProperty()
+ {
+ return Prop.ForAll(str =>
+ {
+ var reversed = new string(str.Reverse().ToArray());
+ var doubleReversed = new string(reversed.Reverse().ToArray());
+ return str == doubleReversed;
+ });
+ }
+
+}
diff --git a/TUnit.Example.FsCheck.TestProject/TUnit.Example.FsCheck.TestProject.csproj b/TUnit.Example.FsCheck.TestProject/TUnit.Example.FsCheck.TestProject.csproj
new file mode 100644
index 0000000000..f23b29646c
--- /dev/null
+++ b/TUnit.Example.FsCheck.TestProject/TUnit.Example.FsCheck.TestProject.csproj
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TUnit.Example.FsCheck.TestProject/TUnit.Example.FsCheck.TestProject.sln b/TUnit.Example.FsCheck.TestProject/TUnit.Example.FsCheck.TestProject.sln
new file mode 100644
index 0000000000..4ddb9e442f
--- /dev/null
+++ b/TUnit.Example.FsCheck.TestProject/TUnit.Example.FsCheck.TestProject.sln
@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Example.FsCheck.TestProject", "TUnit.Example.FsCheck.TestProject.csproj", "{41C48729-CBC0-9C84-9E2E-AD18967D3F54}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {41C48729-CBC0-9C84-9E2E-AD18967D3F54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {41C48729-CBC0-9C84-9E2E-AD18967D3F54}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {41C48729-CBC0-9C84-9E2E-AD18967D3F54}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {41C48729-CBC0-9C84-9E2E-AD18967D3F54}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {29D619C4-A78D-44B6-9D5C-304546DF9B20}
+ EndGlobalSection
+EndGlobal
diff --git a/TUnit.FsCheck/FsCheckPropertyAttribute.cs b/TUnit.FsCheck/FsCheckPropertyAttribute.cs
new file mode 100644
index 0000000000..7433a2618a
--- /dev/null
+++ b/TUnit.FsCheck/FsCheckPropertyAttribute.cs
@@ -0,0 +1,100 @@
+using TUnit.Core;
+using TUnit.Core.Interfaces;
+
+namespace TUnit.FsCheck;
+
+///
+/// Marks a test method as an FsCheck property-based test.
+/// The test method parameters will be generated by FsCheck and the test
+/// will be run multiple times with different generated values.
+///
+///
+/// This attribute must be used together with the .
+/// Example:
+///
+/// [Test, FsCheckProperty]
+/// public bool MyProperty(int value) => value * 0 == 0;
+///
+///
+[AttributeUsage(AttributeTargets.Method)]
+public class FsCheckPropertyAttribute : Attribute, ITestRegisteredEventReceiver, IDataSourceAttribute
+{
+
+ ///
+ /// The maximum number of tests to run. Default is 100.
+ ///
+ public int MaxTest { get; set; } = 100;
+
+ ///
+ /// The maximum number of rejected tests (tests that failed the precondition) before failing.
+ ///
+ public int MaxFail { get; set; } = 1000;
+
+ ///
+ /// The starting size for test generation. Size increases linearly between StartSize and EndSize.
+ ///
+ public int StartSize { get; set; } = 1;
+
+ ///
+ /// The ending size for test generation. Size increases linearly between StartSize and EndSize.
+ ///
+ public int EndSize { get; set; } = 100;
+
+ ///
+ /// If set, replay the test using this seed. Format: "seed1,seed2" or just "seed1".
+ /// Useful for reproducing failures.
+ ///
+ public string? Replay { get; set; }
+
+ ///
+ /// If true, output all generated arguments to the test output.
+ ///
+ public bool Verbose { get; set; }
+
+ ///
+ /// If true, suppress output on passing tests.
+ ///
+ public bool QuietOnSuccess { get; set; }
+
+ ///
+ /// The level of parallelism to use when running tests.
+ /// Default is 1 (no parallelism within property execution).
+ ///
+ public int Parallelism { get; set; } = 1;
+
+ ///
+ /// Types containing Arbitrary instances to use for generating test data.
+ ///
+ public Type[]? Arbitrary { get; set; }
+
+ ///
+ /// Gets the order in which this event receiver is executed.
+ ///
+ public int Order => 0;
+
+ ///
+ /// Not used - FsCheck generates its own data during test execution.
+ /// This property exists to satisfy the IDataSourceAttribute interface.
+ ///
+ public bool SkipIfEmpty { get; set; }
+
+ ///
+ /// Called when the test is registered. Sets up the FsCheck property executor.
+ ///
+ public ValueTask OnTestRegistered(TestRegisteredContext context)
+ {
+ context.SetTestExecutor(new FsCheckPropertyTestExecutor(this));
+ return default;
+ }
+
+ ///
+ /// Returns placeholder data - actual test data is generated by FsCheck during test execution.
+ ///
+#pragma warning disable CS1998 // Async method lacks 'await' operators
+ async IAsyncEnumerable>> IDataSourceAttribute.GetDataRowsAsync(DataGeneratorMetadata dataGeneratorMetadata)
+#pragma warning restore CS1998
+ {
+ // Return null array as placeholder - the FsCheckPropertyTestExecutor will generate actual data
+ yield return () => Task.FromResult