Test Spies in Haskell April 18th, 2025 | Sopot, Poland When testing a web application, you often want to make sure that a certain email would be sent — without actually sending it. How do you test that? Take something like a transactional email: a user signs up, resets their password, or completes a purchase — and your application needs to send a notification. You don’t care how the email gets sent — just that it was triggered correctly. The simplest implementation for the email-sending function might look like this. sendEmail :: Recipient -> IO () sendEmail recipient = -- some email sending code here… All this function needs to know is where to send the email. How it sends it isn’t important here — and it’s not what we’re testing. What we care about is how it’s called. This function might be called from a request handler, and it would be that handler function’s responsibility to decide how to call the email sending function. postThingR :: Handler () postThingR = do liftIO $ sendEmail "user@example.com" We can track how a function is called with a test spy. Spies are stubs that also record some information based on how they were called. You can emulate a test spy using the same stubbing method I wrote about earlier. Instead of calling sendEmail directly, you’ll retrieve it from somewhere that can be swapped out at runtime — like your application’s settings. postThingR :: Handler () postThingR = do sendEmail <- getsYesod appSendEmail liftIO $ sendEmail "user@example.com" The idea is to swap the implementation for one that records the arguments that the function was called with, and then check what was recorded in the test’s assertion phase. We can use mutable state — such as an IORef or TVar — to record how the function was called. Here, we’ll keep it simple with IORef. it "notifies the right person" $ do -- Arrange callsRef <- liftIO $ newIORef [] stub $ \app -> app { appSendEmail = \recipient -> liftIO $ modifyIORef' callsRef $ \cs -> recipient : cs } -- Act post T...
First seen: 2025-04-21 01:30
Last seen: 2025-04-21 01:30