2

in the case of unit testing a wrapper library, testing the wrapper without depending/exercising the upstream library is a goal; In a known case, all calls to the upstream library can be mocked and that's what I've done, but I've been frustrated by changes to the wrapper that introduce more calls to the upstream library being missed by the mock tools;

How can I best fail any test that tries to use a given namespace?

My idea currently is to change all the unittest methods to have a monkey patch like

@unittest.mock.patch('wrapper_namespace.upsteam_namespace')

and reply the upstream library with a mock that can be asserted untouched; I'm hoping for an option that works globally, so that I

  • don't have to add a monkeypatch to every test method, though this level of granularity is acceptable; but also don't have to perform the assertion that the mock was never used in the test methods (or make a decorator to do all that either)
  • prohibits access to the upstream library from any part of the software (e.g, Wrapper calls B calls Upstream, B's call to upstream might not be caught)
martineau
  • 119,623
  • 25
  • 170
  • 301
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147
  • Option 1 doesn't sound too bad if you can do all the work automatically by inspecting the relevant objects. Regarding option 2, can you wrap the upstream in an additional object that monitors / manages attribute access? Maybe a little example would help in getting a better understanding of your situation. – a_guest Oct 02 '18 at 22:30

1 Answers1

1

You don't have to patch every test method. You can easily patch over the class if you're using unittest, or just assign the module to whatever you want to patch over it with. Here's a workable example:

A fake lib in some_lib.py:

def some_lib_func():
    raise ValueError("I've been called.")

def some_other_lib_func():
    raise ValueError("I've been called.")

class SomeClass:
    def __init__(self):
        raise ValueError("I've been constructed.")

wrapper.py:

import some_lib

def wrapper1():
    some_lib.some_lib_func()

def wrapper2():
    some_lib.some_other_lib_func()

def wrapper3():
    x = some_lib.SomeClass()

test.py:

from unittest.mock import patch, MagicMock
import unittest

import wrapper

# Alternative:                                                                                                                                            
# wrapper.some_lib = MagicMock()                                                                                                                                                                            

# Can patch an entire class                                                                                                                                                                                 
@patch('wrapper.some_lib', MagicMock())
class TestWrapper(unittest.TestCase):
    def test_wrapper1(self):
        wrapper.wrapper1()

    def test_wrapper2(self):
        wrapper.wrapper2()

    def test_wrapper3(self):
        wrapper.wrapper3()

if __name__ == "__main__":
    unittest.main()

We would explode if the functions/classes in some_lib were called, but they aren't:

Matthews-MacBook-Pro:stackoverflow matt$ python test.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

Feel free to comment out the patch and comment in wrapper.some_lib = MagicMock(). You'll get the same result in this toy example, but there is a major difference between the two approaches:

When using @patch('wrapper.some_lib', MagicMock()) the patch is only live for that Test Case class.

When using wrapper.some_lib = MagicMock(), however, that patch will stay live for the entire length of your python program, unless you save off the original module and patch it back manually at some point. Everything that is using the wrapper module will get the mocked version.

So you could so something like:

original_lib = wrapper.some_lib
wrapper.some_lib = MagicMock()
...
# call some test suite, every call to the wrapper module will be mocked out
...
wrapper.some_lib = original_lib
...
# call some other test suite that actually needs the real thing
...

HTH.

EDIT: Misread your question slightly, but you can inspect MagicMock objects to see if they've been called, and if so, fail the test. Or just patch over with something that fails when called (instead of MagicMock). I can provide code to do this if requested (just leave a comment), but hopefully the above can get you started. I think the crux of the question was really about the global patching. Cheers!

Matt Messersmith
  • 12,939
  • 6
  • 51
  • 52