John Carrell
John Carrell

Reputation: 1970

How to mock Python classes when nested several dependencies deep

If I have the following architecture...

Please note the edits below. It occurred to me (after some recent refactoring) that there are actually three classes in three different files. Sorry that the file/class names are getting ridiculous. I assure you those are not the real names. :)

main_class.py

class MainClass(object):
    def do_some_stuff(self):
        dependent_class = DependentClass()



dependent_class.py

class DependentClass(object):
    def __init__():
        dependent_dependent_class = DependentDependentClass()
        dependent_dependent_class.do_dependent_stuff()



dependent_dependent_class.py

class DependentDependentClass(object):
    def do_dependent_stuff(self):
        print "I'm gonna do production stuff that I want to mock"
        print "Like access a database or interact with a remote server"

class MockDependentDependentClass(object):
    def do_dependent_stuff(self):
        print "Respond as if the production stuff was all successful."

and I want to call main_class.do_some_stuff during testing but, during its execution, I want instances of DependentDependentClass replaced with MockDependentDependentClass how can I do that pythonically using best practices.

Currently, the best thing I could come up with is to conditionally instantiate one class or the other based on the presence/value of an environment variable. It certainly works but is pretty dirty.

I spent some time reading about the unittest.mock and mock.patch functions and they seem like they might be able to help but each description that I could wrap my head around seemed to be a little different than my actual use case.

The key is that I don't want to define mock return values or attributes but that I want the namespace changed, globally, I guess, such that when my application thinks it is instantiating DependentClass it is actually instantiating MockDependentClass.

The fact that I can't find any examples of anyone doing exactly this means one of two things:

  1. It's because I'm doing it in a very dumb/naive way.
  2. I'm doing something so genius no else has ever encountered it.

... I assume it's number 1...

Full disclosure, unit testing is not something with which I am skilled. It's an effort that my internal tools development team is trying to catch up to step our game up a bit. It's possible that I'm not thinking about testing correctly.

Any thoughts would be most welcome. Thank you, in advance!

SOLUTION!!!

Thanks to @de1 for the help. Given my clever architecture shown above the following accomplishes what I want.

The following code is located in main_class.py

import dependent_class
from dependent_dependent_class import MockDependentDependentClass

with patch.object(dependent_class, "DependentDependentClass", MockDependentDependentClass):
    main_class = MainClass()
    main_class.do_some_stuff()

The code seems to (and hell if I know how it's doing this) manipulate the namespace within the module dependent_class so that, while inside the with block (that's a context manager for anyone who is hung up on that part) anything referring to the class object DependentDependentClass will actually be referencing MockDependentDependentClass.

Upvotes: 3

Views: 2615

Answers (1)

de1
de1

Reputation: 3124

The mock module does indeed seem to be a good fit in this case. You can specify the mock (your mock) to use when calling the various patch methods.

If you are importing only the class rather than the module you can patch the imported DependentDependentClass in DependentClass:

import .DependentClass as dependent_class
from .DependentDependentClass import MockDependentDependentClass

with patch.object(dependent_class, 'DependentDependentClass', MockDependentDependentClass):
  # do something while class is patched

Alternatively:

with patch('yourmodule.DependentClass.DependentDependentClass', MockDependentDependentClass):
  # do something while class is patched

or the following will only work if you are accessing the class via a module or import it after it is being patched:

with patch('yourmodule.DependentDependentClass.DependentDependentClass', MockDependentDependentClass):
  # do something while class is patched

Just bare in mind what object is being patched, when.

Note: you might find it less confusing naming your files in lower case, slightly different to the embedded class(es).

Note 2: If you need to mock a dependency of a dependency of the module under test then it might suggest that you are not testing at the right level.

Upvotes: 2

Related Questions