Python – the best/most Pythonic way to mock a private function

mockingpythontesting

Consider a module with a 'public' function which looks something like this:

def func(arg):
    val = _generate_something(arg)
    _do_something(val)

As you can see this is a 'void function'. Its core logic is in _generate_something.

I want to write unit tests for this function. I can think of several approaches to do so. I would like to know what the best one is, both in terms of general programming practices and best practice in Python specifically.

Essentialy this question is about the Pythonic way of mocking a private function (money patching? Injecting the function? Injecting an object? Etc.)


Option A

In the test monkeypatch _do_something (which is a 'private' function of the module), so I can check it receives a good value.


Option B

Inject _do_something as an argument to func, like so:

def func(arg, do_something=None):
    do_something = do_something or _do_something  # in production use default
    val = _generate_something(arg)
    do_something(val)

Then in my do_something mock I can check I received the correct value.


Option C

Have func be a method of a class, and have that class take do_something as an argument to __init__:

class Something(object):
    def __init__(self, do_something=None):
        self._do_something = do_something or _production_default

    def func(self, arg):
        val = self._generate_something(arg)
        self.do_something(val)

    # ... omitted for brevity ...

Option D

Like Option B or C, but do_something is injected as a 'full' object, not a bare function. For example:

def func(arg, something_doer=None):
    something_doer = something_doer or _something_doer # in production use default
    val = _generate_something(arg)
    something_doer.do_something(val)

As I said, I would like to know what the best option is, also with regard to 'Pythonicness'. For example, is it 'acceptable' in Python to mock bare functions (rather than objects which contain them)?

Best Answer

I would recommend to use the most simple and shortest approach, which is in Python probably option A, monkey patching. Everything else makes the code more complicated, with no substantial benefit. The only thing I would change here is to make this solution more explicit (see below).

Converting your module to a class (option C) should be motivated by the fact you need a class instead of a module, not by the fact you want to mock a single function. This option is preferable in languages like Java or C#, where there is no distinction between a "module" and a "class" in a way which is comparable to Python, and where a function like func would probably "live" inside a class at first hand.

Replacing the method do_something by an object with a method (option D) should not be done for the same reason - use an object instead of a standalone function when a standalone function is "not enough", for the sheer purpose of mocking it brings no advantage.

So this leaves you with option B or A. However, I would not pollute the parameter list of func for the purpose of mocking, nor would I stuff mocking code into a function func just for the purpose of mocking if there is simpler alternative. Better keep the "official" API as as well the functional code as clean as possible. Imagine what happens if you want to mock more "private" methods instead of just one, and if you want to test 5 public methods of the module - the API will contain 90% parameters only used for testing, which decreases the readability by a magnitude.

If you have concerns about the monkey patching approach because it changes something private, you can add a "public" (or "internal") patching function to the module, which allows the manipulation to be done through an "official" function:

def setup_mock_for_do_something(do_something):
    _do_something = do_something

This makes the possibility of "monkey patching" in this module more explicit, by simultanously avoiding unneccessary structural changes to the whole code.

So why is this all more "Pythonic"? Looking at "The Zen of Python", we see the following principles applied here

  • Explicit is better than implicit.
  • Simple is better than complex.
  • Readability counts.
  • If the implementation is easy to explain, it may be a good idea.