quickcheck-testing
Authors property-based tests for Haskell using QuickCheck (the original PBT library) and for Scala via ScalaCheck (the JVM port) - wires `quickCheck` (Haskell) / `forAll` (ScalaCheck) drivers, defines `Arbitrary` instances or generators, uses `shrink` to find minimal counterexamples, and integrates with HSpec / Tasty (Haskell) or specs2 / ScalaTest. Use when the codebase is Haskell or Scala and the team wants the canonical PBT library that the entire family (Hypothesis / fast-check / proptest / jqwik) was inspired by.
Install with skills.sh (any agent)
npx skills add testland/qa --skill quickcheck-testingquickcheck-testing
Overview
QuickCheck is the original property-based testing library (qc-hackage (opens in new window)). This skill covers both:
When to use
Step 1 - Install (Haskell)
-- In .cabal:
build-depends: QuickCheck >= 2.18
-- In stack.yaml: add 'QuickCheck' to extra-deps if not in resolverStep 2 - Install (Scala)
// build.sbt
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.18.0" % Test
// For ScalaTest integration:
libraryDependencies += "org.scalatest" %% "scalatest-propspec" % "3.2.18" % TestPinned versions and update notes: references/quickcheck-reference.md.
Step 3 - Basic Haskell property
import Test.QuickCheck
prop_reverseInvolutive :: [Int] -> Bool
prop_reverseInvolutive xs = reverse (reverse xs) == xs
main :: IO ()
main = quickCheck prop_reverseInvolutivequickCheck runs the property with 100 random [Int] values by default; on failure, prints the failing case + the shrunk minimal example.
For ghci:
ghci> quickCheck prop_reverseInvolutive
+++ OK, passed 100 tests.Step 4 - Custom generators
Define an Arbitrary instance with arbitrary (the generator) and shrink (which returns simpler candidates for minimizing a failure):
data User = User { userId :: Int, userEmail :: String, userAge :: Int }
deriving (Show, Eq)
instance Arbitrary User where
arbitrary = do
uid <- arbitrary `suchThat` (> 0)
name <- listOf1 (elements ['a'..'z'])
age <- choose (18, 100)
return $ User uid (name ++ "@example.com") age
shrink (User i e a) =
[ User i' e a | i' <- shrink i, i' > 0 ] ++
[ User i e a' | a' <- shrink a, a' >= 18, a' <= 100 ]The QuickCheck module map (Test.QuickCheck.Gen, .Arbitrary, .Function, .Monadic): references/quickcheck-reference.md.
Step 5 - Combinators
==> is conditional implication - discard cases where the precondition fails:
-- Conditional property: discard cases where xs is empty
prop_headOfNonEmpty :: [Int] -> Property
prop_headOfNonEmpty xs = not (null xs) ==> head xs == xs !! 0forAll (inline quantification) and classify / label (distribution tracking): references/quickcheck-reference.md.
Step 6 - ScalaCheck equivalent
import org.scalacheck._
import org.scalacheck.Prop.forAll
object UserSpecification extends Properties("User") {
implicit val userGen: Arbitrary[User] = Arbitrary {
for {
id <- Gen.posNum[Int]
name <- Gen.alphaLowerStr.suchThat(_.length >= 3)
age <- Gen.choose(18, 100)
} yield User(id, s"$name@example.com", age)
}
property("json round-trip") = forAll { (u: User) =>
val json = encode(u)
decode[User](json) == Right(u)
}
property("sorted list stays sorted") = forAll { (xs: List[Int]) =>
val sorted = xs.sorted
sorted == sorted.sorted
}
}Same shape as Haskell QuickCheck; Prop.forAll is the equivalent of quickCheck. ScalaTest integration (AnyPropSpec + ScalaCheckPropertyChecks): references/quickcheck-reference.md.
Step 7 - Configuration
Haskell:
import Test.QuickCheck
main = quickCheckWith (stdArgs { maxSuccess = 1000, maxSize = 100 }) prop_X
-- or:
quickCheckWith (stdArgs { maxSuccess = 100, maxSize = 50, maxDiscardRatio = 10 }) prop_XScala:
import org.scalacheck.Test
import org.scalacheck.Prop
val params = Test.Parameters.default
.withMinSuccessfulTests(1000)
.withMaxDiscardRatio(10.0f)Step 8 - CI integration
Haskell with cabal:
cabal testScala with sbt:
sbt testFor deterministic CI, set the seed:
quickCheckWith (stdArgs { replay = Just (mkQCGen 42, 0) }) prop_Xval params = Test.Parameters.default.withInitialSeed(rng.Seed(42L))References
QuickCheck / ScalaCheck reference
View source (opens in new window)QuickCheck / ScalaCheck reference
Detailed lookup material for quickcheck-testing. Sources: QuickCheck on Hackage (opens in new window), ScalaCheck (scalacheck.org).
Pinned versions
Check the sources before bumping.
QuickCheck modules
| Module | Use |
|---|---|
Test.QuickCheck | Main entry: quickCheck, verboseCheck. |
Test.QuickCheck.Arbitrary | Arbitrary typeclass for custom types. |
Test.QuickCheck.Gen | Custom generators. |
Test.QuickCheck.Function | Function generation. |
Test.QuickCheck.Monadic | "for testing stateful/monadic code" (qch (opens in new window)). |
Combinators (Haskell)
-- Quantify per-test scope
prop_sortIdempotent :: Property
prop_sortIdempotent = forAll (listOf1 arbitrary :: Gen [Int]) $ \xs ->
sort (sort xs) == sort xs
-- Classify cases for distribution monitoring
prop_lengthClassified :: [Int] -> Property
prop_lengthClassified xs = classify (null xs) "empty" $
classify (length xs > 100) "large" $
length (reverse xs) == length xsforAll quantifies inline; classify / label track distribution.
ScalaTest integration
import org.scalatest.propspec.AnyPropSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
class UserPropSpec extends AnyPropSpec with Matchers with ScalaCheckPropertyChecks {
property("reverse is involutive") {
forAll { (xs: List[Int]) =>
xs.reverse.reverse shouldBe xs
}
}
}Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Skipping shrink in custom Arbitrary | Failures aren't shrunk; counterexample messages are huge. | Always implement shrink (Step 4). |
Heavy ==> (Haskell) / suchThat (Scala) preconditions | Cases discarded; "Gave up after N tests" warning. | Restructure the generator to produce only valid inputs (Step 4-5). |
arbitrary without Arbitrary typeclass instance for custom types | Compile error / runtime "no instance" - must define instance per type. | Define Arbitrary T instance (Step 4). |
| Random seed in CI | Failures hard to reproduce. | Fixed seed (Step 8). |
quickCheck from Main | Mixes test code with executable. | Use HSpec / Tasty (Haskell) or ScalaTest (Scala) for organization. |
| Properties that always pass trivially | No actual verification; false confidence. | verboseCheck to see distribution; reformulate. |
Limitations
Related skills
fast-check-testing
Authors property-based tests in JavaScript / TypeScript using fast-check - wires `fc.assert(fc.property(arbitrary, ...))`, picks arbitraries (`fc.integer`, `fc.string`, `fc.array`, `fc.tuple`, `fc.record`), uses `.map()` / `.chain()` / `.filter()` to build domain arbitraries, and integrates with Jest / Vitest / Mocha / Jasmine / AVA / Tape. Use when a JS/TS codebase needs PBT to catch edge cases - fast-check has been used to find bugs in major libraries (`query-string`, etc.) and is trusted by Jest, Jasmine, fp-ts, Ramda.
hypothesis-testing
Authors property-based tests in Python using Hypothesis - wires `@given` with `strategies` (`st.integers`, `st.text`, `st.lists`, `st.from_regex`, `st.composite`), uses `assume()` / `.filter()` for preconditions, configures via `@settings(max_examples=..., deadline=...)`, and exploits Hypothesis's automatic shrinking to find the falsifying example. Integrates with pytest fixtures + parametrize. Use when a Python project needs PBT to catch edge cases the example-based tests miss - bug clusters around input ranges / boundary values / interaction between fields.
jqwik-testing
Authors property-based tests for the JVM (Java + Kotlin) using jqwik - wires `@Property` test methods, `@ForAll` parameter annotations, `Arbitraries.integers/strings/etc` generators, custom `@Provide` arbitraries, and the JUnit 5 platform integration. Use when a JVM project needs PBT - alternative to JUnit-QuickCheck and Vavr's property-checking; tightly integrates with JUnit 5 so property tests run alongside conventional unit tests in the same Maven / Gradle pipeline.
proptest-testing
Authors property-based tests in Rust using proptest - wires the `proptest!` macro, defines strategies (`prop::collection::vec`, type-driven `any` strategies, regex-based string strategies), uses the strategy-per-value model (vs QuickCheck's per-type) for flexible composition, and exploits proptest's automatic shrinking + persistence of failed cases (regression test artifact). Use when a Rust codebase needs PBT - pairs especially well with parsers, serializers, and any function with a structured input domain.