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
90%
Does it follow best practices?
Impact
94%
1.11xAverage score across 5 eval scenarios
Passed
No known issues
Dependency: go get github.com/flyingmutant/rapid
import "github.com/flyingmutant/rapid"rapid.Int() // Any int
rapid.IntRange(0, 100) // Constrained int
rapid.Float64() // Any float64
rapid.Float64Range(0, 1) // Constrained float
rapid.String() // Any string (including empty)
rapid.StringN(1, 50) // Sized string (1-50 chars)
rapid.Bool() // true or false
rapid.Byte() // Any byte
rapid.Slice(rapid.Int()) // Slice of ints
rapid.SliceN(rapid.Int(), 1, 10) // Sized slice
rapid.Map(rapid.String(), rapid.Int()) // map[string]int
rapid.Custom(genFn) // Custom generatorTest functions must take *rapid.T and use its Make method:
func TestSortLengthInvariant(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
lst := rapid.Slice(rapid.Int()).Draw(t, "lst")
originalLen := len(lst)
sort.Ints(lst)
if len(lst) != originalLen {
t.Fatalf("sort changed length: got %d, want %d", len(lst), originalLen)
}
})
}
func TestSortOrdered(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
lst := rapid.Slice(rapid.Int()).Draw(t, "lst")
sort.Ints(lst)
for i := 0; i < len(lst)-1; i++ {
if lst[i] > lst[i+1] {
t.Fatalf("sort failed at index %d: %d > %d", i, lst[i], lst[i+1])
}
}
})
}rapid.Check(t, func(t *rapid.T) {
a := rapid.Int().Draw(t, "a")
b := rapid.Int().Filter(func(i int) bool { return i != 0 }).Draw(t, "b")
// Now b is never 0
result := a / b * b // Lossy for integers, but division by zero is avoided
_ = result
})type User struct {
Name string
Age int
}
var userGen = rapid.Custom(func(t *rapid.T) User {
return User{
Name: rapid.StringN(1, 50).Draw(t, "name"),
Age: rapid.IntRange(0, 150).Draw(t, "age"),
}
})
func TestUserProperties(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
user := userGen.Draw(t, "user")
if len(user.Name) == 0 {
t.Error("expected non-empty name")
}
if user.Age < 0 || user.Age > 150 {
t.Errorf("age out of range: %d", user.Age)
}
})
}rapid automatically shrinks failing cases to minimal examples. The failure output shows the shrunken input:
test.go:42: after 37 attempts, counterexample found:
test.go:42: lst = [3, 1, 2]rapid.Check(t, func(t *rapid.T) {
// ...
})
// Default: 100 iterations
// Set via: rapid.Check(t, func(...) { ... }, rapid.MaxExamples(500))Note: rapid doesn't have a built-in way to configure iterations per test call in the current stable API. Use environment awareness: run fewer iterations in CI, more locally.
go test -v ./... # All tests
go test -v -run TestSort -count=1 # Specific test, no cache
go test -v -run "TestSort" -count=5 # Run multiple times for flakiness detection