Search code examples
haskellhunithspec

Haskell - assert a function was called


Is it possible to verify that a function was called in Haskell HSpec?

Assuming I had two functions foo and bar that transform my data.

foo :: Stuff -> Stuff
bar :: Stuff -> Stuff

And I have a function that applies either foo or bar on Stuff depending on whether it received 'f' or 'b' as its second argument and returns the result of the applied function.

apply :: Stuff -> Char -> Stuff

And In my tests, I have tested each of the functions foo and bar comprehensively that i would not want to test there effect with in apply.

Is it possible for me to verify that a function foo or bar was called? depending on what argument is passed to apply?


Solution

  • "I'm thinking more TDD, like in an OOP language. Is such a thing possible in Haskell?"

    A better question is "is such a thing necessary in Haskell?" ;-)

    [I realise that is not the question you actually asked. Feel free to ignore this answer.]

    In an OO language, we build objects that talk to other objects to get their job done. To test such an object, we build a bunch of fake objects, hook the real object up to the fake ones, run the method(s) we want to test, and assert that it calls faked methods with the expected inputs, etc.

    In Haskell, we write functions. The only thing a pure function does is take some input, and produce some output. So the way to test that is to just run the thing, feeding it known inputs and checking that it returns known outputs. What other functions it calls in the process of doing that doesn't matter; all we care about is whether the answer is right.

    In particular, the reason we don't usually do this in OOP is that calling some arbitrary method might cause "real work" to happen — reading or writing disk files, opening network connections, talking to databases and other servers, etc. If you're just testing one part of your code, you don't want the test to depend on whether some database is running on a real network server somewhere; you just want to test one little part of your code.

    With Haskell, we separate anything that can affect the Real World from stuff that just does data transformations. Testing stuff that just transforms data in memory is delightfully trivial! (Testing the parts of your code that do interact with the Real World is still hard, in general. But hopefully those parts are very small now.)

    The Haskell test style of choice seems to be property-based testing. For example, if you've got a function to solve an equation, you write a QuickCheck property that randomly generates 100 equations, and for each one, it checks whether the number returned actually solves the original equation or not. It's a tiny handful of code that automatically tests just about everything you'd ever want to know! (But not quite: You need to make sure that the "randomly" chosen equations actually test all the code paths you care about.)