Rapid Property-based Testing Engine
Boost productivity with intelligent property-based testing for multiple languages. Built for Claude Code with best practices and real-world patterns.
Property-Based Testing Engine
Advanced property-based testing framework that generates random inputs, discovers edge cases, and verifies program invariants automatically, going beyond example-based testing to find bugs you wouldn't think to test for.
When to Use This Skill
Choose Property-Based Testing when:
- Testing functions with large or complex input spaces
- Verifying mathematical properties (commutativity, idempotency, roundtrip)
- Finding edge cases in parsers, serializers, and data transformations
- Testing API contracts and data validation logic
- Validating state machines and concurrent systems
Consider alternatives when:
- Testing UI interactions — use end-to-end testing
- Testing specific business scenarios — use example-based tests
- Need deterministic CI behavior — seed your generators
Quick Start
# Activate property-based testing claude skill activate rapid-property-based-testing-engine # Add property tests to a module claude "Write property-based tests for the UserValidator class" # Find edge cases claude "Use property testing to find edge cases in the parseDate function"
Example Property Tests
// Using fast-check for TypeScript/JavaScript import fc from 'fast-check'; // Property: JSON.parse is the inverse of JSON.stringify test('JSON roundtrip preserves data', () => { fc.assert( fc.property( fc.jsonValue(), (value) => { const roundtripped = JSON.parse(JSON.stringify(value)); expect(roundtripped).toEqual(value); } ) ); }); // Property: sorting is idempotent test('sorting twice equals sorting once', () => { fc.assert( fc.property( fc.array(fc.integer()), (arr) => { const sortedOnce = [...arr].sort((a, b) => a - b); const sortedTwice = [...sortedOnce].sort((a, b) => a - b); expect(sortedTwice).toEqual(sortedOnce); } ) ); }); // Property: email validation accepts valid emails and rejects invalid test('email validator consistency', () => { const validEmailArb = fc.tuple( fc.stringOf(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyz0123456789'.split('')), { minLength: 1, maxLength: 20 }), fc.constantFrom('gmail.com', 'example.org', 'test.co.uk') ).map(([local, domain]) => `${local}@${domain}`); fc.assert( fc.property(validEmailArb, (email) => { expect(isValidEmail(email)).toBe(true); }) ); });
Core Concepts
Common Property Patterns
| Pattern | Description | Example |
|---|---|---|
| Roundtrip | encode then decode returns original | parse(stringify(x)) === x |
| Idempotence | applying twice equals applying once | sort(sort(x)) === sort(x) |
| Invariant | property holds for all valid inputs | length(filter(x)) <= length(x) |
| Commutativity | order doesn't matter | add(a, b) === add(b, a) |
| Associativity | grouping doesn't matter | concat(a, concat(b, c)) === concat(concat(a, b), c) |
| Oracle | compare against reference implementation | fastSort(x) === referenceSort(x) |
| Metamorphic | relate outputs of related inputs | search(db, q1 AND q2) ⊆ search(db, q1) |
Arbitrary Generators
| Generator | Produces | Example |
|---|---|---|
fc.integer() | Random integers | -2147483648 to 2147483647 |
fc.string() | Unicode strings | "", "aB3!@", "🎉" |
fc.array() | Arrays of values | [], [1, 2, 3] |
fc.object() | Plain objects | {a: 1, b: "x"} |
fc.date() | Date objects | new Date(...) |
fc.jsonValue() | Any JSON-valid value | Nested objects, arrays, primitives |
fc.oneof() | One of multiple generators | Union types |
| Custom | Domain-specific types | userArb, orderArb |
# Python with Hypothesis from hypothesis import given, strategies as st, settings @given(st.lists(st.integers())) def test_sort_preserves_length(xs): assert len(sorted(xs)) == len(xs) @given(st.lists(st.integers())) def test_sort_preserves_elements(xs): assert sorted(sorted(xs)) == sorted(xs) assert set(sorted(xs)) == set(xs) # Custom strategy for domain objects @st.composite def user_strategy(draw): return User( name=draw(st.text(min_size=1, max_size=100)), age=draw(st.integers(min_value=0, max_value=150)), email=draw(st.emails()), ) @given(user_strategy()) @settings(max_examples=1000) def test_user_serialization_roundtrip(user): serialized = user.to_json() deserialized = User.from_json(serialized) assert deserialized == user
Configuration
| Parameter | Description | Default |
|---|---|---|
num_runs | Number of random inputs per property | 100 |
seed | Random seed for reproducibility | Random |
max_shrinks | Maximum shrinking iterations on failure | 100 |
timeout_per_run | Timeout per individual test run | 5s |
verbose | Show generated examples during testing | false |
endOnFailure | Stop after first failing property | true |
examples | Explicit examples to always test | [] |
Best Practices
-
Test properties, not examples — Instead of testing
add(2, 3) === 5, testadd(a, b) === add(b, a)(commutativity) andadd(a, 0) === a(identity). Properties verify behavior across the entire input space, not just specific cases you thought of. -
Invest in custom generators for domain types — Generic generators produce unrealistic inputs that waste test budget. Build generators that produce valid domain objects (users, orders, transactions) that exercise realistic code paths and constraint boundaries.
-
Use shrinking to get minimal failing examples — When a property fails, the framework automatically shrinks the input to the smallest case that still fails. This makes debugging dramatically easier. Ensure custom generators support shrinking by building on primitive generators.
-
Combine property tests with example tests — Property tests find unexpected edge cases but can miss specific business rules. Use example-based tests for known scenarios and regulatory requirements, property tests for invariants and boundary exploration.
-
Pin seeds for CI determinism, randomize locally — Use a fixed seed in CI to prevent flaky builds, but run with random seeds during development. When random testing finds a failure, add the specific failing case as an explicit example test.
Common Issues
Property tests are slow because they generate too many complex inputs. Constrain generator sizes with maxLength, maxDepth, and custom filters. Use fc.statistics() to understand the distribution of generated inputs and ensure test budget is spent on interesting cases, not trivial ones.
Failing to identify useful properties for business logic. Start with these universal properties: roundtrip (serialize/deserialize), idempotence (applying operation twice), "no crash" (function doesn't throw for any valid input), and comparison with a simpler reference implementation. These catch surprisingly many bugs.
Flaky test failures that can't be reproduced. Always log the seed value when a property fails. Use fc.assert(property, { seed: <failing_seed> }) to reproduce deterministically. If failures are timing-dependent, the property may be testing concurrent behavior that needs explicit modeling rather than random exploration.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.