Unit testing Haskel code with HUnit

As with most languages nowadays, we want to have some unit testing libraries/frameworks. Obviously Haskell is no different and we have tools such as HUnit.

Let’s first create a simple little bit of code to test, here’s my Calculator.hs file (abridged just to show the code I intend to write tests for)

module Modules.Calculator where

factorial :: (Integral a) => a -> a  
factorial 0 = 1  
factorial n = n * factorial (n - 1) 

We’ll need to have installed the HUnit package, if you haven’t already then run

cabal install --lib QuickCheck HUnit

We’ll store our tests in a folder named test off of our root folder (the name of the folder can be altered if you prefer).

Here’s our test code (my file is named CalculatorTests.hs)

module Main (main) where

import Test.HUnit
import System.Exit

import Modules.Calculator as Calc

testZeroCase = TestCase(assertEqual "Factorial 1" (1) (Calc.factorial 1))
testNonZeroCase = TestCase(assertEqual "Factorial 10" (2) (Calc.factorial 10))

main :: IO ()
main = do
    counts <- runTestTT ( test [
        testZeroCase,
        testNonZeroCase
        ])
    if (errors counts + failures counts == 0)
        then exitSuccess
        else exitFailure

In the above code we import our library code and have two tests, the first tests our factorial code with a 0 value, the second tests a non-zero. In this instance I’ve purposefully written a breaking test.

Before we run these and/or fix the tests let’s see what we’re doing with the test code. We create a function for the TestCase and within the TestCase we have our assertion along with a message prefix (the string), next we have the expected value and finally the actual value or in this case the function call which returns the actual value.

In main we basically create the test runner and supply an array of the test functions to be run, finally we exit the runner with success or failure.

Now before we can run our tests we need to declare a Test-Suite within the .cabal file. We add the following to the file

Test-Suite test-Calculator
  type:                exitcode-stdio-1.0
  hs-source-dirs:      test
                       .
  default-language:    Haskell2010
  main-is:             CalculatorTests.hs
  other-modules:       Modules.Calculator
  build-depends:       base >=4.14 && <4.15, HUnit

This tells cabal the folder to look for the tests (test in this case) along with . for the current folder (otherwise our tests won’t find out Calculator module). We then state what the main application for the cabal to run for the tests, include other modules etc.

Now run

cabal test

If all goes well you’ll get something like the following

Running 1 test suites...
Test suite test-Calculator: RUNNING...
### Failure in: 1
test\CalculatorTests.hs:9
Factorial 10
expected: 2
 but got: 3628800
Cases: 2  Tried: 2  Errors: 0  Failures: 1
Test suite test-Calculator: FAIL
Test suite logged to:
D:\Development\somefolder\test\HaskellBasics-0.1.0.0-test-HaskellBasics.log
0 of 1 test suites (0 of 1 test cases) passed.
cabal.exe: Tests failed for test:test-Calculator from
Calculator-0.1.0.0.

Obviously the test with the prefix message “Factorial 10” failed with the expected value 2 but the actual value 3628800, so we can fix that test case so it looks like this

testNonZeroCase = TestCase(assertEqual "Factorial 10" (3628800) (Calc.factorial 10))

and now all our tests will pass.

I’ll leave it to the reader to look into the other assertions etc. For example, checkout Test.HUnit.Base

Other References

HUnit: A unit testing framework for Haskell