Search code examples
haskellhspec

Hspec: discovery, custom main, and passing argument to spec


I am trying to use hspec-discover along with custom Main. Custom Main is a bracket that creates a file descriptor to be used by all Spec's.

This is my Spec.hs:

{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}

This is my Main.hs:

module Main (main) where

import Control.Exception
import System.Posix.IO
import System.Posix.Files
import Test.Hspec
import Spec (spec)

main :: IO ()
main = bracket
  (openFd verybigFile ReadWrite (Just 384) defaultFileFlags)
  (\fd -> closeFd fd >> removeLink verybigFile)
  (\fd -> hspec (spec fd))
    where
      verybigFile = "test/verybigFile"

For my spec in individual autodiscovered module to accept the file descriptor argument, I need to declare it as

spec :: Fd -> Spec

but hspec-discover demands that spec is declared as

spec :: Spec

otherwise autogenerated module does not compile:

test/Spec.hs:8:68:
    Couldn't match type `System.Posix.Types.Fd -> Spec'
                  with `hspec-core-2.1.7:Test.Hspec.Core.Spec.Monad.SpecM () ()'
    Expected type: hspec-core-2.1.7:Test.Hspec.Core.Spec.Monad.SpecWith
                     ()
      Actual type: System.Posix.Types.Fd -> Spec
    In the second argument of `describe', namely `SendfileSpec.spec'
    In the second argument of `postProcessSpec', namely
      `(describe "Sendfile" SendfileSpec.spec)'
    In the expression:
      postProcessSpec
        "test/SendfileSpec.hs" (describe "Sendfile" SendfileSpec.spec)

So, how to pass an argument to the spec without disturbing autodiscovery? My imagination drifts towards IORef's but the idea makes me shudder. What would be a Right Way to do it?


Solution

  • Sharing values across spec files is currently not support with hspec-discover. But you can still share values within the same spec file. The following works:

    FooSpec.hs:

    module FooSpec (spec) where
    
    import           Test.Hspec
    import           System.IO
    
    spec :: Spec
    spec = beforeAll (openFile "foo.txt" ReadMode) $ afterAll hClose $ do
      describe "hGetLine" $ do
        it "reads a line" $ \h -> do
          hGetLine h `shouldReturn` "foo"
    
        it "reads an other line" $ \h -> do
          hGetLine h `shouldReturn` "bar"
    

    Spec.hs:

    {-# OPTIONS_GHC -F -pgmF hspec-discover #-}
    

    But note that beforeAll is generally considered a code smell. It's good practice to use before instead if possible.