packages feed

mockcat 1.3.3.0 → 1.4.0.0

raw patch · 34 files changed

+835/−707 lines, 34 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Test.MockCat.AssociationList: (!?) :: Eq k => AssociationList k a -> k -> Maybe a
- Test.MockCat.AssociationList: empty :: AssociationList k a
- Test.MockCat.AssociationList: insert :: Eq k => k -> a -> AssociationList k a -> AssociationList k a
- Test.MockCat.AssociationList: lookup :: Eq k => k -> AssociationList k a -> Maybe a
- Test.MockCat.AssociationList: member :: Eq k => k -> AssociationList k a -> Bool
- Test.MockCat.AssociationList: type AssociationList k a = [(k, a)]
- Test.MockCat.AssociationList: update :: Eq k => (a -> a) -> k -> AssociationList k a -> AssociationList k a
- Test.MockCat.Internal.Builder: incrementCount :: Eq k => k -> InvocationCounts k -> InvocationCounts k
- Test.MockCat.Internal.Builder: instance Test.MockCat.Internal.Builder.MockBuilder (Test.MockCat.Param.Param r) r ()
- Test.MockCat.Internal.MockRegistry: markUnitUsed :: UnitMeta -> IO ()
- Test.MockCat.Internal.Registry.Core: clearOverlay :: IO ()
- Test.MockCat.Internal.Registry.Core: createOverlay :: IO Overlay
- Test.MockCat.Internal.Registry.Core: installOverlay :: Overlay -> IO ()
- Test.MockCat.Internal.Registry.Core: markUnitUsed :: UnitMeta -> IO ()
+ Test.MockCat.Internal.Types: type AssociationList k a = [(k, a)]
- Test.MockCat.Internal.Builder: incrementInvocationCount :: Eq params => TVar (InvocationRecord params) -> params -> IO ()
+ Test.MockCat.Internal.Builder: incrementInvocationCount :: EqParams params => TVar (InvocationRecord params) -> params -> IO ()
- Test.MockCat.Internal.Builder: readInvocationCount :: Eq params => TVar (InvocationRecord params) -> params -> IO Int
+ Test.MockCat.Internal.Builder: readInvocationCount :: EqParams params => TVar (InvocationRecord params) -> params -> IO Int

Files

CHANGELOG.md view
@@ -6,6 +6,19 @@ and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [1.4.0.0] - 2026-01-10+### Changed+- **Breaking Change**: Removed the verify fallback mechanism that searched thread history when `StableName` lookup failed.+    - **Impact**: Strict verification is now enforced. Tests using `shouldBeCalled` or related matchers will now deterministically fail if the mock function's identity cannot be strictly resolved (e.g., in some HPC/Coverage environments without proper handling), rather than attempting to guess the intent.+    - **Motivation**: To ensure absolute confidence in test results and support reliable HPC coverage analysis.++### Improved+- **HPC Code Coverage Support**: The library and test suite are now fully compatible with `stack test --coverage`.+    - Verification logic has been hardened to handle HPC instrumentation.+    - The test suite now automatically detects HPC mode and skips only those tests that strictly depend on `StableName` identity (which is inherently unstable under HPC), ensuring the rest of the suite runs safely.+- **Test Suite Modernization**: Refactored Property-based tests and `WithMockIO` specs to use the `expects` pattern (definition-time expectation). This makes them robust enough to strictly verify behavior even in HPC environments.+- **Granularity**: Improved module organization in `Spec.hs` for better maintainability.+ ## [1.3.3.0] - 2026-01-04 ### Added - **Type Families Support**: `deriveMockInstances` now supports type classes containing Associated Type Families.
README-ja.md view
@@ -14,14 +14,18 @@ </div>  **Mockcat** は、Haskell のための直感的で宣言的なモックライブラリです。-専用の演算子 **Mock Arrow (`~>`)** を使うことで、関数定義と同じような感覚で、引数と振る舞いをモックとして記述できます。+2つの検証スタイルをサポートしています。 +*   **事前宣言による検証 (`expects`)**: 【推奨】 モック定義時に期待される振る舞いを宣言します。+*   **事後検証 (`shouldBeCalled`)**: テスト実行後に呼び出しを検証します。+ ```haskell--- 定義 (Define)+-- 定義と同時に検証内容を宣言 ("input" で1回呼ばれることを期待) f <- mock ("input" ~> "output")+  `expects` called once --- 検証 (Verify)-f `shouldBeCalled` "input"+-- 実行+f "input" ```  ---@@ -61,7 +65,7 @@ | | **Before: 手書き...** 😫 | **After: Mockcat** 🐱✨ | | :--- | :--- | :--- | | **定義 (Stub)**<br />「この引数には<br />この値を返したい」 | <pre>f :: String -> IO String<br />f arg = case arg of<br />  "a" -> pure "b"<br />  _   -> error "unexpected"</pre><br />_単純な分岐を書くだけでも行数を消費します。_ | <pre>-- 検証不要なら stub (純粋)<br />let f = stub ("a" ~> "b")</pre><br />_完全な純粋関数として振る舞います。_ |-| **検証 (Verify)**<br />「正しく呼ばれたか<br />テストしたい」 | <pre>-- 記録の仕組みから作る必要がある<br />ref <- newIORef []<br />let f arg = do<br />      modifyIORef ref (arg:)<br />      ...<br /><br />-- 検証ロジック<br />calls <- readIORef ref<br />calls `shouldBe` ["a"]</pre><br />_※ これはよくある一例です。実際にはさらに補助コードが増えがちです。_ | <pre>-- 検証したいなら mock (内部で記録)<br />f <- mock ("a" ~> "b")<br /><br />-- 検証したい内容を書くだけ<br />f `shouldBeCalled` "a"</pre><br />_記録は自動。<br />「何を検証するか」という本質に集中できます。_ |+| **検証 (Verify)**<br />「正しく呼ばれたか<br />テストしたい」 | <pre>-- 記録の仕組みから作る必要がある<br />ref <- newIORef []<br />let f arg = do<br />      modifyIORef ref (arg:)<br />      ...<br /><br />-- 検証ロジック<br />calls <- readIORef ref<br />calls `shouldBe` ["a"]</pre><br />_※ これはよくある一例です。実際にはさらに補助コードが増えがちです。_ | <pre>withMock $ do<br />  -- 定義と同時に期待値を宣言<br />  f <- mock ("a" ~> "b")<br />    &#96;expects&#96; called once<br /><br />  -- 実行するだけ (自動検証)</pre><br />_記録は自動。<br />「何を検証するか」という本質に集中できます。_ |  ### 主な特徴 @@ -104,73 +108,92 @@     mockcat ``` -### 最初のテスト (`Main.hs` / `Spec.hs`)-+### 最初のテスト ```haskell-{-# 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. モックを作成 ("Hello" を受け取ったら 42 を返す)-    f <- mock ("Hello" ~> (42 :: Int))+  it "Quick Start Demo" $ do+    withMockIO $ do+      -- 1. モックを作成 ("Hello" を受け取ったら 42 を返す)+      --    同時に「1回呼ばれるはずだ」と期待を宣言+      f <- mock ("Hello" ~> (42 :: Int))+        `expects` called once -    -- 2. 関数として使う-    let result = f "Hello"-    result `shouldBe` 42+      -- 2. f "Hello" を呼び出し、42 を得る+      f "Hello" `shouldBe` 42 -    -- 3. 呼び出されたことを検証-    f `shouldBeCalled` "Hello"+      -- 3. スコープを抜ける際、宣言した期待に対する検証が自動で行われる ``` ---- -### At a Glance: Matchers-| Matcher | Description | Example |-| :--- | :--- | :--- |-| **`any`** | どんな値でも許可 | `f <- mock (any ~> True)` |-| **`when`** | 条件(述語)で検証 | `f <- mock (when (> 5) "gt 5" ~> True)` |-| **`"val"`** | 値の一致 (Eq) | `f <- mock ("val" ~> True)` |-| **`inOrder`** | 順序検証 | ``f `shouldBeCalled` inOrderWith ["a", "b"]`` |-| **`inPartial`**| 部分順序 | ``f `shouldBeCalled` inPartialOrderWith ["a", "c"]`` | ----- ## 使い方ガイド (User Guide) -Mockcat は、テストの目的や好みに応じて 2 つの検証スタイルをサポートしています。--1.  **事後検証スタイル (Spy)**:-    とりあえずモックで振る舞いを定義して実行し、後から `shouldBeCalled` で検証するスタイル。探索的なテストや、セットアップを簡単に済ませたい場合に適しています。(以下のセクション 1, 2 で主に使用)-2.  **事前期待スタイル (Declarative/Expectation)**:-    定義と同時に「こう呼ばれるべき」という期待を記述するスタイル。厳密なインタラクションのテストに適しています。(以下のセクション 3 で解説)+Mockcat は、テストの目的や環境に応じて 2 つの検証スタイルをサポートしています。 -### 1. 関数のモック (`mock`) - [基本]+### 1. 宣言的な検証 (`withMock` (`withMockIO`) / `expects`) - [推奨] -最も基本的な使い方です。特定の引数に対して値を返す関数を作ります。+定義と同時に期待値を記述するスタイルです。スコープを抜ける時に自動的に検証が走ります。+「定義」と「検証」を近くに書きたい場合に便利です。  ```haskell--- "a" -> "b" -> True を返す関数-f <- mock ("a" ~> "b" ~> True)+import Test.Hspec+import Test.MockCat+import Control.Monad.IO.Class (MonadIO(liftIO))++spec :: Spec+spec = do+  it "User Guide (withMock)" $ do+    withMock $ do+      -- "Hello" に対して True を返すモックを定義+      f <- mock ("Hello" ~> True)+        `expects` called once++      -- 実行+      let result = f "Hello"++      liftIO $ result `shouldBe` True ``` -**柔軟なマッチング**:-具体的な値だけでなく、条件(述語)を指定することもできます。+#### `withMockIO`: IO テストの簡略化+`withMockIO` は `withMock` を IO に特化させたバージョンです。`liftIO` を使わずにモックコンテキスト内で直接 IO アクションを実行できます。  ```haskell--- 任意の文字列 (param any)-f <- mock (any ~> True)+import Test.Hspec+import Test.MockCat --- 条件式 (when)-f <- mock (when (> 5) "> 5" ~> True)+spec :: Spec+spec = do+  it "User Guide (withMockIO)" $ do+    withMockIO $ do+      f <- mock ("Hello" ~> True)+        `expects` called once++      let result = f "Hello"++      result `shouldBe` True ``` +> [!IMPORTANT]+> `expects`(宣言的検証)を使用する場合、モック定義部分は必ず **括弧 `(...)`** で囲んでください。+> 以前のバージョンで使用できた `$` 演算子 (`mock $ ... expected ...`) は、優先順位の関係でコンパイルエラーになります。+>+> ❌ `mock $ any ~> True expects ...`+> ✅ `mock (any ~> True) expects ...`++> [!NOTE]+> `runMockT` ブロックの中でも、同様に `expects` を使った宣言的検証が可能です。+> 生成された型クラスのモック関数(`_xxx`)に対してもそのまま使用できます。+>+> ```haskell+> runMockT do+>   _readFile "config.txt" ~> pure "value"+>     `expects` called once+> ```+ ### 2. 型クラスのモック (`makeMock`)  既存の型クラスをそのままテストに持ち込みたい場合に使います。Template Haskell を使って、既存の型クラスからモックを自動生成します。@@ -221,48 +244,54 @@     result `shouldBe` () ``` -### 3. 宣言的な検証 (`withMock` / `expects`)+### 3. 関数のモックと事後検証 (`mock` / `shouldBeCalled`) -定義と同時に期待値を記述するスタイルです。スコープを抜ける時に自動的に検証が走ります。-「定義」と「検証」を近くに書きたい場合に便利です。+`withMock` (`withMockIO`) を使用せずに、特定の引数に対して値を返す関数を作ることもできます。+その場合は、事後検証 (`shouldBeCalled`) を組み合わせることになります。  ```haskell-withMock $ do-  -- 定義と同時に期待値(expects)を書く-  f <- mock (any ~> True)-    `expects` do-      called once `with` "arg"+import Test.Hspec+import Test.MockCat -  -- 実行-  f "arg"+spec :: Spec+spec = do+  it "Function Mocking" $ do+    -- "Hello" に対して 1 を返す関数を定義 (expects は書かない)+    f <- mock ("Hello" ~> True)+    +    -- 実行+    f "Hello" `shouldBe` True -#### `withMockIO`: IO テストの簡略化-`withMockIO` は `withMock` を IO に特化させたバージョンです。`liftIO` を使わずにモックコンテキスト内で直接 IO アクションを実行できます。+    -- 事後検証 (shouldBeCalled)+    f `shouldBeCalled` "Hello"+``` +> [!WARNING]+> **HPC (コードカバレッジ) 環境での制限**+> `stack test --coverage` 等を使用する場合、`shouldBeCalled` は使用しないでください。+> GHC のカバレッジ計測機能が関数をラップするため、関数の同一性が失われ、検証に失敗します。+> カバレッジ計測が必要な場合は、**`expects`** スタイル (Section 1) を使用してください。++**柔軟なマッチング**:+具体的な値だけでなく、条件(述語)を指定することもできます。+ ```haskell-it "IO test" $ withMockIO do-  f <- mock (any ~> pure "result")-  res <- someIOCall f-  res `shouldBe` "result"-```-```+{-# LANGUAGE TypeApplications #-}+import Test.Hspec+import Test.MockCat+import Prelude hiding (any) -> [!IMPORTANT]-> `expects`(宣言的検証)を使用する場合、モック定義部分は必ず **括弧 `(...)`** で囲んでください。-> 以前のバージョンで使用できた `$` 演算子 (`mock $ ... expected ...`) は、優先順位の関係でコンパイルエラーになります。->-> ❌ `mock $ any ~> True expects ...`-> ✅ `mock (any ~> True) expects ...`+spec :: Spec+spec = do+  it "Matcher Examples" $ do+    -- 任意の文字列 (param any)+    f <- mock (any @String ~> True)+    f "foo" `shouldBe` True -> [!NOTE]-> `runMockT` ブロックの中でも、同様に `expects` を使った宣言的検証が可能です。-> 生成された型クラスのモック関数(`_xxx`)に対してもそのまま使用できます。->-> ```haskell-> runMockT do->   _readFile "config.txt" ~> pure "value"->     `expects` called once-> ```+    -- 条件式 (when)+    g <- mock (when (> (5 :: Int)) "> 5" ~> True)+    g 6 `shouldBe` True+```  ### 4. 柔軟な検証(マッチャー) @@ -273,7 +302,7 @@  ```haskell -- どんな引数で呼ばれても True を返す-f <- mock (any ~> True)+f <- mock (any @String ~> True)  -- 何でもいいから呼ばれたことを検証 f `shouldBeCalled` any@@ -285,16 +314,21 @@ `Eq` を持たない型(関数など)や、部分的な一致を確認したい場合に強力です。  ```haskell--- 引数が "error" で始まる場合のみ False を返す-f <- mock do-  onCase $ when (\s -> "error" `isPrefixOf` s) "start with error" ~> False-  onCase $ any ~> True+it "When Example" $ do+  -- 引数が "error" で始まる場合のみ False を返す+  f <- mock $ do+    onCase $ when (\s -> "error" `isPrefixOf` s) "start with error" ~> False+    onCase $ any ~> True++  f "error message" `shouldBe` False+  f "success" `shouldBe` True+  f "other" `shouldBe` True ```  ラベル(エラー時に表示される説明)が不要な場合は、`when_` を使用することもできます。  ```haskell-f <- mock (when_ (> 5) ~> True)+f <- mock (when_ (> (5 :: Int)) ~> True) ```  ### 5. 高度な機能 - [応用]@@ -400,28 +434,6 @@  ※ このセクションは、困ったときの辞書として使ってください。 -### 検証マッチャ一覧 (`shouldBeCalled`)--| マッチャ | 説明 | 例 |-| :--- | :--- | :--- |-| `x` (値そのもの) | その値で呼ばれたか | ``f `shouldBeCalled` (10 :: Int)`` |-| `times n` | 回数指定 | ``f `shouldBeCalled` (times 3 `with` "arg")`` |-| `once` | 1回だけ | ``f `shouldBeCalled` (once `with` "arg")`` |-| `never` | 呼ばれていない | ``f `shouldBeCalled` never`` |-| `atLeast n` | n回以上 | ``f `shouldBeCalled` atLeast 2`` |-| `atMost n` | n回以下 | ``f `shouldBeCalled` atMost 5`` |-| `anything` | 引数は何でも良い(回数不問) | ``f `shouldBeCalled` anything`` |-| `inOrderWith [...]` | 厳密な順序 | ``f `shouldBeCalled` inOrderWith ["a", "b"]`` |-| `inPartialOrderWith [...]` | 部分的順序(間飛びOK) | ``f `shouldBeCalled` inPartialOrderWith ["a", "c"]`` |--### パラメータマッチャ一覧(引数定義)--| マッチャ | 説明 | 例 |-| :--- | :--- | :--- |-| `any` | 任意の値 | `any ~> True` |-| `when pred label` | 条件式 | `when (>0) "positive" ~> True` |-| `when_ pred` | ラベルなし | `when_ (>0) ~> True` |- ### 宣言的検証 DSL (`expects`)  `expects` ブロックでは、ビルダースタイルの構文を使って宣言的に期待値を記述できます。@@ -456,6 +468,28 @@ | **`with matcher`** | マッチャを使って引数を検証します。 | `called `with` when (>5) "gt 5"` | | **`inOrder`** | 呼び出し順序を検証 (リスト内で使用) | (順序検証の項を参照) | +### 検証マッチャ一覧 (`shouldBeCalled`)++| マッチャ | 説明 | 例 |+| :--- | :--- | :--- |+| `x` (値そのもの) | その値で呼ばれたか | ``f `shouldBeCalled` (10 :: Int)`` |+| `times n` | 回数指定 | ``f `shouldBeCalled` (times 3 `with` "arg")`` |+| `once` | 1回だけ | ``f `shouldBeCalled` (once `with` "arg")`` |+| `never` | 呼ばれていない | ``f `shouldBeCalled` never`` |+| `atLeast n` | n回以上 | ``f `shouldBeCalled` atLeast 2`` |+| `atMost n` | n回以下 | ``f `shouldBeCalled` atMost 5`` |+| `anything` | 引数は何でも良い(回数不問) | ``f `shouldBeCalled` anything`` |+| `inOrderWith [...]` | 厳密な順序 | ``f `shouldBeCalled` inOrderWith ["a", "b"]`` |+| `inPartialOrderWith [...]` | 部分的順序(間飛びOK) | ``f `shouldBeCalled` inPartialOrderWith ["a", "c"]`` |++### パラメータマッチャ一覧(引数定義)++| マッチャ | 説明 | 例 |+| :--- | :--- | :--- |+| `any` | 任意の値 | `any ~> True` |+| `when pred label` | 条件式 | `when (>0) "positive" ~> True` |+| `when_ pred` | ラベルなし | `when_ (>0) ~> True` |+ ### よくある質問 (FAQ)  <details>@@ -470,7 +504,9 @@  <details> <summary><strong>Q. コードカバレッジ (HPC) を有効にしてテストを実行できますか?</strong></summary>-A. はい (v1.1.0.0 以降)。Mockcat は HPC によって生じる 不安定さを内部で吸収するため、`stack test --coverage` を問題なく実行できます。+A. はい (v1.1.0.0 以降)。Mockcat の `expects` スタイルは、HPC による関数のラップの影響を受けない設計になっているため、HPC下でも安全に動作します。+ただし、前述の理由により **`expects`** スタイル (または `withMock`) の使用を推奨します。+`shouldBeCalled` スタイルは、HPC の仕組み上、モックの同一性を特定できないため使用できません。 </details>  <details>
README.md view
@@ -14,14 +14,18 @@ </div>  **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.+It supports two verification styles: +*   **Declarative Verification (`expects`)**: **[Recommended]** Declaratively state the expected behavior at mock definition time.+*   **Post-hoc Verification (`shouldBeCalled`)**: Verify call history after test execution.+ ```haskell--- Define+-- Define and Expect at the same time ("input" should be called exactly once) f <- mock ("input" ~> "output")+  `expects` called once --- Verify-f `shouldBeCalled` "input"+-- Execute+f "input" ```  ---@@ -61,7 +65,7 @@ | | **Before: Handwritten...** 😫 | **After: Mockcat** 🐱✨ | | :--- | :--- | :--- | | **Definition (Stub)**<br />"I want to return<br />this value for this arg" | <pre>f :: String -> IO String<br />f arg = case arg of<br />  "a" -> pure "b"<br />  _   -> error "unexpected"</pre><br />_Even simple branching consumes many lines._ | <pre>-- Use stub if verification is unneeded (Pure)<br />let f = stub ("a" ~> "b")</pre><br />_Behaves as a completely pure function._ |-| **Verification (Verify)**<br />"I want to test<br />if it was called correctly" | <pre>-- Need to implement recording logic<br />ref <- newIORef []<br />let f arg = do<br />      modifyIORef ref (arg:)<br />      ...<br /><br />-- Verification logic<br />calls <- readIORef ref<br />calls `shouldBe` ["a"]</pre><br />_※ This is just one example. Real-world setups often require even more boilerplate._ | <pre>-- Use mock if verification is needed (Recorded internally)<br />f <- mock ("a" ~> "b")<br /><br />-- Just state what you want to verify<br />f `shouldBeCalled` "a"</pre><br />_Recording is automatic.<br />Focus on the "Why" and "What", not the "How"._ |+| **Verify**<br />(Did it get called<br />correctly?) | <pre>-- Need manual recording mechanism<br />ref <- newIORef []<br />let f arg = do<br />      modifyIORef ref (arg:)<br />      ...<br /><br />-- Verification Logic<br />calls <- readIORef ref<br />calls `shouldBe` ["a"]</pre><br />_ This is just one example. Boilerplate often grows._ | <pre>withMock $ do<br />  -- Declare expected values at definition<br />  f <- mock ("a" ~> "b")<br />    &#96;expects&#96; called once<br /><br />  -- Just execute (Automatic verification)</pre><br />_Recording is automatic.<br />Focus on the "Why" and "What", not the "How"._ |  ### Key Features @@ -107,72 +111,90 @@ ### First Test (`Main.hs` / `Spec.hs`)  ```haskell-{-# 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))+  it "Quick Start Demo" $ do+    withMockIO $ do+      -- 1. Create a mock (Return 42 when receiving "Hello")+      --    Simultaneously declare "it should be called once"+      f <- mock ("Hello" ~> (42 :: Int))+        `expects` called once -    -- 2. Use it as a function-    let result = f "Hello"-    result `shouldBe` 42+      -- 2. Call f "Hello" and get 42+      f "Hello" `shouldBe` 42 -    -- 3. Verify it was called-    f `shouldBeCalled` "Hello"+      -- 3. Verification happens automatically when exiting the scope ``` ---- -### At a Glance: Matchers-| Matcher | Description | Example |-| :--- | :--- | :--- |-| **`any`** | Matches any value | `f <- mock (any ~> True)` |-| **`when`** | Matches condition | `f <- mock (when (> 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. -1.  **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)-2.  **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]+### 1. Declarative Verification (`withMock` / `expects`) - [Recommended] -The most basic usage. Creates a function that returns values for specific arguments.+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.  ```haskell--- Function that returns True for "a" -> "b"-f <- mock ("a" ~> "b" ~> True)+import Test.Hspec+import Test.MockCat+import Control.Monad.IO.Class (MonadIO(liftIO))++spec :: Spec+spec = do+  it "User Guide (withMock)" $ do+    withMock $ do+      -- Define a mock that returns True for "Hello"+      f <- mock ("Hello" ~> True)+        `expects` called once++      -- Execution+      let result = f "Hello"++      liftIO $ result `shouldBe` True ``` -**Flexible Matching**:-You can specify conditions (predicates) instead of concrete values.+#### `withMockIO`: Simplified IO Testing+`withMockIO` is an IO-specialized version of `withMock`. It allows you to run IO actions directly within the mock context without needing `liftIO`.  ```haskell--- Arbitrary string (param any)-f <- mock (any ~> True)+import Test.Hspec+import Test.MockCat --- Condition (when)-f <- mock (when (> 5) "> 5" ~> True)+spec :: Spec+spec = do+  it "User Guide (withMockIO)" $ do+    withMockIO $ do+      f <- mock ("Hello" ~> True)+        `expects` called once++      let result = f "Hello"++      result `shouldBe` True ``` +> [!IMPORTANT]+> When using `expects` (declarative verification), you MUST wrap the mock definition in **parentheses `(...)`**.+> The `$` operator pattern used in previous versions (`mock $ ... expected ...`) will cause compilation errors due to precedence changes.+>+> ❌ `mock $ any ~> True expects ...`+> ✅ `mock (any ~> True) expects ...`++> [!NOTE]+> You can also use `expects` for declarative verification inside `runMockT` blocks.+> This works seamlessly with generated typeclass mocks as well.+>+> ```haskell+> runMockT do+>   _readFile ("config.txt" ~> pure "value")+>     `expects` called once+> ```+ ### 2. Typeclass Mocking (`makeMock`)  Useful when you want to bring existing typeclasses directly into your tests. Generates mocks from existing typeclasses using Template Haskell.@@ -223,48 +245,54 @@     result `shouldBe` () ``` -### 3. Declarative Verification (`withMock` / `expects`)+### 3. Function Mocking and Post-Verification (`mock` / `shouldBeCalled`) -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.+The most basic usage. Creates a function that returns values for specific arguments.+Combining it with Post-Verification (`shouldBeCalled`) makes it suitable for exploratory testing or prototyping.  ```haskell-withMock $ do-  -- Write expectations (expects) at definition time-  f <- mock (any ~> True)-    `expects` do-      called once `with` "arg"+import Test.Hspec+import Test.MockCat -  -- Execution-  f "arg"+spec :: Spec+spec = do+  it "Function Mocking" $ do+    -- Define a mock that returns True for "Hello" (No 'expects' here)+    f <- mock ("Hello" ~> True)+    +    -- Execution+    f "Hello" `shouldBe` True -#### `withMockIO`: Simplified IO Testing-`withMockIO` is an IO-specialized version of `withMock`. It allows you to run IO actions directly within the mock context without needing `liftIO`.+    -- Post-Verification (shouldBeCalled)+    f `shouldBeCalled` "Hello"+``` +> [!WARNING]+> **Limitation in HPC (Code Coverage) Environments**+> Do not use `shouldBeCalled` when running tests with `stack test --coverage` or similar.+> The code coverage instrumentation by GHC wraps functions, which changes their identity and causes verification to fail.+> If you need code coverage, please use the **`expects`** style (Section 1).++**Flexible Matching**:+You can specify conditions (predicates) instead of concrete values.+ ```haskell-it "IO test" $ withMockIO do-  f <- mock (any ~> pure "result")-  res <- someIOCall f-  res `shouldBe` "result"-```-```+{-# LANGUAGE TypeApplications #-}+import Test.Hspec+import Test.MockCat+import Prelude hiding (any) -> [!IMPORTANT]-> When using `expects` (declarative verification), you MUST wrap the mock definition in **parentheses `(...)`**.-> The `$` operator pattern used in previous versions (`mock $ ... expected ...`) will cause compilation errors due to precedence changes.->-> ❌ `mock $ any ~> True expects ...`-> ✅ `mock (any ~> True) expects ...`+spec :: Spec+spec = do+  it "Matcher Examples" $ do+    -- Arbitrary string (param any)+    f <- mock (any @String ~> True)+    f "foo" `shouldBe` True -> [!NOTE]-> You can also use `expects` for declarative verification inside `runMockT` blocks.-> This works seamlessly with generated typeclass mocks as well.->-> ```haskell-> runMockT do->   _readFile "config.txt" ~> pure "value"->     `expects` called once-> ```+    -- Condition (when)+    g <- mock (when (> (5 :: Int)) "> 5" ~> True)+    g 6 `shouldBe` True+```  ### 4. Flexible Verification (Matchers) @@ -402,28 +430,6 @@  ※ 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` |-| `when pred label` | Condition | `when (>0) "positive" ~> True` |-| `when_ pred` | No label | `when_ (>0) ~> True` |- ### Declarative Verification DSL (`expects`)  In `expects` blocks, you can describe expectations declaratively using a builder-style syntax.@@ -458,6 +464,28 @@ | **`with matcher`** | Uses a matcher for argument verification. | `called `with` when (>5) "gt 5"` | | **`inOrder`** | Verify usage order (when used in a list) | (See "Order Verification" section) | +### 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` |+| `when pred label` | Condition | `when (>0) "positive" ~> True` |+| `when_ pred` | No label | `when_ (>0) ~> True` |+ ### FAQ  <details>@@ -472,7 +500,9 @@  <details> <summary><strong>Q. Can I run tests with code coverage (HPC)?</strong></summary>-A. Yes (since v1.1.0.0). Mockcat natively handles the instability of `StableName` introduced by HPC instrumentation, so you can run `stack test --coverage` without issues.+A. Yes (since v1.1.0.0). Mockcat's `expects` style is designed to be unaffected by the function wrapping performed by HPC, so it operates safely even under HPC.+However, for the reasons mentioned above, we strongly recommend using the **`expects`** style (or `withMock`).+The `shouldBeCalled` style cannot be used because HPC's mechanism makes it impossible to identify mock identity. </details>  <details>
mockcat.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           mockcat-version:        1.3.3.0+version:        1.4.0.0 synopsis:       Declarative mocking with a single arrow `~>`. description:    Mockcat is a minimal, architecture-agnostic mocking library for Haskell.                 It enables declarative verification and intent-driven matching, allowing you to define function behavior and expectations without specific architectural dependencies.@@ -38,7 +38,6 @@ library   exposed-modules:       Test.MockCat-      Test.MockCat.AssociationList       Test.MockCat.Cons       Test.MockCat.Internal.Builder       Test.MockCat.Internal.GHC.StableName@@ -89,17 +88,15 @@       Property.ParamSpecRangeMergeRandomProp       Property.ReinforcementProps       Property.ScriptProps-      ReadmeVerifySpec       Support.ParamSpec       Support.ParamSpecNormalize-      Test.MockCat.AssociationListSpec       Test.MockCat.ConcurrencySpec       Test.MockCat.ConfirmTHSpec       Test.MockCat.ConsSpec       Test.MockCat.DeriveSpec       Test.MockCat.ExampleSpec-      Test.MockCat.HPCFallbackSpec       Test.MockCat.HPCNestSpec+      Test.MockCat.HpcSpec       Test.MockCat.Impl       Test.MockCat.Internal.MockRegistrySpec       Test.MockCat.MockSpec@@ -109,11 +106,20 @@       Test.MockCat.PartialMockCommonSpec       Test.MockCat.PartialMockSpec       Test.MockCat.PartialMockTHSpec+      Test.MockCat.Readme.MakeAutoLiftMockSpec+      Test.MockCat.Readme.MakeMockSpec+      Test.MockCat.Readme.MatcherSpec+      Test.MockCat.Readme.QuickStartDemoSpec+      Test.MockCat.Readme.ReadmeSpec+      Test.MockCat.Readme.ShouldBeCalledSpec+      Test.MockCat.Readme.WithMockIOSpec+      Test.MockCat.Readme.WithMockSpec       Test.MockCat.SharedSpecDefs       Test.MockCat.ShouldBeCalledErrorDiffSpec       Test.MockCat.ShouldBeCalledMockMSpec       Test.MockCat.ShouldBeCalledSpec       Test.MockCat.StubSpec+      Test.MockCat.TestHelper       Test.MockCat.TH.ClassAnalysisSpec       Test.MockCat.TH.ContextBuilderSpec       Test.MockCat.TH.FunctionBuilderSpec
− src/Test/MockCat/AssociationList.hs
@@ -1,34 +0,0 @@-module Test.MockCat.AssociationList - (AssociationList, empty, insert, lookup, member, (!?), update) where--import Prelude hiding (lookup)-import Data.Maybe (isJust)--type AssociationList k a = [(k, a)]--empty :: AssociationList k a-empty = []--insert :: Eq k => k -> a -> AssociationList k a -> AssociationList k a-insert key value [] = [(key, value)]-insert key value ((k, v) : xs)-  | key == k  = (key, value) : xs-  | otherwise = (k, v) : insert key value xs--lookup :: Eq k => k -> AssociationList k a -> Maybe a-lookup _ [] = Nothing-lookup key ((k, a) : xs)-  | key == k  = Just a-  | otherwise = lookup key xs--member :: Eq k => k -> AssociationList k a -> Bool-member k list = isJust (lookup k list)--(!?) :: Eq k => AssociationList k a -> k -> Maybe a-(!?) = flip lookup--update :: Eq k => (a -> a) -> k -> AssociationList k a -> AssociationList k a-update f key list =-  case list !? key of-    Just value -> insert key (f value) list-    Nothing    -> list
src/Test/MockCat/Internal/Builder.hs view
@@ -29,7 +29,6 @@ import Data.Maybe import Test.MockCat.Cons (Head(..), (:>)(..)) import Test.MockCat.Param-import Test.MockCat.AssociationList (lookup, update, insert, empty, member) import Prelude hiding (lookup) import Control.Monad.State import Test.MockCat.Internal.Types@@ -155,20 +154,6 @@     pure (BuiltMock fn recorder)  ---- | Instance for building a stub for a value (backward compatibility).-instance-  MockBuilder (Param r) r ()-  where-  build _ params = do-    ref <- liftIO $ newTVarIO invocationRecord-    let v = value params-        fn = perform $ do-          liftIO $ appendCalledParams ref ()-          pure v-        recorder = InvocationRecorder ref PureConstant-    pure (BuiltMock fn recorder)- -- | Instance for building a stub for `Cases (IO a) ()`. instance MockBuilder (Cases (IO a) ()) (IO a) () where   build _ cases = do@@ -257,7 +242,7 @@ invocationRecord =   InvocationRecord     { invocations = mempty-    , invocationCounts = empty+    , invocationCounts = []     }  appendCalledParams :: TVar (InvocationRecord params) -> params -> IO ()@@ -268,23 +253,18 @@         { invocations = invocations record ++ [inputParams]         } -readInvocationCount :: Eq params => TVar (InvocationRecord params) -> params -> IO Int+readInvocationCount :: EqParams params => TVar (InvocationRecord params) -> params -> IO Int readInvocationCount ref params = do   record <- readTVarIO ref-  pure $ fromMaybe 0 (lookup params (invocationCounts record))+  pure $ fromMaybe 0 (lookupEqParams params (invocationCounts record)) -incrementInvocationCount :: Eq params => TVar (InvocationRecord params) -> params -> IO ()+incrementInvocationCount :: EqParams params => TVar (InvocationRecord params) -> params -> IO () incrementInvocationCount ref inputParams =   atomically $     modifyTVar' ref $ \record ->       record-        { invocationCounts = incrementCount inputParams (invocationCounts record)+        { invocationCounts = incrementCountEqParams inputParams (invocationCounts record)         }--incrementCount :: Eq k => k -> InvocationCounts k -> InvocationCounts k-incrementCount key list =-  if member key list then update (+ 1) key list-  else insert key 1 list  runCase :: Cases a b -> [a] runCase (Cases s) = execState s []
src/Test/MockCat/Internal/MockRegistry.hs view
@@ -13,7 +13,6 @@   , UnitMeta   , withUnitGuard   , withAllUnitGuards-  , markUnitUsed   , isGuardActive   , getLastRecorder   , resetMockHistory@@ -27,7 +26,6 @@   , UnitMeta   , withUnitGuard   , withAllUnitGuards-  , markUnitUsed   , isGuardActive   , getLastRecorder   , resetMockHistory@@ -52,8 +50,8 @@  -- | Wrap a function value for unit-typed stubs so that calls are tracked. -- This uses the UnitMeta guard to avoid double-counting when both the tracked--- and base values are registered. The wrapper will mark the unit meta used and--- append an invocation to the recorder's TVar when appropriate.+-- and base values are registered. The wrapper will append an invocation to the+-- recorder's TVar when appropriate. wrapUnitStub ::   forall fn.   Typeable fn =>@@ -67,7 +65,6 @@         if guardActive || isIOType (Proxy :: Proxy fn)           then pure value           else do-            markUnitUsed meta             appendCalledParams ref ()             pure value   in
src/Test/MockCat/Internal/Registry/Core.hs view
@@ -3,21 +3,18 @@ {-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ExistentialQuantification #-}+{- HLINT ignore "Use newtype instead of data" -}   module Test.MockCat.Internal.Registry.Core   ( attachVerifierToFn   , lookupVerifierForFn   , attachDynamicVerifierToFn-  , createOverlay-  , installOverlay-  , clearOverlay   , registerUnitMeta   , lookupUnitMeta   , UnitMeta   , withUnitGuard   , withAllUnitGuards-  , markUnitUsed   , isGuardActive   , getLastRecorder   , getLastRecorderRaw@@ -153,8 +150,6 @@   tid <- myThreadId   atomically $ modifyTVar' threadWithMockStore (Map.delete tid) -- attachVerifierToFn ::   forall fn params.   (Typeable (InvocationRecorder params)) =>@@ -181,15 +176,7 @@          case mbMatch of     Just match -> pure [match]-    Nothing -> do-      -- 2. Fallback: return thread's mock history-      --    This handles case where StableName is unstable (e.g. HPC enabled)-      tid <- myThreadId-      atomically $ do-        hist <- readTVar threadMockHistory-        case Map.lookup tid hist of-           Nothing -> pure []-           Just list -> pure list+    Nothing -> pure []  attachDynamicVerifierToFn :: forall fn. fn -> (Maybe MockName, Dynamic) -> IO () attachDynamicVerifierToFn fn (name, payload) = do@@ -223,17 +210,10 @@   | sameFnStable target (stableFnName entry) = Just (mockName entry, entryPayload entry)   | otherwise = findMatch target rest ------ type UnitStableName = SomeStableName  data UnitMeta = UnitMeta   { unitGuardRef :: TVar Bool-  , unitUsedRef :: TVar Bool   }  data UnitEntry = UnitEntry !UnitStableName !UnitMeta@@ -255,22 +235,6 @@ unitRegistry :: TVar UnitRegistry unitRegistry = (unsafePerformIO $ newTVarIO IntMap.empty) :: TVar UnitRegistry --- | Per-run overlay registry (optional).-data Overlay = Overlay---- | Run the given IO action with a per-run overlay registry active.--- The overlay is cleaned up after the action completes.-createOverlay :: IO Overlay-createOverlay = pure Overlay--installOverlay :: Overlay -> IO ()-installOverlay _ = pure ()--clearOverlay :: IO ()-clearOverlay = pure ()--- registerUnitMeta :: TVar ref -> IO UnitMeta registerUnitMeta ref = do   stable <- makeStableName ref@@ -308,17 +272,13 @@ withAllUnitGuards :: IO a -> IO a withAllUnitGuards = bracket_ (setAllUnitGuards True) (setAllUnitGuards False) -markUnitUsed :: UnitMeta -> IO ()-markUnitUsed meta = atomically $ writeTVar (unitUsedRef meta) True- isGuardActive :: UnitMeta -> IO Bool-isGuardActive meta = readTVarIO (unitGuardRef meta)+isGuardActive (UnitMeta guardRef) = readTVarIO guardRef  createUnitMeta :: IO UnitMeta createUnitMeta = do   guardRef <- newTVarIO False-  usedRef <- newTVarIO False-  pure UnitMeta {unitGuardRef = guardRef, unitUsedRef = usedRef}+  pure $ UnitMeta guardRef  findUnit :: UnitStableName -> [UnitEntry] -> Maybe UnitMeta findUnit _ [] = Nothing@@ -332,5 +292,3 @@     store <- readTVar unitRegistry     forM_ (concat (IntMap.elems store)) $ \entry ->       writeTVar (unitGuardRef (unitEntryMeta entry)) flag--
src/Test/MockCat/Internal/Types.hs view
@@ -15,9 +15,10 @@ import Control.Concurrent.STM (TVar) import Data.Maybe (listToMaybe) import GHC.IO (unsafePerformIO)-import Test.MockCat.AssociationList (AssociationList) import Prelude hiding (lookup) import Control.Monad.State ( State, MonadState, execState, modify )++type AssociationList k a = [(k, a)]  type MockName = String 
src/Test/MockCat/MockT.hs view
@@ -162,16 +162,12 @@           , envWithMockContext = withMockCtx           , envNameForwarders = fwdRef           }-  -- Run user code with a per-run overlay registry active so registry writes/read-  -- during this MockT invocation are isolated to this run.-  overlay <- liftIO Registry.createOverlay-  liftIO $ Registry.installOverlay overlay+  -- Run user code.    liftIO $ Registry.setThreadWithMockContext withMockCtx   a <- runReaderT r env   actions <- liftIO $ readTVarIO expectsVar   liftIO $ sequence_ actions   liftIO Registry.clearThreadWithMockContext-  liftIO Registry.clearOverlay   pure a  instance MonadIO m => MonadMockDefs (MockT m) where
src/Test/MockCat/Verify.hs view
@@ -174,28 +174,19 @@ verificationFailureMessage =   intercalate     "\n"-    [ "Error: 'shouldBeCalled' can only verify functions created by 'mock'.",-      "",-      "The value you passed could not be recognized as a mock function.",-      "",-      "This usually happens in one of the following cases:",-      "  - You passed a normal (non-mock) function.",-      "  - You passed a stub or value not created via 'mock' / 'mockIO'.",-      "  - You are trying to verify a value that was never registered as a mock.",+    [ "Error: Mock verification failed.",       "",-      "How to fix it:",-      "  1. Make sure you created the function with 'mock' (or 'mockIO' for IO)",-      "     before calling 'shouldBeCalled'.",-      "  2. Pass that mock value directly to 'shouldBeCalled'",-      "     (not the original function or a plain value).",+      "The function passed to 'shouldBeCalled' could not be recognized as a registered mock.",       "",-      "If this message still appears, check that:",-      "  - You are not passing a pure constant.",-      "  - The mock value is still in scope where 'shouldBeCalled' is used.",+      "Possible causes:",+      "  1. You passed a raw wrapper function around the mock.",+      "  2. You passed a normal (non-mock) function.",+      "  3. HPC (Coverage) is enabled. (HPC instrumentation changes function identity)",       "",-      "Tip: If you prefer automatic verification,",-      "consider using 'withMock', which runs all expectations at the end",-      "of the block."+      "Solution:",+      "  - If you are using HPC or Wrappers, please use 'expects' or 'withMock' style.",+      "    'expects' is robust against HPC and wrappers as it does not rely on function identity lookup.",+      "  - Ensure you are passing the mock function directly created by 'mock'."     ]  -- ============================================
test/Property/AdditionalProps.hs view
@@ -7,22 +7,32 @@ {-# OPTIONS_GHC -fno-hpc #-} {-# OPTIONS_GHC -Wno-deprecations #-} module Property.AdditionalProps-  ( prop_predicate_param_match_counts+  ( spec+  , prop_predicate_param_match_counts   , prop_multicase_progression   , prop_runMockT_isolation   , prop_partial_order_duplicates   ) where -import Test.QuickCheck+import Test.QuickCheck hiding (once) import Test.QuickCheck.Monadic (monadicIO, run, assert) import Control.Exception (try, SomeException, evaluate) import Control.Monad (forM, forM_) import Data.List (nub)-import Data.Proxy (Proxy(..))+ import Test.MockCat import Control.Monad.IO.Class (liftIO) import Property.Generators (resetMockHistory)+import Test.Hspec (Spec, describe, it) +spec :: Spec+spec = do+    describe "Property Additional (Predicate / Multi-case / Isolation / Duplicates)" $ do+      it "predicate param counts match" $ property prop_predicate_param_match_counts+      it "multi-case progression saturates" $ property prop_multicase_progression+      it "runMockT isolation of counts" $ property prop_runMockT_isolation+      it "partial order with duplicates behaves" $ property prop_partial_order_duplicates+ perCall :: Int -> a -> a perCall i x = case i of   _ -> x@@ -33,19 +43,26 @@  -- | Property: A predicate Param (expect even) accepts only matching values and -- the total recorded calls equals count of even inputs attempted.+-- | Property: A predicate Param (expect even) accepts only matching values and+-- the total recorded calls equals count of even inputs attempted. prop_predicate_param_match_counts :: Property prop_predicate_param_match_counts = forAll genVals $ \xs -> monadicIO $ do-  run resetMockHistory-  -- build predicate mock: (Int -> Bool) returning True for all evens-  f <- run $ mock (expect even "even" ~> True)-  results <- run $ mapM (\x -> try (evaluate (f x)) :: IO (Either SomeException Bool)) xs-  let successes = length [ () | Right _ <- results ]-      expected  = length (filter even xs)-  -- sanity: every success came from an even value-  assert (all even [ x | (x, Right _) <- zip xs results ])-  -- counts should match-  run $ f `shouldBeCalled` times expected-  assert (successes == expected)+  -- Use withMock+  let expected  = length (filter even xs)+  run $ withMock $ do+     -- build predicate mock+     f <- mock (expect even "even" ~> True)+            `expects` called (times expected)+            +     results <- liftIO $ mapM (\x -> try (evaluate (f x)) :: IO (Either SomeException Bool)) xs+     +     let successes = length [ () | Right _ <- results ]+     -- sanity checks (optional, but runMockT handles main verification)+     liftIO $ do+       if successes /= expected +         then error $ "Success count mismatch: " ++ show successes ++ " vs " ++ show expected+         else pure ()+  assert True   where     genVals = resize 40 $ listOf (arbitrary :: Gen Int) @@ -57,23 +74,23 @@ -- saturate at the last value. prop_multicase_progression :: Property prop_multicase_progression = forAll genSeq $ \(arg, rs, extra) -> monadicIO $ do-  run resetMockHistory-  f <- run $ mock $ cases [ param arg ~> r | r <- rs ]   let totalCalls = length rs + extra-  -- NOTE [GHC9.4 duplicate-call counting]-  -- On GHC 9.4 we observed that using @replicateM totalCalls (evaluate (f arg))@-  -- can result in only a single side‑effect (call recording) when all-  -- of (argument,result) pairs are identical. The optimizer (or full laziness-  -- even under -O0) may float the pure expression @f arg@ and share it.-  -- We intentionally inject the loop index via a seq so each iteration has a-  -- syntactically distinct thunk, ensuring the unsafePerformIO body runs per call.-  vals <- run $-    forM [1 .. totalCalls] $ \i ->-      evaluate (perCall i (f arg))-  let (prefix, suffix) = splitAt (length rs) vals-  assert (prefix == rs)-  assert (all (== last rs) suffix)-  run $ f `shouldBeCalled` times totalCalls+  run $ withMock $ do+    f <- mock (cases [ param arg ~> r | r <- rs ])+           `expects` called (times totalCalls)+           +    -- Run loop+    vals <- liftIO $+      forM [1 .. totalCalls] $ \i ->+        evaluate (perCall i (f arg))+        +    liftIO $ do+      let (prefix, suffix) = splitAt (length rs) vals+      if prefix /= rs || not (all (== last rs) suffix)+         then error "Result sequence mismatch"+         else pure ()+         +  assert True   where     genSeq = do       arg <- arbitrary :: Gen Int@@ -93,18 +110,23 @@ prop_runMockT_isolation = monadicIO $ do   run resetMockHistory   -- Run 1: expect one call-  r1 <- run $ try $ runMockT $ do-    f <- liftIO $ mock (param (1 :: Int) ~> True)-    addDefinition Definition { symbol = Proxy @"iso", mockFunction = f, verification = Verification (\m' -> m' `shouldBeCalled` times 1) }+  r1 <- run ((try $ runMockT $ do+    f <- mock (param (1 :: Int) ~> True)+          `expects` called once     liftIO $ f 1 `seq` pure ()+    ) :: IO (Either SomeException ()))+   case r1 of     Left (_ :: SomeException) -> assert False     Right () -> assert True+       -- Run 2: expect zero (if leaked, would see 1 and fail)-  r2 <- run $ try $ runMockT $ do-    f <- liftIO $ mock (param (1 :: Int) ~> True)-    addDefinition Definition { symbol = Proxy @"iso", mockFunction = f, verification = Verification (\m' -> m' `shouldBeCalled` times 0) }+  r2 <- run ((try $ runMockT $ do+    _f <- mock (param (1 :: Int) ~> True)+          `expects` called never     pure ()+    ) :: IO (Either SomeException ()))+       case r2 of     Left (_ :: SomeException) -> assert False     Right () -> assert True@@ -118,19 +140,24 @@ -- list of first occurrences; reversing that list (when length >=2) fails. prop_partial_order_duplicates :: Property prop_partial_order_duplicates = forAll genDupScript $ \xs -> length xs >= 2 ==> monadicIO $ do-  run resetMockHistory-  -- build mock over sequence-  f <- run $ mock $ cases [ param x ~> True | x <- xs ]-  run $ forM_ xs $ \x -> f x `seq` pure ()   let uniques = nub xs   -- success case-  run $ f `shouldBeCalled` inPartialOrderWith (param <$> uniques)+  run $ withMock $ do+      f <- mock (cases [ param x ~> True | x <- xs ])+             `expects` calledInSequence uniques+      liftIO $ forM_ xs $ \x -> f x `seq` pure ()   assert True+     -- failure case (if we have at least two unique values)   case uniques of     (_:_:_) | isClusterOrdered uniques xs -> do       let reversed = reverse uniques-      e <- run (try (f `shouldBeCalled` inPartialOrderWith (param <$> reversed)) :: IO (Either SomeException ()))+      e <- run ((try $ withMock $ do+          f <- mock (cases [ param x ~> True | x <- xs ])+                `expects` calledInSequence reversed+          liftIO $ forM_ xs $ \x -> f x `seq` pure ()+          ) :: IO (Either SomeException ()))+                 case e of         Left _ -> assert True         Right _ -> assert False
test/Property/ConcurrentCountProp.hs view
@@ -8,7 +8,7 @@ {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -Wno-missing-export-lists #-} {-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}-module Property.ConcurrentCountProp where+module Property.ConcurrentCountProp (spec, prop_concurrent_total_apply_count) where  import Test.QuickCheck import Test.QuickCheck.Monadic (monadicIO, run, assert)@@ -17,7 +17,9 @@ import Control.Monad.IO.Unlift (withRunInIO, MonadUnliftIO) import Test.MockCat hiding (any) import qualified Test.MockCat as MC (any)+import Test.Hspec (Spec, describe, it) + -- Class for local concurrent actions (defined independently instead of reusing existing ConcurrencySpec) class Monad m => PropConcurrencyAction m where   propAction :: Int -> m Int@@ -46,3 +48,8 @@ parallelInvoke threads callsPerThread = withRunInIO $ \runInIO -> do   as <- replicateM threads (async $ runInIO $ replicateM_ callsPerThread (propAction 42 >> pure ()))   mapM_ wait as++spec :: Spec+spec = do+  describe "Property Concurrency" $ do+    it "total apply count is preserved across threads" $ property prop_concurrent_total_apply_count
test/Property/LazyEvalProp.hs view
@@ -8,12 +8,15 @@ {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -Wno-missing-export-lists #-} {-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}-module Property.LazyEvalProp where+module Property.LazyEvalProp (spec, prop_lazy_unforced_not_counted, prop_lazy_forced_counted) where  import Test.QuickCheck hiding (once) import Test.QuickCheck.Monadic (monadicIO, run, assert) import Test.MockCat hiding (any)+import Test.Hspec (Spec, describe, it) ++ -- | Unary action so we can register a concrete expected argument & return value. --   We test that constructing (but not forcing) the call does not count. class Monad m => LazyUnaryAction m where@@ -45,3 +48,9 @@     v <- lazyUnaryAction 10   -- forcing the monadic action executes the mock     v `seq` pure ()           -- ensure result is evaluated (WHNF for Int)   assert True++spec :: Spec+spec = do+    describe "Property Lazy Evaluation" $ do+      it "unforced stub action is not counted" $ property prop_lazy_unforced_not_counted+      it "forced stub action is counted" $ property prop_lazy_forced_counted
test/Property/OrderProps.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-hpc #-} module Property.OrderProps-  ( prop_inorder_succeeds+  ( spec+  , prop_inorder_succeeds   , prop_adjacent_swap_fails   , prop_partial_order_subset_succeeds   , prop_partial_order_reversed_pair_fails@@ -12,31 +13,48 @@ import Control.Exception (try, SomeException) import Data.Maybe (listToMaybe) import Data.List (nub)-import Test.MockCat (shouldBeCalled, inOrderWith, inPartialOrderWith, param)+-- import Test.MockCat (shouldBeCalled, inOrderWith, inPartialOrderWith, param)+import Test.MockCat import Property.Generators+import Control.Monad.IO.Class (liftIO)+import Test.Hspec (Spec, describe, it) +spec :: Spec+spec = do+    describe "Property Order / PartialOrder" $ do+      it "in-order script succeeds" $ property prop_inorder_succeeds+      it "adjacent swap fails order verification" $ property prop_adjacent_swap_fails+      it "subset partial order succeeds" $ property prop_partial_order_subset_succeeds+      it "reversed pair fails partial order" $ property prop_partial_order_reversed_pair_fails+ -- | Property: executing a non-empty script yields exact order success. prop_inorder_succeeds :: Property prop_inorder_succeeds = forAll scriptGen $ \scr@(Script xs) -> not (null xs) ==> monadicIO $ do-  run resetMockHistory-  f <- run $ buildUnaryMock scr-  run $ runScript f scr-  run $ f `shouldBeCalled` inOrderWith (param <$> xs)+  -- Use withMock for safe verification+  run $ withMock $ do+     -- Expect calls in exact order+     f <- mock (cases [ param a ~> True | a <- xs ])+            `expects` calledInOrder xs+     liftIO $ runScript f scr   assert True  -- | Property: a single adjacent swap causes order verification failure. prop_adjacent_swap_fails :: Property prop_adjacent_swap_fails = forAll scriptGen $ \(Script xs) -> length xs >= 2 ==> monadicIO $ do-  run resetMockHistory   let distinct = nub xs   if length distinct /= length xs     then assert True  -- discard scripts with duplicates; they can mask order errors     else do       i <- run $ generate $ chooseInt (0, length xs - 2)       let swapped = take i xs ++ [xs !! (i+1), xs !! i] ++ drop (i+2) xs-      f <- run $ buildUnaryMock (Script xs)-      run $ runScript f (Script xs)-      e <- run $ (try (f `shouldBeCalled` inOrderWith (param <$> swapped)) :: IO (Either SomeException ()))+      +      -- Verify that expectation fails for swapped order+      e <- run ((try $ withMock $ do+          f <- mock (cases [ param a ~> True | a <- xs ])+                 `expects` calledInOrder swapped+          liftIO $ runScript f (Script xs)+          ) :: IO (Either SomeException ()))+             case e of         Left _ -> assert True         Right _ -> assert False@@ -52,17 +70,16 @@ -- | Property: any non-empty subsequence (order-preserving) passes partial order check. prop_partial_order_subset_succeeds :: Property prop_partial_order_subset_succeeds = forAll scriptGen $ \scr@(Script xs) -> not (null xs) ==> monadicIO $ do-  run resetMockHistory   subset <- run $ generate $ chooseSubsequence xs-  f <- run $ buildUnaryMock scr-  run $ runScript f scr-  run $ f `shouldBeCalled` inPartialOrderWith (param <$> subset)+  run $ withMock $ do+    f <- mock (cases [ param a ~> True | a <- xs ])+           `expects` calledInSequence subset+    liftIO $ runScript f scr   assert True  -- | Property: selecting two distinct values and reversing them causes partial order failure. prop_partial_order_reversed_pair_fails :: Property prop_partial_order_reversed_pair_fails = forAll scriptGen $ \scr@(Script xs) -> length xs >= 2 ==> monadicIO $ do-  run resetMockHistory   if length (nub xs) /= length xs     then assert True -- discard non-unique scripts to avoid accidental subsequences     else do@@ -70,10 +87,14 @@       case listToMaybe pairs of         Nothing -> assert True         Just (i,j) -> do-          f <- run $ buildUnaryMock scr-          run $ runScript f scr           let reversed = [xs!!j, xs!!i]-          e <- run $ (try (f `shouldBeCalled` inPartialOrderWith (param <$> reversed)) :: IO (Either SomeException ()))+          -- Verify failure+          e <- run ((try $ withMock $ do+              f <- mock (cases [ param a ~> True | a <- xs ])+                     `expects` calledInSequence reversed+              liftIO $ runScript f scr+              ) :: IO (Either SomeException ()))+                     case e of             Left _ -> assert True             Right _ -> assert False
test/Property/ReinforcementProps.hs view
@@ -10,8 +10,7 @@ {-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-} {-# OPTIONS_GHC -fno-hpc #-} {-# OPTIONS_GHC -Wno-deprecations #-}-module Property.ReinforcementProps-  where+module Property.ReinforcementProps (spec, prop_predicate_negative_not_counted, prop_lazy_partial_force_concurrency, prop_partial_order_interleaved_duplicates) where  import Test.QuickCheck import Test.QuickCheck.Monadic (monadicIO, run, assert)@@ -20,29 +19,36 @@ import UnliftIO (withRunInIO) import Test.MockCat hiding (any) import Property.Generators (resetMockHistory)+import Control.Monad.IO.Class (liftIO)+import Test.Hspec (Spec, describe, it) ++ -------------------------------------------------------------------------------- -- 1. Predicate negative: failing inputs raise & are not counted --------------------------------------------------------------------------------  prop_predicate_negative_not_counted :: Property prop_predicate_negative_not_counted = forAll genVals $ \xs -> monadicIO $ do-  run resetMockHistory-  f <- run $ mock (expect even "even" ~> True)-  outcomes <- run $ mapM (\x -> (try (evaluate (f x)) :: IO (Either SomeException Bool))) xs   let evens = length (filter even xs)-      successes = length [ () | (x, Right _) <- zip xs outcomes, even x ]-      oddSuccesses = [ x | (x, Right _) <- zip xs outcomes, odd x ]-      failures = length [ () | (_, Left _) <- zip xs outcomes ]-      odds = length (filter odd xs)-  -- No odd value should succeed-  assert (null oddSuccesses)-  -- Failures count equals number of odd inputs-  assert (failures == odds)-  -- Successes count equals even inputs-  assert (successes == evens)-  -- Called count equals successes (only even)-  run $ f `shouldBeCalled` times evens+  run $ withMock $ do+    f <- mock (expect even "even" ~> True)+          `expects` called (times evens)+          +    outcomes <- liftIO $ mapM (\x -> (try (evaluate (f x)) :: IO (Either SomeException Bool))) xs+    +    {- Analysis (optional checks) -}+    liftIO $ do+      let successes = length [ () | (x, Right _) <- zip xs outcomes, even x ]+          oddSuccesses = [ x | (x, Right _) <- zip xs outcomes, odd x ]+          failures = length [ () | (_, Left _) <- zip xs outcomes ]+          odds = length (filter odd xs)+      -- No odd value should succeed+      if not (null oddSuccesses) then error "oddSuccesses not null" else pure ()+      -- Failures count equals number of odd inputs+      if failures /= odds then error "failures /= odds" else pure ()+      -- Successes count equals even inputs+      if successes /= evens then error "successes /= evens" else pure ()   assert True   where     genVals = resize 60 $ listOf (arbitrary :: Gen Int)@@ -88,14 +94,22 @@  prop_partial_order_interleaved_duplicates :: Property prop_partial_order_interleaved_duplicates = forAll genPair $ \(a,b) -> a /= b ==> monadicIO $ do-  run resetMockHistory   -- Pattern a a b : [a,b] subsequence succeeds, [b,a] fails.-  f <- run $ mock $ cases [ param a ~> True-                                , param a ~> True-                                , param b ~> True ]-  run $ f a `seq` f a `seq` f b `seq` pure ()-  run $ f `shouldBeCalled` inPartialOrderWith [param a, param b]-  e <- run $ (try (f `shouldBeCalled` inPartialOrderWith [param b, param a]) :: IO (Either SomeException ()))+  run $ withMock $ do+      f <- mock (cases [ param a ~> True+                                    , param a ~> True+                                    , param b ~> True ])+             `expects` calledInSequence [a, b]+      liftIO $ f a `seq` f a `seq` f b `seq` pure ()++  e <- run ((try $ withMock $ do+      f <- mock (cases [ param a ~> True+                                    , param a ~> True+                                    , param b ~> True ])+             `expects` calledInSequence [b, a]+      liftIO $ f a `seq` f a `seq` f b `seq` pure ()+      ) :: IO (Either SomeException ()))+   case e of     Left _  -> assert True     Right _ -> assert False@@ -105,3 +119,10 @@       a <- arbitrary :: Gen Int       b <- arbitrary :: Gen Int       pure (a,b)++spec :: Spec+spec = do+    describe "Property Reinforcement (Negative predicate / Partial force / Interleave)" $ do+      it "predicate negative not counted" $ property prop_predicate_negative_not_counted+      it "lazy partial force in concurrency counts only forced" $ property prop_lazy_partial_force_concurrency+      it "interleaved duplicate partial order semantics" $ property prop_partial_order_interleaved_duplicates
test/Property/ScriptProps.hs view
@@ -1,19 +1,29 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-hpc #-} module Property.ScriptProps-  ( prop_script_count_matches+  ( spec+  , prop_script_count_matches   ) where  import Test.QuickCheck import Test.QuickCheck.Monadic (monadicIO, run, assert)-import Test.MockCat (shouldBeCalled, times)+import Test.MockCat import Property.Generators+import Control.Monad.IO.Class (liftIO)+import Test.Hspec (Spec, describe, it) +spec :: Spec+spec = do+    describe "Property Script Generator" $ do+      it "script count matches recorded calls" $ property prop_script_count_matches+ -- | Property: executing a generated script produces exactly that many recorded calls. prop_script_count_matches :: Property prop_script_count_matches = forAll scriptGen $ \scr@(Script xs) -> monadicIO $ do-  run resetMockHistory-  f <- run $ buildUnaryMock scr-  run $ runScript f scr-  run $ f `shouldBeCalled` times (length xs)+  -- Use withMock for safe verification+  run $ withMock $ do+    f <- mock (cases [ param a ~> True | a <- xs ])+          `expects` called (times (length xs))+    +    liftIO $ runScript f scr   assert True
− test/ReadmeVerifySpec.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# OPTIONS_GHC -Wno-missing-export-lists #-}-{-# OPTIONS_GHC -Wno-unused-do-bind #-}-{-# OPTIONS_GHC -fno-hpc #-}-{-# OPTIONS_GHC -Wno-deprecations #-}-module ReadmeVerifySpec where--import Test.Hspec-import Test.MockCat hiding (when)-import Prelude hiding (readFile, writeFile, any)-import Control.Monad (when)----- ---------------------------------------------------------- 2. Typeclass Mocking Definitions--- --------------------------------------------------------class Monad m => FileSystem m where-  readFile :: FilePath -> m String-  writeFile :: FilePath -> String -> m ()---- [Strict Mode]-makeMock [t|FileSystem|]---- Dummy program for Typeclass Mocking-myProgram :: FileSystem m => FilePath -> m ()-myProgram path = do-  content <- readFile path-  when (content == "debug=true") $ writeFile "log.txt" "start"---spec :: Spec-spec = do-  describe "Quick Start" do-    it "Quick Start Demo" do-      -- 1. Create a mock-      f <- mock $ "Hello" ~> (42 :: Int)--      -- 2. Use it-      let result = f "Hello"-      result `shouldBe` 42--      {- -        Note: The README includes:-        -- 3. Verify-        f `shouldBeCalled` "Hello"--        Without OverloadedStrings, "Hello" is String.-        f :: String -> Int.-        shouldBeCalled expects Matcher (String -> Int).-        "Hello" works as matcher implies `Param String`.-      -}-      f `shouldBeCalled` "Hello"--  describe "Function Mocking" do-    it "Basic" do-      f <- mock $ "a" ~> "b" ~> True-      let r = f "a" "b"-      r `shouldBe` True--    it "Condition" do-      f <- mock $ expect (> 5) "> 5" ~> True-      let r = f (6 :: Int)-      r `shouldBe` True--    it "Any" do-      f <- mock $ any ~> True-      let r = f ("something" :: String)-      r `shouldBe` True--  describe "Typeclass Mocking" do-    it "filesystem test (Strict)" do-      result <- runMockT do-        _readFile $ "config.txt" ~> pure @IO "debug=true"-        _writeFile $ "log.txt" ~> "start" ~> pure @IO ()-        myProgram "config.txt"-      result `shouldBe` ()
test/Spec.hs view
@@ -1,13 +1,14 @@-import Test.Hspec (hspec, describe, it)+{-# LANGUAGE BlockArguments #-}+import Test.Hspec (hspec, describe, it, pendingWith)+import Test.MockCat.TestHelper (checkStrictVerificationWorks)+ import Test.MockCat.MockSpec as Mock import Test.MockCat.DeriveSpec as Derive import Test.MockCat.ConsSpec as Cons import Test.MockCat.ParamSpec as Param-import Test.MockCat.AssociationListSpec as AssociationList import Test.MockCat.ExampleSpec as Example import Test.MockCat.TypeClassMinimalSpec as TypeClassMinimal import Test.MockCat.TypeClassSpec as TypeClass- import Test.MockCat.TypeClassTHSpec as TypeClassTH import Test.MockCat.PartialMockSpec as PartialMock import Test.MockCat.PartialMockTHSpec as PartialMockTH@@ -26,12 +27,10 @@ import Test.MockCat.ShouldBeCalledErrorDiffSpec as ShouldBeCalledErrorDiff import Test.MockCat.WithMockErrorDiffSpec as WithMockErrorDiff import Test.MockCat.THCompareSpec as THCompare-import ReadmeVerifySpec as ReadmeVerify-import qualified Test.MockCat.HPCFallbackSpec as HPCFallback import qualified Test.MockCat.MultipleMocksSpec as MultipleMocks import qualified Test.MockCat.TypeFamilySpec as TypeFamily import Test.MockCat.UnsafeCheck ()-import Test.QuickCheck (property)+import Test.QuickCheck () import qualified Property.ConcurrentCountProp as ConcurrencyProp import qualified Property.LazyEvalProp as LazyEvalProp import qualified Property.ScriptProps as ScriptProps@@ -41,14 +40,27 @@ import qualified Property.ParamSpecNormalizeProp as ParamSpecNormalizeProp import qualified Property.ParamSpecMergeProp as ParamSpecMergeProp import qualified Property.ParamSpecRangeMergeRandomProp as ParamSpecRangeMergeRandomProp+import qualified Test.MockCat.HpcSpec as HpcSpec+import qualified Test.MockCat.Readme.ReadmeSpec as Readme  main :: IO ()-main = hspec $ do+main = do+  strictWorks <- checkStrictVerificationWorks+  -- If strict verification works (Standard), verify everything.+  -- If it fails (HPC enabled), skip standard verification tests that would falsely fail.+  let conditionalSpec = if strictWorks +        then id +        else \_ -> describe "Standard Tests (Skipped due to HPC/Coverage detection)" $ +                        it "All standard verification tests skipped" $ +                          pendingWith "Strict Verification disabled (HPC active)"++  hspec $ do+    -- ALWAYS RUN: HpcSpec & Safe Tests (Cons, Param, Mock, Stub, Expects-based, etc.)+    HpcSpec.spec     Cons.spec     Param.spec     Derive.spec     Mock.spec-    AssociationList.spec     Example.spec     TypeClassMinimal.spec     TypeClass.spec@@ -58,43 +70,33 @@     Concurrency.spec     Stub.spec     MockTSpec.spec-    Registry.spec     THCompare.spec     THTypeUtils.spec     THContextBuilder.spec     THClassAnalysis.spec     THFunctionBuilder.spec-    ShouldBeCalled.spec-    ShouldBeCalledMockM.spec     WithMock.spec-    WithMockIO.spec-    ShouldBeCalledErrorDiff.spec     WithMockErrorDiff.spec-    ReadmeVerify.spec-    HPCFallback.spec+    WithMockIO.spec     MultipleMocks.spec     TypeFamily.spec-    describe "Property Concurrency" $ do-      it "total apply count is preserved across threads" $ property ConcurrencyProp.prop_concurrent_total_apply_count-    describe "Property Lazy Evaluation" $ do-      it "unforced stub action is not counted" $ property LazyEvalProp.prop_lazy_unforced_not_counted-      it "forced stub action is counted" $ property LazyEvalProp.prop_lazy_forced_counted-    describe "Property Script Generator" $ do-      it "script count matches recorded calls" $ property ScriptProps.prop_script_count_matches-    describe "Property Order / PartialOrder" $ do-      it "in-order script succeeds" $ property OrderProps.prop_inorder_succeeds-      it "adjacent swap fails order verification" $ property OrderProps.prop_adjacent_swap_fails-      it "subset partial order succeeds" $ property OrderProps.prop_partial_order_subset_succeeds-      it "reversed pair fails partial order" $ property OrderProps.prop_partial_order_reversed_pair_fails-    describe "Property Additional (Predicate / Multi-case / Isolation / Duplicates)" $ do-      it "predicate param counts match" $ property AdditionalProps.prop_predicate_param_match_counts-      it "multi-case progression saturates" $ property AdditionalProps.prop_multicase_progression-      it "runMockT isolation of counts" $ property AdditionalProps.prop_runMockT_isolation-      it "partial order with duplicates behaves" $ property AdditionalProps.prop_partial_order_duplicates-    describe "Property Reinforcement (Negative predicate / Partial force / Interleave)" $ do-      it "predicate negative not counted" $ property ReinforcementProps.prop_predicate_negative_not_counted-      it "lazy partial force in concurrency counts only forced" $ property ReinforcementProps.prop_lazy_partial_force_concurrency-      it "interleaved duplicate partial order semantics" $ property ReinforcementProps.prop_partial_order_interleaved_duplicates++    ConcurrencyProp.spec+    LazyEvalProp.spec+    ScriptProps.spec+    OrderProps.spec+    AdditionalProps.spec+    ReinforcementProps.spec+         ParamSpecNormalizeProp.spec     ParamSpecMergeProp.spec     ParamSpecRangeMergeRandomProp.spec++    -- CONDITIONAL RUN: Standard Tests using 'shouldBeCalled' (Unsafe under HPC)+    conditionalSpec $ do+      Registry.spec+      ShouldBeCalled.spec+      ShouldBeCalledMockM.spec+      ShouldBeCalledErrorDiff.spec++    Readme.spec strictWorks
− test/Test/MockCat/AssociationListSpec.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# OPTIONS_GHC -Wno-type-defaults #-}-module Test.MockCat.AssociationListSpec (spec) where--import Test.Hspec-import Test.MockCat.AssociationList (AssociationList, empty, insert, update, (!?), member)--spec :: Spec-spec = do-  it "empty" do-    let e = empty :: AssociationList String Int-    e `shouldBe` []--  describe "insert" do-    it "not exist" do-      let l = empty :: AssociationList String Int-      insert "key" 100 l `shouldBe` [("key", 100)]--    it "exist" do-      let l = insert "key" 100 empty :: AssociationList String Int-      insert "key" 120 l `shouldBe` [("key", 120)]-  -  describe "update" do-    it "not exist" do-      let l = empty :: AssociationList String Int-      update (+ 20) "key" l `shouldBe` []--    it "exist" do-      let l = insert "key" 100 empty :: AssociationList String Int-      update (+ 20) "key" l `shouldBe` [("key", 120)]-  -  describe "lookup" do-    it "not exist" do-      let l = empty :: AssociationList String Int-      l !? "key" `shouldBe` Nothing--    it "exist" do-      let l = insert "key" 100 empty :: AssociationList String Int-      l !? "key" `shouldBe` Just 100--  describe "member" do-    it "not exist" do-      let l = empty :: AssociationList String Int-      member "key" l `shouldBe` False--    it "exist" do-      let l = insert "key" 100 empty :: AssociationList String Int-      member "key" l `shouldBe` True
− test/Test/MockCat/HPCFallbackSpec.hs
@@ -1,59 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-do-bind #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Test.MockCat.HPCFallbackSpec (spec) where--import Test.Hspec-import Test.MockCat-import Control.Exception (evaluate)-import Prelude hiding (readFile, writeFile)--spec :: Spec-spec = do-  describe "HPC Fallback Scenarios (Multiple Mocks)" do-    -    it "distinguishes mocks of different types" do-      -- Case 1: Different types should be distinguishable by type check in fallback-      f <- mock $ "a" ~> (1 :: Int)-      g <- mock ((1 :: Int) ~> "b")-      -      -- Verify f (String -> Int)-      f "a" `shouldBe` 1-      f `shouldBeCalled` "a"-      -      -- Verify g (Int -> String)-      g 1 `shouldBe` "b"-      g `shouldBeCalled` (1 :: Int)--    it "behavior with multiple mocks of SAME type (Unnamed)" do-      -- Case 2: Same type mocks. -      -- In HPC environment (StableName broken), fallback picks based on history.-      -- Expected behavior: It might pick the wrong recorder if implementation blindly takes the first/last match.-      -      f1 <- mock $ "1" ~> (1 :: Int)-      f2 <- mock $ "2" ~> (2 :: Int)--      -- Execute both-      evaluate $ f1 "1"-      evaluate $ f2 "2"--      -- Verify f1. -      -- If fallback picks f2 (latest or first), verification might fail or see wrong calls.-      f1 `shouldBeCalled` "1" -      -      -- Verify f2-      f2 `shouldBeCalled` "2"--    it "behavior with multiple mocks of SAME type (Named)" do-      -- Case 3: Named mocks of same type.-      -- Verification should ideally use the name to disambiguate.-      -      fA <- mock (label "mockA") $ "A" ~> (1 :: Int)-      fB <- mock (label "mockB") $ "B" ~> (2 :: Int)--      evaluate $ fA "A"-      evaluate $ fB "B"--      fA `shouldBeCalled` "A"-      fB `shouldBeCalled` "B"
+ test/Test/MockCat/HpcSpec.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+module Test.MockCat.HpcSpec (spec) where++import Test.Hspec+import Test.MockCat+import Control.Exception (ErrorCall (..))+import Control.Monad.IO.Class (liftIO)++spec :: Spec+spec = do+  describe "[HPC] Strict Verification Compatibility" $ do+    it "expects works correctly" $ do+      withMock $ do+        m <- mock ("arg" ~> "result") `expects` (called (times 1))+        let r = m "arg"+        liftIO $ r `shouldBe` "result"++    it "shouldBeCalled fails with specific error message when StableName lookup fails" $ do+      -- In HPC mode (or if wrapped), StableName lookup fails.+      -- To guarantee failure regardless of HPC trigger, we use a Wrapper.+      -- This simulates the condition we want to test: "Mock not recognized".+      +      m :: String -> String <- mock $ "arg" ~> "result"+      +      -- Wrap the mock. verifying 'wrapped' directly should fail+      -- because 'wrapped' is not registered in the Core registry.+      let wrapped x = m x+      +      -- Execute verification on the WRAPPED (unregistered) function.+      (wrapped `shouldBeCalled` "arg") `shouldThrow` \(ErrorCall msg) ->+        let expected = +              "Error: Mock verification failed.\n" +++              "\n" +++              "The function passed to 'shouldBeCalled' could not be recognized as a registered mock.\n" +++              "\n" +++              "Possible causes:\n" +++              "  1. You passed a raw wrapper function around the mock.\n" +++              "  2. You passed a normal (non-mock) function.\n" +++              "  3. HPC (Coverage) is enabled. (HPC instrumentation changes function identity)\n" +++              "\n" +++              "Solution:\n" +++              "  - If you are using HPC or Wrappers, please use 'expects' or 'withMock' style.\n" +++              "    'expects' is robust against HPC and wrappers as it does not rely on function identity lookup.\n" +++              "  - Ensure you are passing the mock function directly created by 'mock'."+        in msg == expected
test/Test/MockCat/Internal/MockRegistrySpec.hs view
@@ -6,7 +6,6 @@ import Test.MockCat.Internal.MockRegistry import Control.Concurrent.STM (newTVarIO, readTVarIO) import Data.Dynamic (fromDynamic)-import Test.MockCat.AssociationList (empty) import Test.MockCat.Internal.Types (InvocationRecorder(..), FunctionNature(..), InvocationRecord(..))  spec :: Spec@@ -14,7 +13,7 @@   describe "MockRegistry" do     it "register and lookup" do       let f = (+ 1) :: Int -> Int-      ref <- newTVarIO InvocationRecord { invocations = [] :: [Int], invocationCounts = empty }+      ref <- newTVarIO InvocationRecord { invocations = [] :: [Int], invocationCounts = [] }       attachVerifierToFn f (Just "name", InvocationRecorder ref ParametricFunction)       results <- lookupVerifierForFn f       case results of@@ -23,7 +22,7 @@           case (fromDynamic dyn :: Maybe (InvocationRecorder Int)) of             Just (InvocationRecorder vref _) -> do               r <- readTVarIO vref-              r `shouldBe` InvocationRecord { invocations = [] :: [Int], invocationCounts = empty }+              r `shouldBe` InvocationRecord { invocations = [] :: [Int], invocationCounts = [] }             Nothing -> expectationFailure "payload dynamic mismatch"         _ -> expectationFailure "lookupStubFn returned unexpected number of results" 
test/Test/MockCat/PartialMockSpec.hs view
@@ -16,7 +16,7 @@ import Data.Text (Text) import Test.Hspec (Spec) import Data.List (find)-import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.IO.Class (MonadIO) import Control.Monad.Trans.Class (lift) import Test.MockCat import Test.MockCat.SharedSpecDefs@@ -28,19 +28,8 @@ import Unsafe.Coerce (unsafeCoerce) import qualified Test.MockCat.Verify as Verify -ensureVerifiable ::-  ( MonadIO m-  , Verify.ResolvableMock target-  ) =>-  target ->-  m ()-ensureVerifiable target =-  liftIO $ do-    candidates <- Verify.resolveForVerification target-    case candidates of-      [] -> Verify.verificationFailure-      _ -> pure () + instance UserInputGetter IO where   getInput = getLine   toUserInput "" = pure Nothing@@ -113,7 +102,7 @@   MockT m (FilePath -> Text) _readFile p = MockT $ do   mockInstance <- unMockT $ mock (label "readFile") p-  ensureVerifiable mockInstance+   addDefinition (Definition (Proxy :: Proxy "readFile") mockInstance NoVerification)   pure mockInstance @@ -125,7 +114,7 @@   MockT m (MockResult (Verify.ResolvableParamsOf (FilePath -> Text -> ()))) _writeFile p = MockT $ do   mockInstance <- unMockT $ mock (label "writeFile") p-  ensureVerifiable mockInstance+   addDefinition (Definition (Proxy :: Proxy "writeFile") mockInstance NoVerification)   pure (MockResult ()) @@ -138,7 +127,7 @@   MockT m (MockResult (Verify.ResolvableParamsOf String)) _getInput value = MockT $ do   mockInstance <- unMockT $ mock (label "getInput") value-  ensureVerifiable mockInstance+   addDefinition (Definition (Proxy :: Proxy "getInput") mockInstance NoVerification)   pure (MockResult ()) @@ -152,7 +141,7 @@   MockT m (String -> m (Maybe UserInput)) _toUserInput p = MockT $ do   mockInstance <- unMockT $ mock (label "toUserInput") p-  ensureVerifiable mockInstance+   addDefinition (Definition (Proxy :: Proxy "toUserInput") mockInstance NoVerification)   pure mockInstance @@ -163,7 +152,7 @@   MockT IO (MockResult (Verify.ResolvableParamsOf (String -> IO Int))) _getByPartial p = MockT $ do   mockInstance <- unMockT $ mock (label "getBy") p-  ensureVerifiable mockInstance+   addDefinition (Definition (Proxy :: Proxy "getBy") mockInstance NoVerification)   pure (MockResult ()) @@ -174,7 +163,7 @@   MockT IO (MockResult (Verify.ResolvableParamsOf (String -> IO ()))) _echoPartial p = MockT $ do   mockInstance <- unMockT $ mock (label "echo") p-  ensureVerifiable mockInstance+   addDefinition (Definition (Proxy :: Proxy "echo") mockInstance NoVerification)   pure (MockResult ()) @@ -213,7 +202,7 @@ _findIds p = MockT $ do   mockInstance <- unMockT $ mock (label "_findIds") p -  ensureVerifiable mockInstance+   addDefinition     ( Definition         (Proxy :: Proxy "_findIds")@@ -230,7 +219,7 @@   MockT m (MockResult (Verify.ResolvableParamsOf (Int -> String))) _findById p = MockT $ do   mockInstance <- unMockT $ mock (label "_findById") p-  ensureVerifiable mockInstance+   addDefinition     ( Definition         (Proxy :: Proxy "_findById")@@ -246,7 +235,7 @@   MockT IO (MockResult (Verify.ResolvableParamsOf (Int -> IO String))) _findByIdNI p = MockT $ do   mockInstance <- unMockT $ mock (label "_findByIdNI") p-  ensureVerifiable mockInstance+   addDefinition     ( Definition         (Proxy :: Proxy "_findByIdNI")
+ test/Test/MockCat/Readme/MakeAutoLiftMockSpec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+module Test.MockCat.Readme.MakeAutoLiftMockSpec (spec) where++import Test.Hspec+import Test.MockCat++class Monad m => FileSystem m where+  readFile :: FilePath -> m String+  writeFile :: FilePath -> String -> m ()++-- [Auto-Lift Mode] 利便性重視のモード。+-- 純粋な値を自動的にモナド(m String など)に包んで返します。+makeAutoLiftMock [t|FileSystem|]++-- Dummy program for makeMock example+myProgram :: FileSystem m => FilePath -> m ()+myProgram _ = pure ()++spec :: Spec+spec = do+  it "filesystem test" $ do+    result <- runMockT $ do+      -- [Auto-Lift Mode] (makeAutoLiftMock 使用時): 値は自動的に包まれる (便利)+      _readFile $ "config.txt" ~> "debug=true"+      _writeFile $ "log.txt" ~> "start" ~> ()++      -- テスト対象コードの実行(モックが注入される)+      myProgram "config.txt"+    +    result `shouldBe` ()
+ test/Test/MockCat/Readme/MakeMockSpec.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+module Test.MockCat.Readme.MakeMockSpec (spec) where++import Test.Hspec+import Test.MockCat++class Monad m => FileSystem m where+  readFile :: FilePath -> m String+  writeFile :: FilePath -> String -> m ()++-- [Strict Mode] デフォルトの動作。「mock」関数と挙動が一致します。+-- 戻り値の型が `m a` の場合、スタブ定義の右辺には `m a` 型の値(例: `pure @IO "value"`, `throwIO Error`)を記述する必要があります。+-- Haskell の型システムに対して正直で、明示的な記述を好む場合に推奨されます。+makeMock [t|FileSystem|]++-- Dummy program for makeMock example+myProgram :: FileSystem m => FilePath -> m ()+myProgram _ = pure ()++spec :: Spec+spec = do+  it "filesystem test" $ do+    result <- runMockT $ do+      -- [Strict Mode] (makeMock 使用時): 明示的に pure で包む+      _readFile $ "config.txt" ~> pure @IO "debug=true"+      _writeFile $ "log.txt" ~> "start" ~> pure @IO ()++      -- テスト対象コードの実行(モックが注入される)+      myProgram "config.txt"+    +    result `shouldBe` ()
+ test/Test/MockCat/Readme/MatcherSpec.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE TypeApplications #-}+module Test.MockCat.Readme.MatcherSpec (spec) where++import Test.Hspec+import Test.MockCat+import Prelude hiding (any)+import Data.List (isPrefixOf)++spec :: Spec+spec = do+  it "Matcher Examples" $ do+    -- 任意の文字列 (param any)+    f <- mock (any @String ~> True)+    f "foo" `shouldBe` True++    -- 条件式 (when)+    g <- mock (when (> (5 :: Int)) "> 5" ~> True)+    g 6 `shouldBe` True+  +  it "When Example" $ do+    -- 引数が "error" で始まる場合のみ False を返す+    f <- mock $ do+      onCase $ when (\s -> "error" `isPrefixOf` s) "start with error" ~> False+      onCase $ any ~> True++    f "error message" `shouldBe` False+    f "success" `shouldBe` True+    f "other" `shouldBe` True
+ test/Test/MockCat/Readme/QuickStartDemoSpec.hs view
@@ -0,0 +1,18 @@+module Test.MockCat.Readme.QuickStartDemoSpec (spec) where++import Test.Hspec+import Test.MockCat++spec :: Spec+spec = do+  it "Quick Start Demo" $ do+    withMockIO $ do+      -- 1. モックを作成 ("Hello" を受け取ったら 42 を返す)+      --    同時に「1回呼ばれるはずだ」と期待を宣言+      f <- mock ("Hello" ~> (42 :: Int))+        `expects` called once++      -- 2. f "Hello" を呼び出し、42 を得る+      f "Hello" `shouldBe` 42++      -- 3. スコープを抜ける際、宣言した期待に対する検証が自動で行われる
+ test/Test/MockCat/Readme/ReadmeSpec.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE BlockArguments #-}+module Test.MockCat.Readme.ReadmeSpec (spec) where++import Test.Hspec+import qualified Test.MockCat.Readme.QuickStartDemoSpec as QuickStartDemo+import qualified Test.MockCat.Readme.WithMockSpec as WithMock+import qualified Test.MockCat.Readme.WithMockIOSpec as WithMockIO+import qualified Test.MockCat.Readme.MakeMockSpec as MakeMock+import qualified Test.MockCat.Readme.MakeAutoLiftMockSpec as MakeAutoLiftMock+import qualified Test.MockCat.Readme.ShouldBeCalledSpec as ShouldBeCalled+import qualified Test.MockCat.Readme.MatcherSpec as Matcher+import Control.Monad (when)++spec :: Bool -> Spec+spec strictWorks = do+  describe "Readme Tests" do+    QuickStartDemo.spec+    WithMock.spec+    WithMockIO.spec+    MakeMock.spec+    MakeAutoLiftMock.spec+    Matcher.spec+    when strictWorks $ do+      ShouldBeCalled.spec
+ test/Test/MockCat/Readme/ShouldBeCalledSpec.hs view
@@ -0,0 +1,16 @@+module Test.MockCat.Readme.ShouldBeCalledSpec (spec) where++import Test.Hspec+import Test.MockCat++spec :: Spec+spec = do+  it "Function Mocking" $ do+    -- "Hello" に対して 1 を返す関数を定義 (expects は書かない)+    f <- mock ("Hello" ~> True)+    +    -- 実行+    f "Hello" `shouldBe` True++    -- 事後検証 (shouldBeCalled)+    f `shouldBeCalled` "Hello"
+ test/Test/MockCat/Readme/WithMockIOSpec.hs view
@@ -0,0 +1,15 @@+module Test.MockCat.Readme.WithMockIOSpec (spec) where ++import Test.Hspec+import Test.MockCat++spec :: Spec+spec = do+  it "User Guide (withMockIO)" $ do+    withMockIO $ do+      f <- mock ("Hello" ~> True)+        `expects` called once++      let result = f "Hello"++      result `shouldBe` True
+ test/Test/MockCat/Readme/WithMockSpec.hs view
@@ -0,0 +1,19 @@+module Test.MockCat.Readme.WithMockSpec (spec) where ++import Test.Hspec+import Test.MockCat+import Control.Monad.IO.Class (MonadIO(liftIO))++spec :: Spec+spec = do+  it "User Guide (withMock)" $ do+    withMock $ do+      -- "Hello" に対して True を返すモックを定義+      f <- mock ("Hello" ~> True)+        `expects` called once++      -- 実行+      let result = f "Hello"++      liftIO $ result `shouldBe` True+
+ test/Test/MockCat/TestHelper.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Test.MockCat.TestHelper (checkStrictVerificationWorks) where++import Test.MockCat+import Test.Hspec (shouldBe)+import Control.Exception (try, ErrorCall)++-- | Checks if Strict Verification is working (i.e. not interfered by HPC/Coverage).+-- Returns True if verification succeeds.+-- Returns False if verification fails (indicating HPC is likely enabled).+checkStrictVerificationWorks :: IO Bool+checkStrictVerificationWorks = do+  result <- try $ do+    m <- mock ("ping" ~> "pong")+    m "ping" `shouldBe` "pong"+    m `shouldBeCalled` "ping"+  +  case result of+    Right _ -> pure True+    Left (_ :: ErrorCall) -> pure False
test/Test/MockCat/WithMockIOSpec.hs view
@@ -15,42 +15,39 @@   describe "withMockIO" $ do     it "can run IO actions directly without liftIO" $ do       withMockIO $ do-        f <- mock $ "hello" ~> "world"+        f <- mock ("hello" ~> "world") `expects` called once         f "hello" `shouldBe` "world"-        f `shouldBeCalled` "hello"      it "cleans up context even if an exception occurs" $ do       -- Raise an exception in the first test       void $ try @ErrorCall $ withMockIO $ do-        void $ mock $ "a" ~> "b"+        _ <- mock ("a" ~> "b") `expects` called once         error "force fail"        -- Verify that no remnants (like expectations) from the previous test remain in the second test       withMockIO $ do-        f <- mock $ "x" ~> "y"+        f <- mock ("x" ~> "y") `expects` called once         f "x" `shouldBe` "y"-        -- Verify that the expectation "a" ~> "b" from the previous test is not verified here+        -- Verify that the expectation "a" ~> "b" from the previous test is not verified here (implicit)      it "supports nested withMockIO" $ do       withMockIO $ do-        f1 <- mock $ "outer" ~> "ok"+        f1 <- mock ("outer" ~> "ok") `expects` called once         withMockIO $ do-          f2 <- mock $ "inner" ~> "ok"+          f2 <- mock ("inner" ~> "ok") `expects` called once           f2 "inner" `shouldBe` "ok"-          f2 `shouldBeCalled` "inner"         f1 "outer" `shouldBe` "ok"-        f1 `shouldBeCalled` "outer"      it "isolates context between parent and child threads" $ do       mvar <- newEmptyMVar       withMockIO $ do-        f <- mock $ "parent" ~> "ok"+        f <- mock ("parent" ~> "ok") `expects` called once         void $ forkIO $ do+          -- This fails because child thread cannot find the parent's mock context           res <- try @SomeException (mock ("child" ~> "ok") `expects` called once)           putMVar mvar res          f "parent" `shouldBe` "ok"-        f `shouldBeCalled` "parent"        childRes <- takeMVar mvar       case childRes of