CtrlK
BlogDocsLog inGet started
Tessl Logo

evilissimo/property-based-testing

Generates **property-based tests** that use randomized input generation to validate invariants and contracts (rather than hand-picked examples). Triggers when the conversation involves: PBT frameworks (Hypothesis library for Python, fast-check for TypeScript, proptest for Rust, rapid for Go, RapidCheck for C++); concepts like invariants, contracts, round-trip symmetry, encode/decode, serialize/deserialize, generative testing, or shrinking; or requests to find edge cases that example-based tests miss — e.g., "find edge cases automatically", "test all possible inputs", "verify this property holds". Does NOT trigger for: writing regular example-based unit tests, debugging, CI/CD setup, UI/component testing, or integration/E2E testing. Identifies up to 7 property patterns (round-trip, idempotence, invariance, metamorphic, inverse, ordering, no-crash), designs input generators, writes property tests, and extracts regression tests from failures.

91

1.11x
Quality

90%

Does it follow best practices?

Impact

94%

1.11x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

typescript.mdreferences/

TypeScript: fast-check

Dependency: npm install --save-dev fast-check

Core API

import * as fc from 'fast-check';

Common Arbitraries (Generators)

fc.integer()                          // Any integer
fc.integer({min: 0, max: 100})       // Constrained
fc.float()                            // Any float
fc.string()                           // Any string (including empty)
fc.string({minLength: 1, maxLength: 50})  // Sized strings
fc.boolean()                          // true or false
fc.constant(null)                     // Constant value
fc.constantFrom('a', 'b', 'c')       // Pick from list
fc.array(fc.integer())                // Arrays of integers
fc.array(fc.integer(), {minLength: 1, maxLength: 10})  // Sized arrays
fc.dictionary(fc.string(), fc.integer())  // Dicts
fc.tuple(fc.string(), fc.integer())   // Tuples
fc.oneof(fc.constant(null), fc.string()) // Union types
fc.record({                           // Objects (records)
  name: fc.string(),
  age: fc.integer({min: 0, max: 150}),
})

Writing Property Tests (Jest / Vitest)

import * as fc from 'fast-check';

describe('sort', () => {
  it('should preserve length after sorting', () => {
    fc.assert(
      fc.property(fc.array(fc.integer()), (arr) => {
        const originalLen = arr.length;
        const sorted = [...arr].sort((a, b) => a - b);
        expect(sorted).toHaveLength(originalLen);
      })
    );
  });

  it('should produce ordered result', () => {
    fc.assert(
      fc.property(fc.array(fc.integer()), (arr) => {
        const sorted = [...arr].sort((a, b) => a - b);
        for (let i = 0; i < sorted.length - 1; i++) {
          expect(sorted[i]).toBeLessThanOrEqual(sorted[i + 1]);
        }
      })
    );
  });
});

Preconditions (filtering)

import * as fc from 'fast-check';

it('should handle division', () => {
  fc.assert(
    fc.property(fc.integer(), fc.integer(), (a, b) => {
      fc.pre(b !== 0);  // Skip when b is 0
      expect(a / b * b).toBeCloseTo(a);
    })
  );
});

Async Property Tests

it('should process async', async () => {
  await fc.assert(
    fc.asyncProperty(fc.array(fc.integer()), async (arr) => {
      const result = await processAsync(arr);
      expect(result.length).toBe(arr.length);
    })
  );
});

Custom Arbitraries

// Map: transform generated values
const evenIntegers = fc.integer().map((x) => x * 2);

// Filter: only keep values matching predicate
const nonZero = fc.integer().filter((x) => x !== 0);

// Chained
const smallPositiveEvens = fc
  .integer({min: 1})
  .map((x) => x * 2)
  .filter((x) => x < 1000);

Complex Object Building

interface User {
  name: string;
  age: number;
  email: string;
}

const userArbitrary: fc.Arbitrary<User> = fc.record({
  name: fc.string({minLength: 1, maxLength: 50}),
  age: fc.integer({min: 0, max: 150}),
  email: fc.string(),
});

it('all created users have a name', () => {
  fc.assert(
    fc.property(userArbitrary, (user) => {
      expect(user.name.length).toBeGreaterThan(0);
    })
  );
});

Exceptions

it('throws on invalid input', () => {
  fc.assert(
    fc.property(fc.integer(), (x) => {
      expect(() => parseAge(x)).toThrow();
    })
  );
});

Configuration & Shrinking

fc.assert(
  fc.property(fc.array(fc.integer()), (arr) => {
    // test
  }),
  { numRuns: 1000 }  // Default is 100
);

// Verbose shrinking output
fc.assert(
  fc.property(fc.array(fc.integer()), (arr) => { ... }),
  { verbose: true }
);

Running

npx jest test_sort.test.ts         # Standard Jest run
npx jest --verbose                 # Verbose output

SKILL.md

tessl.json

tile.json