Property based testing
Skill almasumdev/awesome-mobile-testing-agent-skills/.github/skills/unit/property-based-testing
Expert guidance on adding property-based tests to mobile codebases using kotest-property, SwiftCheck, glados (Dart), and fast-check (TS). Use when example-based tests feel incomplete or when testing parsers, serializers, and state machines.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-testing-agent-skills --skill property-based-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.2 KB, ~1.0k tokens by cl100k_base, as published. Nobody here has run it
Property-Based Testing on Mobile
Instructions
Property-based testing (PBT) generates many inputs and asserts invariants that must hold for all of them. It is most valuable for parsers, serializers, mappers, reducers, pricing rules, and state machines — places where example-based tests only cover a handful of rows but the input space is large.
1. Properties, Not Examples
An example asserts f(3) == 9. A property asserts "for all non-negative n, sqrt(n*n) == n". Write down the invariant first, then encode it.
Common invariant shapes:
- Round-trip:
decode(encode(x)) == x. - Idempotence:
f(f(x)) == f(x)(e.g., normalization). - Commutativity / associativity: where the domain admits it.
- Monotonicity: adding an item never decreases cart total.
- Never-throws:
parse(s)returnsResultfor anyString.
2. Kotlin — kotest-property
class MoneyProps : StringSpec({
"addition is commutative" {
checkAll(Arb.money(), Arb.money()) { a, b ->
(a + b) shouldBe (b + a)
}
}
"rounding is idempotent" {
checkAll(Arb.bigDecimal()) { x ->
Money.round(Money.round(x)) shouldBe Money.round(x)
}
}
})
Custom generator:
fun Arb.Companion.money() = Arb.bigDecimal(
min = BigDecimal("-1000000"), max = BigDecimal("1000000")
).map(Money::usd)
3. Swift — SwiftCheck (or swift-testing custom generators)
import SwiftCheck
property("reverse is an involution") <- forAll { (xs: [Int]) in
xs.reversed().reversed() == xs
}
On newer Swift Testing code, you can hand-roll with @Test(arguments: ...) over a seeded random generator to approximate PBT until a library-level option matures for your project.
4. Dart — glados
void main() {
Glados<int>().test('abs is non-negative', (n) {
expect(n.abs() >= 0, isTrue);
});
}
Combine generators:
Glados2<int, int>().test('min(a,b) <= max(a,b)', (a, b) {
expect(min(a, b) <= max(a, b), isTrue);
});
5. TypeScript / React Native — fast-check
import fc from 'fast-check';
test('JSON round-trips', () => {
fc.assert(fc.property(fc.jsonValue(), (v) => {
expect(JSON.parse(JSON.stringify(v))).toEqual(v);
}));
});
fast-check ships arbitraries for dates, unicode strings, records, and a shrink mechanism that returns a minimal failing case.
6. Shrinking
The payoff of PBT is minimal failing examples. Do not wrap the body in try { ... } catch { /* swallow */ } — you will lose the shrink. Let the assertion throw and the framework will shrink the counterexample for you.
7. Determinism
- Seed the generator. All four libraries accept an explicit seed; log it on failure and use it to reproduce.
- Cap sample count in CI (e.g., 100–200 runs) and boost locally or nightly (e.g., 5 000).
- Keep PBT out of the fast "every save" loop if it exceeds ~1 s per property — move long ones to a nightly job.
8. When NOT to Use PBT
- Tests that assert on one canonical example from a spec (RFC, product decision) — keep them explicit.
- Very wide input spaces with shallow invariants — you will chase shrink noise.
- UI rendering — snapshot tests fit better.
9. Integrating with Existing Suites
Run PBT alongside example-based tests in the same runner. Keep a small number of regression examples pinned as example-based tests whenever PBT finds a bug — the property catches the class, the example pins the specific one.
10. Checklist
- Each property is named after the invariant it expresses.
- Generators cover the domain, including edge cases (empty, negative, unicode, NaN where relevant).
- Failing cases are shrunk to minimal counterexamples.
- Seed is logged on failure; CI can re-run with the same seed.
- Sample count is tuned per environment (PR vs nightly).
- Every PBT-discovered bug has a pinned example test for regression.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.