Created
March 23, 2026 10:08
-
-
Save styoe/38d5445cfa482024af533a4079b703c1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Short answer: no—unittest.mock.patch itself is not inherently thread-safe, and there are only limited ways to make things effectively safe depending on what you mean by “thread-safe”. | |
| Why patching is not thread-safe | |
| patch() works by mutating global/module-level state (replacing an attribute with a mock, then restoring it later) . | |
| That creates a few problems in multithreaded scenarios: | |
| Global mutation: While a patch is active, all threads see the patched object. | |
| Timing/race issues: If multiple threads start/stop patches concurrently, they can overwrite or restore each other’s changes unpredictably. | |
| Non-atomic behavior: There’s no built-in locking around patch application/restoration. | |
| So if one thread applies a patch while another thread is executing code that depends on that object, behavior becomes nondeterministic. | |
| Separate issue: mocks themselves are not thread-safe | |
| Even if patching were coordinated, Mock / MagicMock are not thread-safe for recording calls: | |
| Concurrent calls can race when updating mock_calls, call_args, etc. | |
| This can corrupt call history or produce inconsistent assertions. | |
| What can be done? | |
| There’s no single “make patch thread-safe” switch, but you have a few practical strategies: | |
| 1. Avoid patching across threads (best practice) | |
| Keep patching strictly inside a single thread (typically the test thread): | |
| with patch("module.func"): | |
| # spawn threads AFTER patch is applied | |
| ... | |
| Even then, all threads will see the patched object—so you must ensure that’s intended. | |
| 2. Apply patch before threads start | |
| If multiple threads need to see the same patched object: | |
| Start patch | |
| Then start threads | |
| Join threads | |
| Stop patch | |
| This avoids race conditions during patch/unpatch transitions. | |
| 3. Use dependency injection instead of patching | |
| This is the cleanest and truly thread-safe approach: | |
| def my_func(dep): | |
| return dep() | |
| # test | |
| my_func(mock_dep) | |
| No global mutation → no thread-safety issues. | |
| 4. Use locks around patching (limited usefulness) | |
| You can serialize patch usage: | |
| lock = threading.Lock() | |
| with lock: | |
| with patch("module.func"): | |
| ... | |
| This prevents concurrent patch/unpatch, but: | |
| It does NOT isolate threads from each other | |
| It just avoids collisions | |
| 5. Use thread-safe mocks when needed | |
| If multiple threads call the same mock: | |
| Use a custom mock with a lock, or | |
| Use ThreadingMock (if available in your environment), which records calls atomically | |
| 6. Patch per-thread via design (advanced) | |
| If you truly need isolation: | |
| Avoid globals entirely | |
| Use thread-local storage or pass dependencies explicitly | |
| Mental model | |
| Think of patch like: | |
| “Temporarily monkey-patch a global variable” | |
| That’s fundamentally shared mutable state, which is the opposite of thread-safe design. | |
| Bottom line | |
| ❌ patch() is not thread-safe | |
| ❌ Mock is not thread-safe for concurrent use | |
| ✅ You can manage risk by controlling timing (patch before threads) | |
| ✅ The real solution is avoiding global patching entirely (dependency injection) | |
| If you want, I can show a concrete example where patching breaks under threads and how to redesign it safely. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment