hspec
hspec copied to clipboard
Documentation is unclear about how to use `before`-style combinators
The documentation (meaning Haddocks) doesn't really explain much here, and the examples given in the tutorial docs aren't thorough enough to properly understand these, in my view. After reading all these materials, I'm fairly comfortable writing incantations like:
describe "foo" $ do
it "should bar" (x `shouldBe` y)
But I am utterly unclear on how this needs to change if I incant as so:
before doSomething -- and then what?
The same applies to all before-style combinators - beforeAll, after, and so on. Would it be possible to have some clarification and examples?
@kozross
If I am not mistaken, from the type signatures, you should be able to use before as such:
before (doStuff) $ describe "foo" $ do
it "should bar" $
x `shouldBe` y
@Kleidukos Except that before :: IO a -> SpecWith a -> Spec. How do I get access to, and use for comparisons (for instance) the value of type a produced by the IO a argument? That's 100% the point of this combinator, and nowhere is it properly explained how to form the following describe ... it incantation.
Yes, I don't quite see how one is supposed to access the a.
@Kleidukos Therein lies my precise complaint.
I think I understand better after having read this test. Is it clearer, @kozross?
Not particularly - that example is tied up with a lot of unnecessary context, and self-constructing explanations from that is not exactly easy. The issue isn't my lack of understanding; I could remedy this by reading source code. The issue is that documentation (both Haddock and examples) basically do a terrible job of explaining the logic of these combinators.
Notably, this already arose. There's even an almost copy-pasteable solution by a key contributor.
@kozross if you want to understand how Hspec hooks work, here are a couple of things that might not be obvious at first:
-
Every spec item gets a subject, a value that it can operate on. By default, the subject is
():module RunSpec (spec) where import Test.Hspec spec :: Spec spec = do describe "reverse" $ do it "reverses a list" $ \ () {- <---- here, look, I'm not lying to you, the subject is () -} -> do reverse [1 :: Int, 2, 3] `shouldBe` [3, 2, 1] -
You can modify the subject with
beforeWith:f :: () -> IO Int f () = return 23 spec :: Spec spec = beforeWith f $ do describe "reverse" $ do it "reverses a list" $ \ n -> do reverse [n, 2, 3] `shouldBe` [3, 2, n] -
Every spec item operates on exactly one subject. You can use tuples if you want to operate on multiple values:
f :: () -> IO Int f () = return 23 g :: a -> IO (a, String) g a = return (a, "foo") spec :: Spec spec = beforeWith f $ beforeWith g $ do describe "reverse" $ do it "reverses a list" $ \ (n, "foo") -> do reverse [n, 2, 3] `shouldBe` [3, 2, n] -
aroundWithis a more general version ofbeforeWiththat allows require/release of resources (e.g. open/close a db connections). All other primitives that operate on the subject are specializations ofaroundWith. -
When the subject is
(), you don't have to mentioned it explicitly. This code is equivalent to the example from (1):spec :: Spec spec = do describe "reverse" $ do it "reverses a list" $ do reverse [1 :: Int, 2, 3] `shouldBe` [3, 2, 1](Look at the
Example Expectationinstance, if you want to understand how this works.)
If somebody wants to write a blog post about this, then please feel free to use these examples, no attribution required.
I spent a while trying to figure out how to compose multiple arounds before deciding to file a docs issue, where I foud this. :) It would be much appreciated if the haddocks came with some basic examples.
I've written a blog post on this, though folding it into the actual docs would be great too: https://www.parsonsmatt.org/2021/07/16/hspec_hooks.html
@parsonsmatt this is a great read!