Unit Testing with HUnit and Hspec
Hspec is the most widely used BDD-style testing framework in Haskell, built on top of HUnit-style assertions. It lets you write nested describe/it blocks with matchers like shouldBe and shouldSatisfy to state exactly what a function should return for a given input. Test-suites are declared in the .cabal file with type: exitcode-stdio-1.0 and a main-is entry point, and are run via cabal test or stack test, which compile the test-suite into a standalone executable.
Cricket analogy: Writing describe "reverse" $ it "returns [] for []" $ reverse [] shouldBe [] is like a coach running a specific fielding drill and checking one exact outcome -- catching a high ball cleanly -- rather than vaguely hoping the fielder 'does well' in a real match.
Property-Based Testing with QuickCheck
QuickCheck lets you state a property like prop_reverseTwice xs = reverse (reverse xs) == xs and it generates hundreds of random inputs, via the Arbitrary typeclass, trying to falsify that property. When it finds a failing case, QuickCheck automatically shrinks the input, trying smaller and simpler variants, down to the smallest counterexample that still fails, which makes debugging dramatically easier than staring at one huge random failing input.
Cricket analogy: QuickCheck generating hundreds of random input lists to try to break prop_reverseTwice is like a bowling machine firing hundreds of deliveries at varying pace and length to stress-test a batter's technique, rather than bowling the same three balls a coach already knows they can handle.
-- test/Spec.hs
import Test.Hspec
import Test.QuickCheck
import Data.List (sort)
reverseTwice :: [a] -> [a]
reverseTwice = reverse . reverse
main :: IO ()
main = hspec $ do
describe "reverseTwice" $ do
it "returns [] for an empty list" $
reverseTwice ([] :: [Int]) `shouldBe` []
it "is the identity function" $
property $ \xs -> reverseTwice (xs :: [Int]) == xs
describe "sort" $
it "produces a non-decreasing list" $
property $ \xs ->
let ys = sort (xs :: [Int])
in and (zipWith (<=) ys (drop 1 ys))
Golden Tests and Doctest
Golden tests, provided by libraries like tasty-golden, compare a function's output against a saved reference ('golden') file and flag any unexpected diff -- ideal for complex or generated output such as rendered HTML, pretty-printed ASTs, or serialized JSON that would be tedious to assert field-by-field. Doctest takes a different approach: it scans Haddock documentation comments for >>> example blocks and actually runs them as executable tests, so a usage example in your documentation can never silently drift out of sync with the real implementation.
Cricket analogy: A golden test comparing a function's output byte-for-byte against a saved reference file is like comparing today's scorecard format against last season's officially approved template -- any unexpected deviation, even a formatting change, gets flagged for review.
QuickCheck's default Arbitrary instance for lists or Int may skew toward small or 'nice' values and rarely generate edge cases like empty lists, negative numbers, or Unicode strings unless you write a custom Arbitrary instance with a tailored shrink -- always check the generated distribution with sample before trusting a green property test.
Structuring Test Suites with Tasty
Tasty is a unifying test framework that combines HUnit-style assertions, Hspec-style specs, QuickCheck properties, and golden tests into a single runnable tree, using defaultMain (testGroup "All Tests" [...]) as the entry point. This lets a project have one consistent test runner, one consistent CLI (with options like --pattern to filter which tests run and --quickcheck-tests to adjust how many random cases QuickCheck generates), and one consolidated pass/fail report instead of juggling several disconnected test binaries.
Cricket analogy: Tasty combining HUnit, QuickCheck, and golden tests into a single runnable testGroup tree is like an ICC tournament combining Test, ODI, and T20 results under one unified points system, letting selectors see a player's overall form across formats in one report.
In CI, run cabal test --test-show-details=streaming (or stack test) so test output streams live instead of being buffered until the whole suite finishes -- this makes it much faster to spot which specific test is hanging or failing in a long pipeline log.
- Hspec provides a BDD-style
describe/itsyntax on top of HUnit-style assertions likeshouldBefor exact-value unit tests. - QuickCheck tests properties (
prop_...) against hundreds of randomly generated inputs via theArbitrarytypeclass, and shrinks failures to a minimal counterexample. - Golden tests compare a function's output against a saved reference file, flagging any unexpected diff, and are ideal for complex or rendered output.
- Doctest runs example usages embedded directly in Haddock comments as executable tests, keeping documentation honest.
- Custom
Arbitraryinstances with tailored generators/shrinkers are often needed since default generators can under-sample edge cases. - Tasty unifies HUnit, Hspec, QuickCheck, and golden tests into one runnable
testGrouptree viadefaultMain. - Test-suites are declared in the .cabal file (
type: exitcode-stdio-1.0) and run withcabal testorstack test.
Practice what you learned
1. What does `property $ \xs -> reverseTwice xs == xs` do in an Hspec test using QuickCheck?
2. What does QuickCheck do when it finds an input that falsifies a property?
3. What is a golden test?
4. What does doctest do in a Haskell project?
5. Why might you write a custom `Arbitrary` instance instead of relying on the default one?
Was this page helpful?
You May Also Like
Cabal and Package Management
Learn how Cabal defines, builds, and resolves dependencies for Haskell projects, and how it works alongside Stack and Hackage.
Error Handling in Haskell
Learn how Haskell represents failure explicitly using Maybe, Either, and exceptions, and when to reach for each approach.
Lazy Evaluation in Haskell
How Haskell's non-strict evaluation model defers computation until results are needed, enabling infinite structures but requiring care around space leaks.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics