Search code examples
pythonunit-testingmonkeypatching

Python Mock Patch patches a used module for submodule under test and every other imported submodule in the submodule under test


Sorry for the rather confusing title, but find it really hard to get to a crisp headline.

My Problem:

I have a submodule under test lets call it backend. And I use random.choice in backend. I patch it in my test and that works. It is patched. backend imports pymongoto do some database stuff and pymongo uses random.choiceas well for selecting the right server in core.py.

Somehow my patch of backend.random.choice also is used for backend.pymongo.random.choice. I am not sure why. Is that the correct behavior? And if it is, what is the way to get around that? Preferebly without changing any code in backend and pymongo, but only in my tests.

Other investigation:

I set up a little test construct to see if that is something related to my project or a general thing.

I created some modules:

bar/bar.py

import random

def do_some_other_something():
    return random.choice([10])

foo/foo.py

import random
from bar.bar import do_some_other_something

def do_something():
    return random.choice([1])

def do_something_else():
    return do_some_other_something()

And a test case:

from foo.foo import do_something, do_something_else
from unittest import mock

assert(do_something() == 1) # check
assert(do_something_else() == 10) # check

with mock.patch("foo.foo.random.choice") as mock_random_choice:
    assert (do_something() != 1) # check (mocked as expected)
    assert (do_something_else() == 10) # assertion! also mocked

So I am really confused about this. I explicitly mocked foo.foo's random.choice not bar.bar's. So why is that happening? Intended? Is there a way to fix that?

Thank you!


Solution

  • There's only one random module in the program. When foo.foo and bar.bar both import random, they share the random module. Patching the choice function on the random module modifies the one random module used by every part of the program, including both foo.foo and bar.bar.

    Instead of patching foo.foo.random.choice, patch foo.foo.random. That replaces foo.foo's reference to the random module, but not bar.bar's reference. bar.bar will continue to access the real random module, but foo.foo will see a mock random.