Fuzz a JSON Parser with Property Tests
Generate random and adversarial inputs to assert your JSON handling never crashes and always round-trips valid documents.
Task
Write property-based tests asserting parse-serialise round trips and graceful failure on malformed input.
When to use this
A custom parser crashes on deeply nested or unusual input that unit tests never generate.
Prerequisites
- A property-testing library such as fast-check
- The parser under test
- A defined valid-input space
Steps
1. Assert round-trip for valid values.
fc.assert(fc.property(fc.jsonValue(), (value) => {
expect(parse(serialize(value))).toEqual(value);
}));Expected output: Any generated valid JSON value survives a round trip unchanged.
2. Assert graceful failure on malformed input.
fc.assert(fc.property(fc.string(), (garbage) => {
try { parse(garbage); } catch (e) { expect(e).toBeInstanceOf(SyntaxError); }
}));Expected output: Malformed input raises a controlled error instead of crashing the process.
Troubleshooting
Problem
Shrinking produces confusing input
Solution
Use the shrunk counterexample and add it as a fixed regression case.
Problem
Generator too broad
Solution
Constrain generators to the actual input space to find meaningful bugs.
Limitations
- Property tests explore randomly, not exhaustively.
- Slow properties can time out CI.
- Found counterexamples must be pinned as regressions.
What this recipe does
Write property-based tests asserting parse-serialise round trips and graceful failure on malformed input.
A custom parser crashes on deeply nested or unusual input that unit tests never generate.
Prerequisites
- A property-testing library such as fast-check
- The parser under test
- A defined valid-input space
Procedure
- Assert round-trip for valid values.
fc.assert(fc.property(fc.jsonValue(), (value) => {
expect(parse(serialize(value))).toEqual(value);
}));
Expected result: Any generated valid JSON value survives a round trip unchanged.
- Assert graceful failure on malformed input.
fc.assert(fc.property(fc.string(), (garbage) => {
try { parse(garbage); } catch (e) { expect(e).toBeInstanceOf(SyntaxError); }
}));
Expected result: Malformed input raises a controlled error instead of crashing the process.
How to confirm it worked
Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows fuzzing finds no crash and any counterexample is reproduced by a pinned regression test.
Troubleshooting
Shrinking produces confusing input — Use the shrunk counterexample and add it as a fixed regression case.
Generator too broad — Constrain generators to the actual input space to find meaningful bugs.
Limitations
- Property tests explore randomly, not exhaustively.
- Slow properties can time out CI.
- Found counterexamples must be pinned as regressions.