Reputation: 8303
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? :("
Upvotes: 2
Views: 348
Reputation: 8303
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
.
Upvotes: 2
Reputation: 27771
You could use the functions of the temporary
package to create temporary files with the expected data, and then test that the data is read correctly by the pipe.
Incidentally, your Pipe
is using readFile
that performs lazy I/O. Lazy I/O and streaming libraries like pipes don't mix very well, in fact the latter exist mainly as an alternative to the former!
Perhaps you should instead use functions which perform strict I/O, like openFile
and getLine
.
One annoyance with strict I/O is that it forces you to consider resource allocation more carefully. How to ensure that each file handle is closed at the end, or in case of error? One possible way to achieve this is to work in the ResourceT IO
monad, instead of directly in IO
.
Upvotes: 2