hspec icon indicating copy to clipboard operation
hspec copied to clipboard

Documentation is unclear about how to use `before`-style combinators

Open kozross opened this issue 5 years ago • 11 comments

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 avatar Sep 21 '20 19:09 kozross

@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 avatar Sep 21 '20 21:09 Kleidukos

@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.

kozross avatar Sep 21 '20 21:09 kozross

Yes, I don't quite see how one is supposed to access the a.

Kleidukos avatar Sep 21 '20 21:09 Kleidukos

@Kleidukos Therein lies my precise complaint.

kozross avatar Sep 21 '20 21:09 kozross

I think I understand better after having read this test. Is it clearer, @kozross?

Kleidukos avatar Sep 21 '20 21:09 Kleidukos

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.

kozross avatar Sep 21 '20 21:09 kozross

Notably, this already arose. There's even an almost copy-pasteable solution by a key contributor.

kozross avatar Sep 21 '20 21:09 kozross

@kozross if you want to understand how Hspec hooks work, here are a couple of things that might not be obvious at first:

  1. 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]
    
  2. 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]
    
  3. 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]
    
  4. aroundWith is a more general version of beforeWith that allows require/release of resources (e.g. open/close a db connections). All other primitives that operate on the subject are specializations of aroundWith.

  5. 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 Expectation instance, 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.

sol avatar Sep 25 '20 14:09 sol

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.

robx avatar Nov 19 '21 15:11 robx

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 avatar Mar 30 '22 13:03 parsonsmatt

@parsonsmatt this is a great read!

sol avatar Mar 30 '22 20:03 sol