fast-check patterns for JS/TS — 6 canonical property categories with worked examples targeting agent-studio utilities (path normalization, safe-json, glob-to-regex, routing logic)
Scanned 9/6/2026
Install to Claude Code
npx -y skills add oimiragieo/agent-studio --skill property-based-testing --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Property Based Testing?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/oimiragieo-property-based-testing)More formats (shields.io, HTML) on the badges page.
---
name: property-based-testing
description: 'fast-check patterns for JS/TS — 6 canonical property categories with worked examples targeting agent-studio utilities (path normalization, safe-json, glob-to-regex, routing logic)'
version: 1.1.0
verified: true
lastVerifiedAt: '2026-03-01'
category: 'Testing'
agents: [qa, developer]
user_invocable: true
invoked_by: both
tools: [Read, Write, Bash]
tags: [testing, property-based, fast-check, javascript, typescript, fuzzing, invariants]
best_practices:
- State properties as invariants that must hold for ALL inputs
- Use shrinkage to get minimal counterexamples on failure
- Combine with unit tests — property tests find edge cases, unit tests document examples
error_handling: graceful
source: builtin
trust_score: 100
provenance_sha: 5cad2a35e425e814
---
# Property-Based Testing
fast-check patterns for JavaScript/TypeScript that find edge cases unit tests miss. Includes 6 canonical property categories with worked examples targeting agent-studio's own utilities.
## When to Use
- New utility functions (path normalization, parsers, transformers)
- Bug fixes where the fix has a general invariant (not just the specific repro case)
- Security-sensitive string handling (avoid escaping bugs, injection vectors)
- Any function where "this property should hold for ALL inputs" can be stated
## The 6 Canonical Property Categories
### 1. Inverse Operations (Round-trip)
```javascript
import fc from 'fast-check';
// If you serialize then deserialize, you get back the original
fc.assert(
fc.property(fc.anything(), value => {
expect(deserialize(serialize(value))).toEqual(value);
})
);
```
### 2. Idempotency
```javascript
// Applying a function twice gives same result as once
fc.assert(
fc.property(fc.string(), str => {
expect(normalize(normalize(str))).toEqual(normalize(str));
})
);
```
### 3. Commutativity / Order Independence
```javascript
// Order of inputs shouldn't matter
fc.assert(
fc.property(fc.array(fc.integer()), arr => {
expect(sum(arr)).toEqual(sum([...arr].reverse()));
})
);
```
### 4. Invariants (Properties that must always hold)
```javascript
// Output always has certain structural properties
fc.assert(
fc.property(fc.string(), input => {
const result = parseArgs(input);
expect(result).toHaveProperty('args');
expect(Array.isArray(result.args)).toBe(true);
})
);
```
### 5. Oracle Comparison (Simpler reference implementation)
```javascript
// Compare fast implementation against slow-but-correct reference
fc.assert(
fc.property(fc.array(fc.integer()), arr => {
expect(fastSort(arr)).toEqual(referenceSort(arr));
})
);
```
### 6. Metamorphic Relations
```javascript
// If input changes in a known way, output changes in a predictable way
fc.assert(
fc.property(fc.array(fc.integer()), arr => {
const sorted = sort(arr);
const sortedWithExtra = sort([...arr, Number.MAX_SAFE_INTEGER]);
expect(sortedWithExtra[sortedWithExtra.length - 1]).toBe(Number.MAX_SAFE_INTEGER);
})
);
```
## Agent-Studio Specific Examples
### Path Normalization (targets SE-01 from sharp-edges)
```javascript
import fc from 'fast-check';
const { normalizePath } = require('.claude/lib/utils/path-constants.cjs');
// Property: normalized path never contains backslashes
fc.assert(
fc.property(fc.string(), path => {
expect(normalizePath(path)).not.toMatch(/\\/);
})
);
// Property: idempotent
fc.assert(
fc.property(fc.string(), path => {
expect(normalizePath(normalizePath(path))).toEqual(normalizePath(path));
})
);
```
### Safe JSON Parser (targets SE-02 from sharp-edges)
```javascript
const { safeParseJSON } = require('.claude/lib/utils/safe-json.cjs');
// Property: never throws, always returns { success, data }
fc.assert(
fc.property(fc.string(), input => {
const result = safeParseJSON(input, null);
expect(result).toHaveProperty('success');
expect(typeof result.success).toBe('boolean');
})
);
// Property: prototype pollution not possible
fc.assert(
fc.property(fc.string(), input => {
const before = Object.prototype.toString;
safeParseJSON(input, null);
expect(Object.prototype.toString).toBe(before);
})
);
```
### Glob-to-Regex (targets SE-05 from sharp-edges)
```javascript
// Property: patterns with **/dir/** match root-level dir
fc.assert(
fc.property(fc.constantFrom('foo', 'bar', 'baz'), dir => {
const pattern = `**/${dir}/**`;
const regex = globToRegex(pattern);
expect(`${dir}/file.js`).toMatch(regex); // root-level match
expect(`a/${dir}/file.js`).toMatch(regex); // nested match
})
);
```
### Routing Logic
```javascript
// Property: routing is deterministic (same input -> same agent)
fc.assert(
fc.property(fc.string(), intent => {
expect(route(intent)).toEqual(route(intent));
})
);
// Property: routing never returns null/undefined
fc.assert(
fc.property(fc.string(), intent => {
expect(route(intent)).toBeTruthy();
})
);
```
## Installation
```bash
pnpm add -D fast-check
```
## Running Property Tests
```bash
# Run all property tests
node --test tests/**/*.property.test.cjs
# Run with verbose output (shows counterexamples on failure)
FAST_CHECK_VERBOSE=true node --test tests/**/*.property.test.cjs
```
## Shrinkage (Automatic Counterexample Minimization)
fast-check automatically shrinks failing inputs to the minimal counterexample. Example:
- Test fails on: `"C:\\Users\\foo\\deep\\nested\\path"`
- fast-check shrinks to: `"a\\b"` (minimal backslash case)
This makes debugging far faster than traditional fuzzing.
## Memory Protocol (MANDATORY)
**Before starting:**
Read `.claude/context/memory/learnings.md`
**After completing:**
- New pattern -> `.claude/context/memory/learnings.md`
- Issue found -> `.claude/context/memory/issues.md`
- Decision made -> `.claude/context/memory/decisions.md`
> ASSUME INTERRUPTION: If it's not in memory, it didn't happen.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!