FsCheck (Property-Based Testing)
FsCheck is a property-based testing framework for .NET. Property-based testing generates random test data to verify that properties (invariants) hold true across many inputs.
There is a NuGet package to help integrate FsCheck with TUnit: TUnit.FsCheck
Installationâ
dotnet add package TUnit.FsCheck
Basic Usageâ
Use the [Test, FsCheckProperty] attributes together to create a property-based test:
using TUnit.FsCheck;
public class PropertyTests
{
[Test, FsCheckProperty]
public bool ReverseReverseIsOriginal(int[] array)
{
var reversed = array.Reverse().Reverse().ToArray();
return array.SequenceEqual(reversed);
}
[Test, FsCheckProperty]
public bool AdditionIsCommutative(int a, int b)
{
return a + b == b + a;
}
}
Return Typesâ
Property tests can return:
bool- The test passes if the property returnstruevoid- The test passes if no exception is thrownTask/ValueTask- For async propertiesProperty- For advanced FsCheck properties with custom generators
// Boolean property
[Test, FsCheckProperty]
public bool StringConcatLength(string a, string b)
{
if (a == null || b == null) return true; // Skip null cases
return (a + b).Length == a.Length + b.Length;
}
// Void property (throws on failure)
[Test, FsCheckProperty]
public void MultiplicationByZeroIsZero(int value)
{
if (value * 0 != 0)
throw new InvalidOperationException("Expected zero");
}
// Async property
[Test, FsCheckProperty]
public async Task AsyncPropertyTest(int value)
{
await Task.Delay(1);
if (value * 0 != 0)
throw new InvalidOperationException("Expected zero");
}
// FsCheck Property type for advanced scenarios
[Test, FsCheckProperty]
public Property StringReversalProperty()
{
return Prop.ForAll<string>(str =>
{
var reversed = new string(str.Reverse().ToArray());
var doubleReversed = new string(reversed.Reverse().ToArray());
return str == doubleReversed;
});
}
Configuration Optionsâ
The [FsCheckProperty] attribute supports several configuration options:
| Property | Default | Description |
|---|---|---|
MaxTest | 100 | Maximum number of tests to run |
MaxFail | 1000 | Maximum rejected tests before failing |
StartSize | 1 | Starting size for test generation |
EndSize | 100 | Ending size for test generation |
Replay | null | Seed to replay a specific test run |
Verbose | false | Output all generated arguments |
QuietOnSuccess | false | Suppress output on passing tests |
Arbitrary | null | Types containing custom Arbitrary instances |
Example with Configurationâ
[Test, FsCheckProperty(MaxTest = 50, StartSize = 1, EndSize = 50)]
public bool ListConcatenationPreservesElements(int[] first, int[] second)
{
var combined = first.Concat(second).ToArray();
return combined.Length == first.Length + second.Length;
}
Reproducing Failuresâ
When a property test fails, FsCheck reports the seed that can be used to reproduce the failure. Use the Replay property to run the test with a specific seed:
[Test, FsCheckProperty(Replay = "12345,67890")]
public bool MyProperty(int value)
{
return value >= 0; // Will reproduce the same failing case
}
Custom Generatorsâ
You can provide custom Arbitrary implementations for generating test data:
public class PositiveIntArbitrary
{
public static Arbitrary<int> PositiveInt()
{
return Arb.Default.Int32().Filter(x => x > 0);
}
}
[Test, FsCheckProperty(Arbitrary = new[] { typeof(PositiveIntArbitrary) })]
public bool PositiveNumbersArePositive(int value)
{
return value > 0;
}
Limitationsâ
- Native AOT: TUnit.FsCheck is not compatible with Native AOT publishing because FsCheck requires reflection and dynamic code generation
- Parameter count: Properties can have any number of parameters that FsCheck can generate
When to Use Property-Based Testingâ
Property-based testing is particularly useful for:
- Testing mathematical properties (commutativity, associativity, etc.)
- Serialization/deserialization round-trips
- Data structure invariants
- Parsing and formatting functions
- Any code where you can express general rules that should always hold