Mickey
Mickey

Reputation: 1505

Patching a module-level method in a general way after import

I'll use the following example to illustrate my needs.

Let's say I have a module some_path.module.py with the following code:

def method_i_want_to_patch(*args, **kwargs):
   ...  # some implementation here

And in other places in the production code I have:

from some_path.module import method_i_want_to_patch

Now, in my tests, I would like to patch that method for all unit tests. I have a base class for tests which inherits from unittest.TestCase. However, due to some cleanups and other stuff needed to be done, the patches are added through a method that is used in the individual test cases themselves, which basically has the patch("...") call and calls for patch.start().

The problem is that the method is being imported by other modules, therefore patching it there is "too late". One option I have is to change the some_path.module.py to:

def method_i_want_to_patch(*args, **kwargs):
   return _method_i_want_to_patch_impl(args, kwargs)

def _method_i_want_to_patch_impl(*args, **kwargs):
   ...  # some implementation here

And then patching with patch("some_path.module. _method_i_want_to_patch_impl", mock) but it changes production code just to enable testing, which I don't extremely like. Are there any other clean options which will not modify production code but still keep the way the test infra built?

Upvotes: 0

Views: 1363

Answers (1)

Dirk Herrmann
Dirk Herrmann

Reputation: 5939

From your description I conclude that your dependencies look something like this:

  • Your system under test (SUT), that is, the function you would like to test, imports and uses method_i_want_to_patch.
  • And, the SUT uses some other_module, where from other_module also method_i_want_to_patch is imported. This scenario, as you write in your comment, occurs quite often.

In addition to the option you have described (adding an indirection), you could do the following:

Alternative A:

Ensure that every other_module uses the method_i_want_to_patch in the following way:

import module
...
module.method_i_want_to_patch(...)

instead of

from module import method_i_want_to_patch
...
method_i_want_to_patch(...)

If this is done consistently, then patching module.method_i_want_to_patch will work. See https://docs.python.org/3/library/unittest.mock.html#where-to-patch.

Alternative B:

In your tests of SUT, not only patch method_i_want_to_patch, but also patch what is called from other_module. This way, also the transitive dependency from other_module to method_i_want_to_patch is eliminated.

Final remarks:

It is well possible, though, that in the end your original idea turns out to be the best: Maybe you can not ensure method_i_want_to_patch is used consistently as module.method_i_want_to_patch, or, maybe mocking other_module would be disadvantageous.

You are not fond of this idea of yours, as you mention: "[...] it changes production code just to enable testing, which I don't extremely like". You have the impression you are drifting away from your "ideal" design "just" to improve testability. In my eyes, testability is an important design goal, similar to performance. Keep in mind how little developers are worried to move away from the ideal design "just" to improve performance. If we are OK with deliberate design changes for performance, why should we have a headache if we do the same for testability?

Upvotes: 1

Related Questions