7

I have a custom framework which runs different code for different clients. I have monkeypatched certain methods in order to customize functionality for a client.

Here is the pattern simplified:

    #import monkeypatches here
    if self.config['client'] == 'cool_dudes':
        from app.monkeypatches import Stuff
    if self.config['client'] == 'cool_dudettes':
        from app.monkeypatches import OtherStuff

Here is an example patch:

from app.framework.stuff import Stuff

def function_override(self):
  return pass

Stuff.function = function_override

This works fine when the program executes as it is executed in a batch manner, spinning up from scratch every time. However, when running across unit tests, I find that the monkey patches persist across tests, causing unexpected behavior.

I realize that it would be far better to use an object oriented inheritance approach to these overrides, but I inherited this codebase and am not currently empowered to rearchitect it to that degree.

Barring properly re-architecting the program, how can I prevent these monkey patches from persisting across unit tests?

melchoir55
  • 6,842
  • 7
  • 60
  • 106
  • Can you...move all the monkeypatching into methods that can themselves be monkeypatched during the unit testing? What exactly is the desired behavior here? Presumably you *do* want to unit test your per-customer customizations, in which case you *would* want a monkeypatch operation to persist across all the unit tests? Or not? – larsks Oct 23 '17 at 23:34
  • The monkeypatching should only take effect when I'm running the particular code path which trips the monkeypatch. This is true normally as the program is spun up from scratch, but in a unit test environment the patches seem to persist across tests. At the moment I'm experimenting with just patching the individual instances of these classes rather than the class method itself, but that is wildly more messy as it has to happen all over the code base (wherever the classes are instantiated) – melchoir55 Oct 23 '17 at 23:47

2 Answers2

4

The modules, including app.framework.<whatever>, are not reloaded for every test. So, any changes in them you make persist. The same happens if your module is stateful (that's one of the reasons why global state is not such a good idea, you should rather keep state in objects).

Your options are to:

  • undo the monkey-patches when needed, or
  • change them into something more generic that would change (semi-)automatically depending on the test running, or
  • (preferred) Do not reinvent the wheel and use an existing, manageable, time-proven solution for your task (or at least, base your work on one if it doesn't meet your requirements completely). E.g. if you use them for mocking, see How can one mock/stub python module like urllib . Among the suggestions there is @mock.patch that does the patching for a specific test and undoes it upon its completion.
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
1

Anyone coming here looking for information about monkeypatching, might want to have a look at pytest's monkeypatch fixture. It avoids the problem of the OP by automatically undoing all modifications after the test function has finished.

bandela
  • 116
  • 3
  • I have to admit, this isn't working cleanly for me. Possibly because I have several test methods within the same class and it is doggedly persisting across the class. But a manual check of the monkeypatch attributes before invoking the unit under test would suggest it is cleared. Puzzling. – Amiga500 Jun 21 '22 at 15:54