Skip to content

Haskell Testing Guide — HUnit, QuickCheck, and Test-Driven Development

DodaTech Updated 2026-06-28 2 min read

In this tutorial, you will learn about Haskell Testing Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Haskell testing uses HUnit for traditional unit assertions, QuickCheck for property-based random testing with automatic shrinking, and tasty/HSpec for test suite organization -- all runnable with cabal test.

HUnit

import Test.HUnit

-- Test cases
testReverse :: Test
testReverse = TestCase $ do
    assertEqual "reverse [1,2,3]" [3,2,1] (reverse [1,2,3])
    assertEqual "reverse []" ([] :: [Int]) (reverse [])
    assertBool "reverse preserves length" $
        length (reverse [1..10]) == 10

-- Group tests
tests = TestList [
    TestLabel "reverse" testReverse,
    TestLabel "other"  testOther
    ]

-- Run
main :: IO ()
main = runTestTT tests >> return ()

QuickCheck

import Test.QuickCheck

-- Property: reverse twice is identity
prop_reverseReverse :: [Int] -> Bool
prop_reverseReverse xs = reverse (reverse xs) == xs

-- Property: length after reverse
prop_reverseLength :: [Int] -> Bool
prop_reverseLength xs = length (reverse xs) == length xs

-- Run tests
main = do
    quickCheck prop_reverseReverse
    quickCheck prop_reverseLength

Custom Generators

import Test.QuickCheck

-- Custom data generator
data Color = Red | Green | Blue deriving (Show, Enum)

instance Arbitrary Color where
    arbitrary = elements [Red, Green, Blue]
    shrink Red   = []
    shrink Green = [Red]
    shrink Blue  = [Green, Red]

-- Conditional property
prop_someColors :: Color -> Bool
prop_someColors c = case c of
    Red   -> True
    Green -> True
    Blue  -> True

Tasty Test Framework

import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck

main = defaultMain $ testGroup "All Tests" [
    testGroup "Unit" [
        testCase "Reverse works" $
            reverse [1,2,3] @?= [3,2,1]
    ],
    testGroup "Properties" [
        testProperty "reverse reverse" prop_reverseReverse
    ]
    ]

Common Mistakes

1. Not testing edge cases

Empty lists, negative numbers, large inputs. QuickCheck helps but doesn't guarantee coverage.

2. Writing non-terminating properties

prop_sorted xs = sort xs == sort xs is always true. Test actual properties, not tautologies.

3. Not shrinking

QuickCheck shrinks failing cases to minimal examples. Ensure your types have shrink instances for useful error messages.

Practice Questions

1. What is the difference between HUnit and QuickCheck? HUnit tests specific cases. QuickCheck generates random inputs and tests properties that should hold for all.

2. How do you run tests with cabal? cabal test runs the test suite defined in the .cabal file. cabal test --show-details=direct for verbose output.

3. What does quickCheck return? It prints results to stdout. Use quickCheckResult to get a programmatic Result value.

FAQ

{{< faq question="What is property-based testing?" >}} Instead of specific input-output pairs, define a property that should hold for all inputs and let the framework generate random test cases. {{< /faq >}}

{{< faq question="Can I use QuickCheck with IO?" >}} Yes. Use quickCheck prop for pure properties. For IO properties, use Test.QuickCheck.Monadic with monadicIO. {{< /faq >}}

{{< faq question="What is coverage in QuickCheck?" >}} QuickCheck tracks how many cases pass each condition. Use cover, label, and collect to measure coverage. {{< /faq >}}

What's Next

Now learn about parser combinators.

Topic Description Link
Parsing Parser combinators {{< ref "20-parsing" >}}
Concurrency Concurrent Haskell {{< ref "21-concurrency" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro