@hackage hspec-discover-discover1.0.0.0

hspec-discover-discover

A GHC preprocessor for hspec that discovers test modules in immediate subdirectories and co-located *Spec.hs files.

Unlike hspec-discover, which recursively finds all *Spec.hs files, hspec-discover-discover looks for:

  • Spec.hs files in immediate subdirectories (e.g. test/Foo/Spec.hs)
  • *Spec.hs files in the same directory as the entry point (e.g. test/FooSpec.hs)

This gives you explicit control over test organization — each subdirectory or spec file is a top-level test group.

Usage

In your test/Spec.hs:

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

Then organize your tests as subdirectories containing Spec.hs, or as *Spec.hs files alongside the entry point:

test/
  Spec.hs                -- preprocessor entry point (the line above)
  FooSpec.hs             -- module FooSpec, exports spec :: Spec
  ParseArgs/
    Spec.hs              -- module ParseArgs.Spec, exports spec :: Spec
  Discover/
    Spec.hs              -- module Discover.Spec, exports spec :: Spec
  Generate/
    Spec.hs              -- module Generate.Spec, exports spec :: Spec

Each module should export a spec :: Spec:

module ParseArgs.Spec (spec) where

import Test.Hspec

spec :: Spec
spec = do
    it "does something" $ do
        True `shouldBe` True

This file can itself be generated from hspec-discover, though you will need this patch for it to generate modules properly in subdirectories.

Generated output

For the directory structure above, hspec-discover-discover generates:

{-# LINE 1 "test/Spec.hs" #-}
module Main (main) where

import Test.Hspec
import qualified Discover.Spec
import qualified Generate.Spec
import qualified ParseArgs.Spec
import qualified FooSpec

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  describe "Discover" Discover.Spec.spec
  describe "Generate" Generate.Spec.spec
  describe "ParseArgs" ParseArgs.Spec.spec
  describe "Foo" FooSpec.spec

Options

Pass options via -optF in your GHC options:

  • --module-name=NAME — Set the generated module name (default: Main). When the module name is not Main, the main function is omitted and only spec is exported.
  • --subdir-file=FILENAME — Set the filename to look for in subdirectories (default: Spec.hs). This is useful when other tooling globs on *Spec.hs and interferes with the subdirectory entry points. Only affects subdirectory lookup — co-located *Spec.hs discovery is unchanged.

For example, to look for SubTest.hs instead of Spec.hs in subdirectories:

{-# OPTIONS_GHC -F -pgmF hspec-discover-discover -optF --subdir-file=SubTest.hs #-}

This would discover test/Foo/SubTest.hs and generate import qualified Foo.SubTest.

Comparison with hspec-discover

Feature hspec-discover hspec-discover-discover
Discovery Recursive *Spec.hs Immediate subdirs with configurable file (default Spec.hs) + co-located *Spec.hs
Naming Any file ending in Spec.hs Configurable file in subdirs, or *Spec.hs in same directory
Grouping Flat list of specs One describe per subdirectory or local spec

Truly, these tools are meant to be used in conjunction with each other. hspec-discover-discover is primarily useful when hspec-discover's single generated Spec.hs module becomes too large to compile quickly. This tool was developed when I noticed that our Spec.hs module was taking 2:36 to compile. Splitting things up into test/Spec.hs with this tool (2s compile time) and a myriad of test/*/TestGroup.hs files (can compile in parallel much earlier in the build graph) dropped our overall CI build time from 8:00 to 6:30.

License

BSD-3-Clause