Search code examples
haskellhaskell-pipeshspec

Haskell Pipes and testing with HSpec


I have written a program for a project that uses Pipes, which I love! I'm struggling to unit test my code however.

I have a series of functions of type Pipe In Out IO () (for example) that I wish to test with HSpec. How can I go about this?

For example, suppose I have this domain:

data Person = Person String Int | Unknown deriving (Show, Eq)
data Classification = Friend | Foe | Undecided deriving Show

and this Pipe:

classify :: Pipe Person (Person, Classification) IO ()
classify = do
    p@(Person name _) <- await
    case name of 
      "Alex" -> yield (p, Friend)
      "Bob" -> yield (p, Foe)
      _ -> yield (p, Undecided)

I would like to write a spec:

main = hspec $ do
  describe "readFileP" $ 
    it "yields all the lines of a file"
      pendingWith "How can I test this Pipe? :("

Solution

  • The trick is to use toListM from Pipes ListT monad transformer.

    import Pipes
    import qualified Pipes.Prelude as P
    import Test.Hspec
    
    data Person = Person String Int | Unknown deriving (Show, Eq)
    data Classification = Friend | Foe | Undecided deriving (Show, Eq)
    
    classify :: Pipe Person (Person, Classification) IO ()
    classify = do
      p@(Person name _) <- await
      case name of 
        "Alex" -> yield (p, Friend)
        "Bob" -> yield (p, Foe)
        _ -> yield (p, Undecided)
    

    The test, using the ListT transformer to convert the pipe to a ListT and asserting using HSpec:

    main = hspec $ do
      describe "classify" $ do
        it "correctly finds friends" $ do
          [(p, cl)] <- P.toListM $ each [Person "Alex" 31] >-> classify
          p `shouldBe` (Person "Alex" 31)
          cl `shouldBe` Friend
    

    Note, you don't have to use each, this could be a simple producer with a call to yield.