|
| 1 | +# Usage Rules for Mimic Library |
| 2 | + |
| 3 | +This document provides essential guidelines for coding agents when using the Mimic mocking library in Elixir projects. You should not mock any module that are part of the Elixir standard library nor the Otp library directly. |
| 4 | + |
| 5 | +## Critical Setup Requirements |
| 6 | + |
| 7 | +### **IMPORTANT: Module Preparation** |
| 8 | + |
| 9 | +- **MUST** call `Mimic.copy/2` for each module you want to mock in `test_helper.exs` |
| 10 | +- IMPORTANT: Always call Mimic.copy/2 with type_check: true. Never omit it. |
| 11 | +- **IMPORTANT**: Call `copy/2` BEFORE `ExUnit.start()` |
| 12 | +- Copying a module does NOT change its behavior until you stub/expect it |
| 13 | + |
| 14 | +```elixir |
| 15 | +# test_helper.exs |
| 16 | +Mimic.copy(Calculator, type_check: true) |
| 17 | +Mimic.copy(HTTPClient, type_check: true) |
| 18 | +ExUnit.start() |
| 19 | +``` |
| 20 | + |
| 21 | +### **IMPORTANT: Test Module Setup** |
| 22 | + |
| 23 | +- **MUST** use `use Mimic` or `use Mimic.DSL` in test modules |
| 24 | +- **IMPORTANT**: Include `:test` environment only in mix.exs dependency |
| 25 | + |
| 26 | +```elixir |
| 27 | +# In test files |
| 28 | +defmodule MyTest do |
| 29 | + use ExUnit.Case, async: true |
| 30 | + use Mimic # or use Mimic.DSL |
| 31 | + |
| 32 | + # tests here |
| 33 | +end |
| 34 | +``` |
| 35 | + |
| 36 | +## Core API Functions (Order by Importance) |
| 37 | + |
| 38 | +### 1. `expect/4` - Primary Testing Function |
| 39 | + |
| 40 | +**IMPORTANT**: Use for functions that MUST be called during test |
| 41 | + |
| 42 | +- Creates expectations that must be fulfilled or test fails |
| 43 | +- Works like a FIFO queue for multiple calls |
| 44 | +- Auto-verified at test end when using `use Mimic` |
| 45 | + |
| 46 | +```elixir |
| 47 | +# Single call expectation |
| 48 | +Calculator |
| 49 | +|> expect(:add, fn x, y -> x + y end) |
| 50 | + |
| 51 | +# Multiple calls expectation |
| 52 | +Calculator |
| 53 | +|> expect(:add, 3, fn x, y -> x + y end) |
| 54 | + |
| 55 | +# Chaining expectations (FIFO order) |
| 56 | +Calculator |
| 57 | +|> expect(:add, fn _, _ -> :first_call end) |
| 58 | +|> expect(:add, fn _, _ -> :second_call end) |
| 59 | +``` |
| 60 | + |
| 61 | +### 2. `stub/3` - Flexible Mock Replacement |
| 62 | + |
| 63 | +- Use for functions that MAY be called during test |
| 64 | +- No verification failure if not called |
| 65 | +- Can be called multiple times |
| 66 | + |
| 67 | +```elixir |
| 68 | +Calculator |
| 69 | +|> stub(:add, fn x, y -> x + y end) |
| 70 | +``` |
| 71 | + |
| 72 | +### 3. `stub/1` - Complete Module Stubbing |
| 73 | + |
| 74 | +- Stubs ALL public functions in module |
| 75 | +- Stubbed functions raise `UnexpectedCallError` when called |
| 76 | + |
| 77 | +```elixir |
| 78 | +stub(Calculator) # All functions will raise if called |
| 79 | +``` |
| 80 | + |
| 81 | +### 4. `reject/1` or `reject/3` - Forbidden Calls |
| 82 | + |
| 83 | +- Use to ensure functions are NOT called |
| 84 | +- Test fails if rejected function is called |
| 85 | + |
| 86 | +```elixir |
| 87 | +reject(&Calculator.dangerous_operation/1) |
| 88 | +# or |
| 89 | +reject(Calculator, :dangerous_operation, 1) |
| 90 | +``` |
| 91 | + |
| 92 | +## Mode Selection (Critical Decision) |
| 93 | + |
| 94 | +### **IMPORTANT: Choose Appropriate Mode** |
| 95 | + |
| 96 | +#### Private Mode (Default - Recommended) |
| 97 | + |
| 98 | +- Tests can run with `async: true` |
| 99 | +- Each process sees its own mocks |
| 100 | +- Use `allow/3` for multi-process scenarios |
| 101 | + |
| 102 | +#### Global Mode (Use Sparingly) |
| 103 | + |
| 104 | +- **IMPORTANT**: Use `setup :set_mimic_global` |
| 105 | +- **CRITICAL**: MUST use `async: false` in global mode |
| 106 | +- All processes see same mocks |
| 107 | +- Only global owner can create stubs/expectations |
| 108 | + |
| 109 | +```elixir |
| 110 | +# Private mode (preferred) |
| 111 | +setup :set_mimic_private |
| 112 | +setup :verify_on_exit! |
| 113 | + |
| 114 | +# Global mode (when needed) |
| 115 | +setup :set_mimic_global |
| 116 | +# Remember: async: false required |
| 117 | +``` |
| 118 | + |
| 119 | +## DSL Mode Alternative |
| 120 | + |
| 121 | +### **IMPORTANT: DSL Syntax** |
| 122 | + |
| 123 | +Use `Mimic.DSL` for more natural syntax: |
| 124 | + |
| 125 | +```elixir |
| 126 | +use Mimic.DSL |
| 127 | + |
| 128 | +test "DSL example" do |
| 129 | + stub Calculator.add(_x, _y), do: :stubbed |
| 130 | + expect Calculator.mult(x, y), do: x * y |
| 131 | + expect Calculator.add(x, y), num_calls: 2, do: x + y |
| 132 | +end |
| 133 | +``` |
| 134 | + |
| 135 | +## Multi-Process Coordination |
| 136 | + |
| 137 | +### Using `allow/3` (Private Mode) |
| 138 | + |
| 139 | +```elixir |
| 140 | +test "multi-process test" do |
| 141 | + Calculator |> expect(:add, fn x, y -> x + y end) |
| 142 | + |
| 143 | + parent_pid = self() |
| 144 | + |
| 145 | + spawn_link(fn -> |
| 146 | + Calculator |> allow(parent_pid, self()) |
| 147 | + assert Calculator.add(1, 2) == 3 |
| 148 | + end) |
| 149 | +end |
| 150 | +``` |
| 151 | + |
| 152 | +### **IMPORTANT**: Task Automatic Allowance |
| 153 | + |
| 154 | +- Tasks automatically inherit parent process mocks |
| 155 | +- No need to call `allow/3` for `Task.async` |
| 156 | + |
| 157 | +## Critical Don'ts |
| 158 | + |
| 159 | +### **IMPORTANT: Function Export Requirements** |
| 160 | + |
| 161 | +- Can ONLY mock publicly exported functions |
| 162 | +- **MUST** match exact arity |
| 163 | +- Will raise `ArgumentError` for non-existent functions |
| 164 | + |
| 165 | +### **IMPORTANT: Intra-Module Function Calls** |
| 166 | + |
| 167 | +- Mocking does NOT work for internal function calls within same module |
| 168 | +- Use fully qualified names (`Module.function`) instead of local calls |
| 169 | + |
| 170 | +### **IMPORTANT: Global Mode Restrictions** |
| 171 | + |
| 172 | +- Only global owner process can create stubs/expectations |
| 173 | +- Other processes will get `ArgumentError` |
| 174 | +- Cannot use `allow/3` in global mode |
| 175 | + |
| 176 | +## Advanced Features |
| 177 | + |
| 178 | +### Type Checking (Experimental) |
| 179 | + |
| 180 | +```elixir |
| 181 | +Mimic.copy(HTTPClient, type_check: true) |
| 182 | +``` |
| 183 | + |
| 184 | +### Calling Original Implementation |
| 185 | + |
| 186 | +```elixir |
| 187 | +call_original(Calculator, :add, [1, 2]) # Returns 3 |
| 188 | +``` |
| 189 | + |
| 190 | +### Tracking Function Calls |
| 191 | + |
| 192 | +```elixir |
| 193 | +stub(Calculator, :add, fn x, y -> x + y end) |
| 194 | +Calculator.add(1, 2) |
| 195 | +calls(&Calculator.add/2) # Returns [[1, 2]] |
| 196 | +``` |
| 197 | + |
| 198 | +### Fake Module Stubbing |
| 199 | + |
| 200 | +```elixir |
| 201 | +stub_with(Calculator, MockCalculator) # Replace all functions |
| 202 | +``` |
| 203 | + |
| 204 | +## Common Patterns |
| 205 | + |
| 206 | +### Setup Pattern |
| 207 | + |
| 208 | +```elixir |
| 209 | +setup do |
| 210 | + # Common setup |
| 211 | + %{user: %User{id: 1}} |
| 212 | +end |
| 213 | + |
| 214 | +setup :verify_on_exit! # Auto-verify expectations |
| 215 | +``` |
| 216 | + |
| 217 | +### Expectation Chaining |
| 218 | + |
| 219 | +```elixir |
| 220 | +Calculator |
| 221 | +|> stub(:add, fn _, _ -> :fallback end) # Fallback after expectations |
| 222 | +|> expect(:add, fn _, _ -> :first end) # First call |
| 223 | +|> expect(:add, fn _, _ -> :second end) # Second call |
| 224 | +# Third call returns :fallback |
| 225 | +``` |
| 226 | + |
| 227 | +## Error Handling |
| 228 | + |
| 229 | +### Common Errors and Solutions |
| 230 | + |
| 231 | +- `Module X has not been copied` → Add `Mimic.copy(X)` to test_helper.exs |
| 232 | +- `Function not defined for Module` → Check function name/arity |
| 233 | +- `Only the global owner is allowed` → Wrong process in global mode |
| 234 | +- `Allow must not be called when mode is global` → Don't mix allow with global mode |
| 235 | + |
| 236 | +**IMPORTANT**: Always verify exact function signatures and ensure modules are properly copied before mocking. |
0 commit comments