{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreibzbjk467ipgzayyzxdhrorvwafo2yyxwqjyhouaedsg6uvg564ku",
    "uri": "at://did:plc:tntgihx6r2e2gqexsx72grd5/app.bsky.feed.post/3mbh5yrb7d7r2"
  },
  "path": "/blog/202511/why_your_mock_breaks_later",
  "publishedAt": "2026-04-21T06:42:11.576Z",
  "site": "https://nedbatchelder.com",
  "tags": [
    "Why your mock doesn’t work",
    "fails with an error",
    "a lot of over-mocking out\nthere",
    "the fix",
    "defend\nagainst mocking of the os module",
    "Why your mock still doesn’t work",
    "Fast\ntests for slow services: why you should use verified fakes",
    "Function Core,\nImperative Shell",
    "@patch"
  ],
  "textContent": "In Why your mock doesn’t work I explained this rule of mocking:\n\n> Mock where the object is used, **not** where it’s defined.\n\nThat blog post explained why that rule was important: often a mock doesn’t work at all if you do it wrong. But in some cases, the mock will work even if you don’t follow this rule, and then it can break much later. Why?\n\nLet’s say you have code like this:\n\n>\n>     # user.py\n>\n>     >\n>\n>     > def get_user_settings():\n>\n>     >     with open(Path(\"~/settings.json\").expanduser()) as f:\n>\n>     >         return json.load(f)\n>\n>     >\n>\n>     > def add_two_settings():\n>\n>     >     settings = get_user_settings()\n>\n>     >     return settings[\"opt1\"] + settings[\"opt2\"]\n>\n>     >\n\nYou write a simple test:\n\n>\n>     def test_add_two_settings():\n>\n>     >     # NOTE: need to create ~/settings.json for this to work:\n>\n>     >     #   {\"opt1\": 10, \"opt2\": 7}\n>\n>     >     assert add_two_settings() == 17\n>\n>     >\n\nAs the comment in the test points out, the test will only pass if you create the correct settings.json file in your home directory. This is bad: you don’t want to require finicky environments for your tests to pass.\n\nThe thing we want to avoid is opening a real file, so it’s a natural impulse to mock out `open()`:\n\n>\n>     # test_user.py\n>\n>     >\n>\n>     > from io import StringIO\n>\n>     > from unittest.mock import patch\n>\n>     >\n>\n>     > @patch(\"builtins.open\")\n>\n>     > def test_add_two_settings(mock_open):\n>\n>     >     mock_open.return_value = StringIO('{\"opt1\": 10, \"opt2\": 7}')\n>\n>     >     assert add_two_settings() == 17\n>\n>     >\n\nNice, the test works without needing to create a file in our home directory!\n\n# Much later...\n\nOne day your test suite fails with an error like:\n\n>\n>     ...\n>\n>     >   File \".../site-packages/coverage/python.py\", line 55, in get_python_source\n>\n>     >     source_bytes = read_python_source(try_filename)\n>\n>     >   File \".../site-packages/coverage/python.py\", line 39, in read_python_source\n>\n>     >     return source.replace(b\"\\r\\n\", b\"\\n\").replace(b\"\\r\", b\"\\n\")\n>\n>     >            ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^\n>\n>     > TypeError: replace() argument 1 must be str, not bytes\n>\n>     >\n\nWhat happened!? Coverage.py code runs during your tests, invoked by the Python interpreter. The mock in the test changed the builtin `open`, so any use of it anywhere during the test is affected. In some cases, coverage.py needs to read your source code to record the execution properly. When that happens, coverage.py unknowingly uses the mocked `open`, and bad things happen.\n\nWhen you use a mock, patch it where it’s used, not where it’s defined. In this case, the patch would be:\n\n>\n>     @patch(\"myproduct.user.open\")\n>\n>     > def test_add_two_settings(mock_open):\n>\n>     >     ... etc ...\n>\n>     >\n\nWith a mock like this, the coverage.py code would be unaffected.\n\nKeep in mind: it’s not just coverage.py that could trip over this mock. There could be other libraries used by your code, or you might use `open` yourself in another part of your product. Mocking the definition means _anything_ using the object will be affected. Your intent is to only mock in one place, so target that place.\n\n# Postscript\n\nI decided to add some code to coverage.py to defend against this kind of over-mocking. There is a lot of over-mocking out\nthere, and this problem only shows up in coverage.py with Python 3.14. It’s not happening to many people yet, but it will happen more and more as people start testing with 3.14. I didn’t want to have to answer this question many times, and I didn’t want to force people to fix their mocks.\n\nFrom a certain perspective, I shouldn’t have to do this. They are in the wrong, not me. But this will reduce the overall friction in the universe. And the fix was really simple:\n\n>\n>     open = open\n>\n>     >\n\nThis is a top-level statement in my module, so it runs when the module is imported, long before any tests are run. The assignment to `open` will create a global in my module, using the current value of `open`, the one found in the builtins. This saves the original `open` for use in my module later, isolated from how builtins might be changed later.\n\nThis is an ad-hoc fix: it only defends one builtin. Mocking other builtins could still break coverage.py. But `open` is a common one, and this will keep things working smoothly for those cases. And there’s precedent: I’ve already been using a more involved technique to defend\nagainst mocking of the os module for ten years.\n\n# Even better!\n\nNo blog post about mocking is complete without encouraging a number of other best practices, some of which could get you out of the mocking mess:\n\n  * Use `autospec=True` to make your mocks strictly behave like the original object: see Why your mock still doesn’t work.\n  * Make assertions about how your mock was called to be sure everything is connected up properly.\n  * Use verified fakes instead of auto-generated mocks: Fast\ntests for slow services: why you should use verified fakes.\n  * Separate your code so that computing functions like our `add_two_settings` don’t also do I/O. This makes the functions easier to test in the first place. Take a look at Function Core,\nImperative Shell.\n  * Dependency injection lets you explicitly pass test-specific objects where they are needed instead of relying on implicit access to a mock.\n\n",
  "title": "Why your mock breaks later",
  "updatedAt": "2025-11-16T12:55:48.000Z"
}