mockcat
Declarative mocking with a single arrow `~>`.
https://github.com/pujoheadsoft/mockcat#readme
| LTS Haskell 24.25: | 0.5.5.0 |
| Stackage Nightly 2025-12-26: | 1.0.0.0 |
| Latest on Hackage: | 1.0.0.0 |
mockcat-1.0.0.0@sha256:de09a9b2e391943faa66bb52260253c65bca18f1fc1bd7df9e116a338921bdcf,4764Module documentation for 1.0.0.0
Mockcat is a lightweight, declarative mocking library for Haskell.
By using the dedicated Mock Arrow (~>) operator, you can describe mock behavior with the same ease as defining standard functions.
-- Define
f <- mock $ "input" ~> "output"
-- Verify
f `shouldBeCalled` "input"
Concepts & Terminology
Mockcat adopts a design where âverification happens at runtime, but âconditions to be metâ can be declared at definition time.â
-
Stub: Exists solely to keep the test moving by returning values. It does not care âhow it was calledâ. The
stubfunction returns a completely pure function. -
Mock: In addition to stubbing, it records and verifies âwas it called as expected?â. The
mockfunction returns a value while recording calls. Verification can be done at the end of the test, or declared as âit must be called this wayâ at definition time.
Why Mockcat?
Thereâs no need to brace yourself when writing mocks in Haskell.
Mockcat is a mocking library that âallows you to declaratively describe function behavior and intent without depending on specific architectures.â
âI canât test unless I introduce Typeclasses (MTL).â âI have to define dedicated data types just for mocking.â (e.g., adding extra Typeclasses or Service Handle records just for testing)
You are freed from such constraints. You can mock existing functions as they are, and start writing tests even when the design isnât fully solidified.
Mockcat aims to let you write tests to explore design, rather than forcing you to fixate the design just for testing.
Before / After
See how simple writing tests in Haskell can be.
| Before: Handwritten⊠đ« | After: Mockcat đ±âš | |
|---|---|---|
| Definition (Stub)âI want to returnthis value for this argâ | f :: String -> IO Stringf arg = case arg of âaâ -> pure âbâ _ -> error âunexpectedâEven simple branching consumes many lines. | â Use stub if verification is unneeded (Pure)let f = stub $ âaâ ~> âbâBehaves as a completely pure function. |
| Verification (Verify)âI want to testif it was called correctlyâ | â Need to implement recording logicref <- newIORef []let f arg = do modifyIORef ref (arg:) âŠâ Verification logiccalls <- readIORef refcalls `shouldBe` [âaâ]â» This is just one example. Real-world setups often require even more boilerplate. | â Use mock if verification is needed (Recorded internally)f <- mock $ âaâ ~> âbââ Just state what you want to verifyf `shouldBeCalled` âaâRecording is automatic.Focus on the âWhyâ and âWhatâ, not the âHowâ. |
Key Features
- Haskell Native DSL: No need to memorize redundant data constructors or specialized notation. Write mocks naturally, just like function definitions (
arg ~> return). - Architecture Agnostic: Whether using MTL (Typeclasses), Service Handle (Records), or pure functionsâMockcat adapts to your design choice with minimal friction.
- Verify by âConditionâ, not just Value: Works even if arguments lack
Eqinstances. You can verify based on âwhat properties it should satisfyâ (Predicates) rather than just strict equality. - Helpful Error Messages: Shows âstructural diffsâ on failure, highlighting exactly what didnât match.
Expected arguments were not called. expected: [Record { name = "Alice", age = 20 }] but got: [Record { name = "Alice", age = 21 }] ^^ - Intent-Driven Types: Types exist not to restrict you, but to naturally guide you in expressing your testing intent.
Quick Start
Copy and paste the code below to experience Mockcat right now.
Installation
package.yaml:
dependencies:
- mockcat
Or .cabal:
build-depends:
mockcat
First Test (Main.hs / Spec.hs)
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE TypeApplications #-}
import Test.Hspec
import Test.MockCat
main :: IO ()
main = hspec spec
spec :: Spec
spec = do
it "Quick Start Demo" do
-- 1. Create a mock (Return 42 when receiving "Hello")
f <- mock $ "Hello" ~> (42 :: Int)
-- 2. Use it as a function
let result = f "Hello"
result `shouldBe` 42
-- 3. Verify it was called
f `shouldBeCalled` "Hello"
At a Glance: Matchers
| Matcher | Description | Example |
|---|---|---|
any |
Matches any value | f <- mock $ any ~> True |
expect |
Matches condition | f <- mock $ expect (> 5) "gt 5" ~> True |
"val" |
Matches value (Eq) | f <- mock $ "val" ~> True |
inOrder |
Order verification | f `shouldBeCalled` inOrderWith ["a", "b"] |
inPartial |
Partial order | f `shouldBeCalled` inPartialOrderWith ["a", "c"] |
User Guide
Mockcat supports two verification styles depending on your testing needs and preferences.
- Post-Verification Style (Spy):
Define mock behavior, run the code, and verify afterwards using
shouldBeCalled.
Ideal for exploratory testing or simple setups. (Used mainly in Sections 1 & 2 below) - Pre-Expectation Style (Declarative/Expectation):
Describe âhow it should be calledâ at the definition time.
Ideal for strict interaction testing. (Explained in Section 3)
1. Function Mocking (mock) - [Basic]
The most basic usage. Creates a function that returns values for specific arguments.
-- Function that returns True for "a" -> "b"
f <- mock $ "a" ~> "b" ~> True
Flexible Matching: You can specify conditions (predicates) instead of concrete values.
-- Arbitrary string (param any)
f <- mock $ any ~> True
-- Condition (expect)
f <- mock $ expect (> 5) "> 5" ~> True
2. Typeclass Mocking (makeMock)
Useful when you want to bring existing typeclasses directly into your tests. Generates mocks from existing typeclasses using Template Haskell.
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
class Monad m => FileSystem m where
readFile :: FilePath -> m String
writeFile :: FilePath -> String -> m ()
-- [Strict Mode] Default behavior. Consistent with 'mock'.
-- If the return type is `m a`, the stub definition must return a value of type `m a` (e.g., `pure @IO "value"`, `throwIO Error`).
-- Recommended when you prefer explicit descriptions faithful to Haskell's type system.
makeMock [t|FileSystem|]
-- [Auto-Lift Mode] Convenience-focused mode.
-- Automatically wraps pure values into the monad (m String).
makeAutoLiftMock [t|FileSystem|]
Use runMockT block in your tests.
spec :: Spec
spec = do
it "filesystem test" do
result <- runMockT do
-- [Strict Mode] (if using makeMock)
_readFile $ "config.txt" ~> pure @IO "debug=true"
_writeFile $ "log.txt" ~> "start" ~> pure @IO ()
-- [Auto-Lift Mode] (if using makeAutoLiftMock)
-- _readFile $ "config.txt" ~> "debug=true"
-- Run code under test (mock injected)
myProgram "config.txt"
result `shouldBe` ()
3. Declarative Verification (withMock / expects)
A style where you describe expectations at definition time. Verification runs automatically when exiting the scope. Useful when you want âDefinitionâ and âVerificationâ to be written close together.
withMock $ do
-- Write expectations (expects) at definition time
f <- mock (any ~> True)
`expects` do
called once `with` "arg"
-- Execution
f "arg"
[!NOTE] You can also use
expectsfor declarative verification insiderunMockTblocks. This provides a unified experience where âMock Creationâ and âExpectation Declarationâ complete within a single block.
4. Flexible Verification (Matchers)
Even if arguments donât have Eq instances, or you donât want to depend on specific values, you can verify based on intentââwhat condition should be metâ.
Mockcat provides matchers to verify properties of functions, not just value equality.
Allow Any Value (any)
-- Return True regardless of the argument
f <- mock $ any ~> True
-- Verify that it was called (arguments don't matter)
f `shouldBeCalled` any
Verify with Conditions (expect)
You can verify using âconditions (predicates)â instead of arbitrary values.
Powerfully useful for types without Eq (like functions) or when checking partial matches.
-- Return False only if the argument starts with "error"
f <- mock $ do
onCase $ expect (\s -> "error" `isPrefixOf` s) "start with error" ~> False
onCase $ any ~> True
5. Advanced Features - [Advanced]
mock vs stub vs mockM
In most cases, mock is all you need.
Consider other functions only when you need finer control.
| Function | Verification (shouldBeCalled) |
IO Dependency | Characteristics |
|---|---|---|---|
stub |
â | None | Pure Stub. No IO dependency. Sufficient if verification isnât needed. |
mock |
â | Yes (Hidden) | Mock. Behaves as a pure function, but internally manages call history via IO. |
mockM |
â | Yes (Explicit) | Monadic Mock. Used within MockT or IO, allowing explicit handling of side effects (e.g., logging). |
Partial Mocking: Mixing with Real Functions
Useful when you want to replace only some methods with mocks while using real implementations for others.
-- [Strict Mode]
makePartialMock [t|FileSystem|]
-- [Auto-Lift Mode]
-- Just like makeAutoLiftMock, there is an Auto-Lift version for Partial Mock.
makeAutoLiftPartialMock [t|FileSystem|]
instance FileSystem IO where ... -- Real instance is also required
test = runMockT do
_readFile $ "test" ~> pure @IO "content" -- Only mock readFile (Strict)
-- or
-- _readFile $ "test" ~> "content" -- (Auto-Lift)
program -- writeFile runs the real IO instance
Monadic Return (IO a)
Used when you want a function returning IO to have different side effects (results) for each call.
f <- mock $ do
onCase $ "get" ~> pure @IO 1 -- 1st call
onCase $ "get" ~> pure @IO 2 -- 2nd call
Named Mocks
You can attach labels to display function names in error messages.
f <- mock (label "myAPI") $ "arg" ~> True
Encyclopedia (Feature Reference)
â» Use this section as a dictionary when you get stuck.
Verification Matchers (shouldBeCalled)
| Matcher | Description | Example |
|---|---|---|
x (Value itself) |
Was called with that value | f `shouldBeCalled` (10 :: Int) |
times n |
Exact count | f `shouldBeCalled` (times 3 `with` "arg") |
once |
Exactly once | f `shouldBeCalled` (once `with` "arg") |
never |
Never called | f `shouldBeCalled` never |
atLeast n |
n or more times | f `shouldBeCalled` atLeast 2 |
atMost n |
n or fewer times | f `shouldBeCalled` atMost 5 |
anything |
Any argument (count ignored) | f `shouldBeCalled` anything |
inOrderWith [...] |
Strict order | f `shouldBeCalled` inOrderWith ["a", "b"] |
inPartialOrderWith [...] |
Partial order (skips allowed) | f `shouldBeCalled` inPartialOrderWith ["a", "c"] |
Parameter Matchers (Definition)
| Matcher | Description | Example |
|---|---|---|
any |
Any value | any ~> True |
expect pred label |
Condition | expect (>0) "positive" ~> True |
expect_ pred |
No label | expect_ (>0) ~> True |
Declarative Verification DSL (expects)
In expects blocks, you can describe expectations declaratively.
The syntax used in expects shares the same vocabulary as shouldBeCalled.
| Syntax | Description |
|---|---|
called |
Start expectation |
once |
Called exactly once |
times n |
Called n times |
never |
Never called |
with arg |
Expected argument |
with matcher |
Argument verification with matcher |
FAQ
Tips and Troubleshooting
Name collision with Prelude.any
The any parameter matcher from Test.MockCat may conflict with Prelude.any.
To resolve this, hide any from Prelude or use a qualified name.
import Prelude hiding (any)
-- or
import qualified Test.MockCat as MC
Ambiguous types with OverloadedStrings
If you have OverloadedStrings enabled, string literals may cause ambiguity errors.
Add explicit type annotations to resolve this.
mock $ ("value" :: String) ~> True
Tested Versions
mockcat is continuously tested in CI across these configurations:
| GHC | Cabal | OS |
|---|---|---|
| 9.2.8 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |
| 9.4.8 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |
| 9.6.3 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |
| 9.8.2 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |
| 9.10.1 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |
Happy Mocking! đ±
Changes
Changelog for mockcat
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to the Haskell Package Versioning Policy.
[1.0.0.0] - 2025-12-24
Changed
- DSL Reboot: Replaced
|>with~>as the primary parameter chain operator (representing the âmock arrowâ). - Terminology Shift: Standardized terminology to âcalledâ instead of âappliedâ throughout the library and error messages.
- Simplified creating/stubbing API:
f <- mock $ ...is now the canonical way. - Expanded structural diffing support for nested records and lists.
- Unified verification API: All verification is now handled via
shouldBeCalled. - Strict by Default:
makeMockandmakePartialMocknow default to strict return values (implicit monadic return is disabled).makeAutoLiftMockwas introduced for the previous behavior.
Added
- Deep Structural Diff: Enhanced error messages with precise caret pointers for complex nested data structures.
- STM-based concurrency for mock registration and call recording.
- Infinite arity support for mock/stub building.
Removed
- Backward compatibility with 0.x.x APIs (
stubFn,createMock,applied, etc.). makeMockWithOptions,makePartialMockWithOptions, andMockOptions(internalized to simplify API).
Migration Guide (0.x -> 1.0)
This release is a complete reboot. Previous code will break.
-
Operator Change: Replace
|>with~>.-- Old createStubFn $ "arg" |> "result" -- New stub $ "arg" ~> "result" -
Mock Creation: Use
mock/stubinstead ofcreateMock/createStubFn.-- Old f <- createMock $ "arg" |> "result" -- New f <- mock $ "arg" ~> "result" -
Verification: Use
shouldBeCalled(unified API).-- Old f `shouldApplyTo` "arg" -- New f `shouldBeCalled` "arg" -
Template Haskell Generics:
makeMockis now strict by default (requires explicitpurefor IO actions).- Use
makeAutoLiftMockfor old implicit behavior. - Or stick to
makeMockand addpureto your return values.
- Use
0.6.0.0
Changed
- Removed the upper limit on variable arguments when creating stub functions. Previously, there was a restriction on the maximum number of arguments, but this limitation has been removed, allowing stub functions to accept an arbitrary number of arguments.
0.5.5.0
Added
- Aliases
expectApplyTimesandexpectNever(preferred names) for pre-run expectation declarations.
Documentation
- README (EN/JA) now recommends
expectApplyTimes/expectNeverover legacyapplyTimesIs/neverApply. - Clarified that
expectApplyTimes nis the canonical form;expectNeveris sugar forexpectApplyTimes 0.
Notes
- Legacy names remain exported for backward compatibility (no deprecation pragma yet). They may receive a soft deprecation notice in a future minor release after community feedback.
0.5.4.0
Added
- Parallel execution support (verified counting under concurrency, stress tests).
- Verification helpers:
applyTimesIs,neverApply.
Changed
- Refactored
MockTfromStateTtoReaderT (TVar [Definition])architecture. - Simplified Template Haskell generated constraints.
Fixed
- Race causing lost/double count in concurrent stub applications (strict
modifyTVar').
Removed
unsafePerformIOin TH-generated code.
Internal
- Introduced
MonadMockDefsabstraction.
0.5.3.0
Added
MonadUnliftIOinstance forMockT(initial groundwork for later parallel support).