@@ -8,7 +8,7 @@ import Effect (Effect)
8
8
import Effect.Aff (Aff , launchAff_ )
9
9
import Effect.Class (liftEffect )
10
10
import Node.Process (exit' )
11
- import Test.Spec (Spec )
11
+ import Test.Spec (SpecT , Spec )
12
12
import Test.Spec.Result (Result )
13
13
import Test.Spec.Runner (Reporter )
14
14
import Test.Spec.Runner as Spec
@@ -19,6 +19,9 @@ import Test.Spec.Tree (Tree)
19
19
20
20
-- | Runs the given spec, using configuration derived from CLI options (if any),
21
21
-- | and exits the process with an exit indicating success or failure.
22
+ -- |
23
+ -- | For more control over the configuration or test tree generating monad, use
24
+ -- | `runSpecAndExitProcess'`.
22
25
runSpecAndExitProcess :: Array Reporter -> Spec Unit -> Effect Unit
23
26
runSpecAndExitProcess =
24
27
runSpecAndExitProcess' { defaultConfig: Cfg .defaultConfig, parseCLIOptions: true }
@@ -29,12 +32,26 @@ runSpecAndExitProcess =
29
32
-- | The `parseCLIOptions` parameter determines whether the `defaultConfig`
30
33
-- | should be used as is or CLI options (if any provided) should be applied on
31
34
-- | top of it.
32
- runSpecAndExitProcess' :: ∀ c .
33
- { defaultConfig :: Cfg.TestRunConfig' c
34
- , parseCLIOptions :: Boolean
35
- }
35
+ -- |
36
+ -- | Note that, because this function works for any test tree generator monad
37
+ -- | `m`, you will need to specify it somehow. You can either give the spec
38
+ -- | parameter an explicit type:
39
+ -- |
40
+ -- | spec :: SpecT Aff Unit Aff Unit
41
+ -- | spec = do
42
+ -- | ...
43
+ -- |
44
+ -- | Or specify the monad via visible type application:
45
+ -- |
46
+ -- | runSpecAndExitProcess' @Aff ...
47
+ -- |
48
+ runSpecAndExitProcess' :: ∀ @m c .
49
+ TestTreeGenerator m
50
+ => { defaultConfig :: Cfg.TestRunConfig' c
51
+ , parseCLIOptions :: Boolean
52
+ }
36
53
-> Array Reporter
37
- -> Spec Unit
54
+ -> SpecT Aff Unit m Unit
38
55
-> Effect Unit
39
56
runSpecAndExitProcess' args reporters spec = launchAff_ do
40
57
config <-
@@ -45,9 +62,61 @@ runSpecAndExitProcess' args reporters spec = launchAff_ do
45
62
res <- runSpecAndGetResults config reporters spec
46
63
liftEffect $ exit' $ if successful res then 0 else 1
47
64
48
- runSpecAndGetResults :: ∀ c . Cfg.TestRunConfig' c -> Array Reporter -> Spec Unit -> Aff (Array (Tree String Void Result ))
65
+ -- | The core logic of a persistent test run:
66
+ -- |
67
+ -- | * Runs the spec tree generation in the given monad `m` (which is usually
68
+ -- | just `Identity`, but can be different in most complex scenarios)
69
+ -- | * Persists results to disk.
70
+ -- | * Returns the tree of results.
71
+ -- |
72
+ runSpecAndGetResults :: ∀ c m
73
+ . TestTreeGenerator m
74
+ => Cfg.TestRunConfig' c
75
+ -> Array Reporter
76
+ -> SpecT Aff Unit m Unit
77
+ -> Aff (Array (Tree String Void Result ))
49
78
runSpecAndGetResults config reporters spec = do
50
79
specCfg <- Cfg .toSpecConfig config <#> _ { exit = false }
51
- results <- un Identity $ Spec .evalSpecT specCfg reporters spec
80
+ results <- generateTestTree $ Spec .evalSpecT specCfg reporters spec
52
81
Persist .persistResults results
53
82
pure results
83
+
84
+ -- | A monad in which test tree generation happens. This is different from the
85
+ -- | monad in which the tests themselves run.
86
+ -- |
87
+ -- | In most cases the test tree would be generated in `Identity`, making for
88
+ -- | deterministic, pure test trees:
89
+ -- |
90
+ -- | spec :: SpecT Aff Unit Identity Unit
91
+ -- | spec = do
92
+ -- | it "is a pure test" do
93
+ -- | (2 + 2) `shouldEqual` 4
94
+ -- |
95
+ -- | But in more complicated scenarios, you might want to generate test trees in
96
+ -- | a more powerful monad. For example, the following test tree is generated in
97
+ -- | the `Effect` monad, utilizing the effectful function `randomInt` to
98
+ -- | determine the number of tests to generate:
99
+ -- |
100
+ -- | spec :: SpecT Aff Unit Effect Unit
101
+ -- | spec = do
102
+ -- | numTests <- randomInt 1 10
103
+ -- | for_ numTests \i -> do
104
+ -- | it ("is test number " <> show i) do
105
+ -- | (i + i - i) `shouldEqual` i
106
+ -- |
107
+ -- | This class assumes that the monad can be evaluated without any additional
108
+ -- | parameters. This allows for most normal use cases with ergonomic API. For
109
+ -- | more complicated cases, where the generator monad requires something extra
110
+ -- | (such as `StateT` or `ReaderT`), you can always use the `mapSpecTree`
111
+ -- | function to transform the generated test tree before running it.
112
+ class Monad m <= TestTreeGenerator m where
113
+ -- | Evaluates the test tree generator monad, returning the generated test
114
+ -- | tree. See comments on the `TestTreeGenerator` class for more information.
115
+ generateTestTree :: ∀ a . m (Aff a ) -> Aff a
116
+
117
+ instance TestTreeGenerator Identity where
118
+ generateTestTree = un Identity
119
+ instance TestTreeGenerator Aff where
120
+ generateTestTree = join
121
+ instance TestTreeGenerator Effect where
122
+ generateTestTree = liftEffect >>> join
0 commit comments