Introduction

Haskell is great, but one of its biggest problems is poor documentation. Not just documentation about packages and their functions, but also guidelines on the best practices to follow in order to adhere as much as possible to what is considered idiomatic in the community.

I’ve never used Haskell for something serious before, so when I recently had the need to integrate unit tests into my project I felt a bit lost.

How do I test the functions that my module doesn’t export?

Your library or application is probably composed by different modules. Each module exports what is meant to be used by the other components, and possibly keeps some implementation detail private.

Of course you’d want to test the private functions too, but you can’t because you have no way at all to access them.

The solution is to split your module in two files: a public one and an internal one.

Take as an example an imaginary library with a SerialNumber module, which just checks if a given serial number is valid using a somewhat stupid set of criteria. To make SerialNumber fully testable we could split it in two files, like this:

Folder structure

src/Internal/SerialNumber.hs:

module Internal.SerialNumber where

import Data.Char

data SerialNumber = Valid String | Invalid String | Unknown String

isValid :: SerialNumber -> Bool
isValid (Valid _) = True
isValid (Invalid _) = False
isValid (Unknown s) = validCharacters s && correctSum s

-- Implementation helpers:

correctSum :: String -> Bool
correctSum s = 1024 == foldr ((+) . ord) 0 s

validCharacters :: String -> Bool
validCharacters s = all isAlphaNum s

src/SerialNumber.hs:

module SerialNumber
(
  SerialNumber(..)
, isValid
) where

import Internal.SerialNumber

Basically you want to move your whole code to the Internal module, which exports everything. This is the module that you’re going to import from the tests. Then, in the second file you just re-export the entities that you want to be visible to the other modules.

We could have chosen to place the implementation of the public functions into the second file, but this opens a door for circular imports and we don’t want that.

Should I expose the Internal modules?

If you add your internal modules to the exposed-modules list in your cabal file, your users will be able to use all your private entities if they want to. The nice fact is that the Internal identifier in the module name will warn them that they’re using an internal API, with all the risks that come with it.

As an alternative, you can completely hide the internal modules by not adding them to exposed-modules. But now, they’re hidden from the test project too! To work around this, you have to add the whole source directory (src) and the internal modules to the test-suite in your cabal file, like this:

test-suite mylib-test
  hs-source-dirs:      test
                     , src
  other-modules:       Internal.SerialNumber
  ...

This has the disadvantage of requiring two compilations of your library code: one for the library itself, and another one for the test.