Your Unit Test Is Lying (And You Know It)
\n\nImagine: you wrote 50 unit tests, all green, 90% coverage. You're satisfied. But a week later, production crashes. It turns out your SortArray() method works perfectly for [1, 2, 3] and [3, 1, 2], but fails on [int.MaxValue, -1, 0]. Sound familiar?
The problem with classic unit tests is developer bias. You write tests for the data you expect. Hackers, real users, and random edge cases play by different rules.
\n\nThis is where property-based testing in C# comes into play. Instead of checking "if A, then B", you describe properties that should always be true. The framework itself generates hundreds of random inputs and checks your assertions.
\n\nIn this article, we'll break down how to implement property-based testing in C# using the FsCheck library, write real examples, and see how it catches bugs that regular tests miss.
\n\nWhat is Property-Based Testing?
\n\nProperty-based testing is an approach where you formulate invariants (properties) of your program. For example:
\n- \n
- "After sorting, the length of the array does not change" \n
- "Reversing a string twice gives the original string" \n
- "The sum of two positive numbers is always positive" \n
The framework (e.g., FsCheck) generates dozens and hundreds of random inputs and checks that the property holds for all of them. If a counterexample is found, the test fails, and you get the specific values that broke the code.
\n\nUnlike unit tests, you don't write Assert.AreEqual(3, Add(1,2)). You write Assert(x + y == y + x) — and test commutativity for thousands of number pairs.
Difference from Regular Unit Tests
\n\n| Unit Tests | Property-Based Tests |
|---|---|
| Check specific examples | Check general laws |
| Data is defined by the developer | Data is generated by the framework |
| Miss edge cases | Automatically explore boundaries |
| Easy to read | Require abstract thinking |
FsCheck Library: Your Main Tool
\n\nIn the C# world, the de facto standard for property-based testing is FsCheck (originally a port of the Haskell library QuickCheck). It integrates with xUnit, NUnit, and MSTest.
\n\nInstallation is simple:
\ndotnet add package FsCheck\ndotnet add package FsCheck.Xunit // for xUnit integration\n\nFsCheck can generate random data for primitives (int, string, float), lists, arrays, tuples, and even custom types (via custom generators).
Writing Your First Property-Based Test in C#
\n\nLet's start simple: verify that integer addition is commutative.
\n\nusing FsCheck;\nusing FsCheck.Xunit;\n\npublic class AdditionTests\n{\n [Property]\n public bool Commutative_Addition(int x, int y)\n {\n return x + y == y + x;\n }\n}\n\nRun the test. FsCheck will generate 100 random pairs of int (by default) and check the property. If no counterexample is found, the test is green. If we had made a mistake (e.g., wrote x - y == y - x), the test would fail with a specific example: (5, 3) — because 5 - 3 != 3 - 5.
Breaking Down the Code
\n- \n
- [Property] — an attribute that tells FsCheck this is a property-based test. \n
- The method returns
bool— if it returnsfalse, the test fails. \n - Parameters
int x, int y— FsCheck will automatically substitute random values