Reputation: 9637
I have some code like the following in Python:
library.py:
class HasSideEffects(object):
def __init__(self, kittens):
print 'kittens cry' # <- side effect
def launch_nukes(self):
print 'launching nukes' # <- side effect
my_code.py:
from library import HasSideEffects
class UtilitySubclass(HasSideEffects):
def party(self):
self.launch_nukes()
return 'confetti' # I want to test that my confetti cannon will work
if __name__ == '__main__':
x = UtilitySubclass(1) # oh no! crying kittens :(
x.party() # oh no! nukes!
I want to unit test that when I call party
I will get confetti
. However, I want to avoid all the side effects that are part of the library class I am subclassing. More specifically, I want to avoid these side effects in the unit tests, but the side effects are needed in the production code.
I cannot change the code in library.py
because it's a library, and I did not write the code. Obviously I wouldn't want to maintained a forked version of a library just to facilitate a unit test.
I've been trying to mock the HasSideEffects
superclass. I need to mock __init__
and launch_nukes
in the superclass so they no longer perform side effects. I've been having some trouble mocking the __init__
method, apparently the Python mock
library does not support it?
What's the best way to test that party
return confetti
, while avoiding side effects?
Upvotes: 2
Views: 1892
Reputation: 23721
Maybe you was quite close to a solution:
I've been trying to mock the HasSideEffects superclass. I need to mock init and launch_nukes in the superclass so they no longer perform side effects. I've been having some trouble mocking the init method, apparently the Python mock library does not support it?
unittest.mock
and legacy mock
are designed exactly to do this kind of work. When you need to remove dependencies from library or resources mock it and replace by patch
is powerful especially when you should play with legacy code that you cannot change.
In your case you need to patch either __init__
method: to do it you must take in account two things
__init__
directly instead of class definition.__init__
method MUST return None
and your mock too.Now come back to your simple example: I changed it a little bit to make tests more explicit.
library.py
class HasSideEffects(object):
def __init__(self, kittens):
raise Exception('kittens cry') # <- side effect
def launch_nukes(self):
raise Exception('launching nukes') # <- side effect
my_code.py
from library import HasSideEffects
class UtilitySubclass(HasSideEffects):
def party(self):
self.launch_nukes()
return 'confetti' # I want to test that my confetti cannon will work
test_my_code.py
import unittest
from unittest.mock import patch, ANY
from my_code import UtilitySubclass
class MyTestCase(unittest.TestCase):
def test_step_by_step(self):
self.assertRaises(Exception, UtilitySubclass) #Normal implementation raise Exception
#Pay attention to return_value MUST be None for all __init__ methods
with patch("library.HasSideEffects.__init__", autospec=True, return_value=None) as mock_init:
self.assertRaises(TypeError, UtilitySubclass) #Wrong argument: autospec=True let as to catch it
us = UtilitySubclass("my kittens") #Ok now it works
#Sanity check: __init__ call?
mock_init.assert_called_with(ANY, "my kittens") #Use autospec=True inject self as first argument -> use Any to discard it
#But launch_nukes() was still the original one and it will raise
self.assertRaises(Exception, us.party)
with patch("library.HasSideEffects.launch_nukes") as mock_launch_nukes:
self.assertEqual("confetti",us.party())
# Sanity check: launch_nukes() call?
mock_launch_nukes.assert_called_with()
@patch("library.HasSideEffects.launch_nukes")
@patch("library.HasSideEffects.__init__", autospec=True, return_value=None)
def test_all_in_one_by_decorator(self, mock_init, mock_launch_nukes):
self.assertEqual("confetti",UtilitySubclass("again my kittens").party())
mock_init.assert_called_with(ANY, "again my kittens")
mock_launch_nukes.assert_called_with()
if __name__ == '__main__':
unittest.main()
Note as the decorator version is neat and simple.
Upvotes: 3
Reputation: 309929
This is a pretty tricky question to answer in general. If the library class that you're mocking is simple enough, then you can provide your functionality as a mixin.
class UtilitySubclassMixin(object):
def party(self):
self.launch_nukes()
return 'confetti'
class UtilitySubclass(library.HasSideEffects, UtilityClassMixin):
"""meaningful docstring."""
Now, for testing you just need to provide a subclass that has the interface that your UtililtySubclassMixin expects (e.g. something with a launch_nukes
method). This isn't ideal, but if you need to mock out a library method that has an __init__
with side-effects it's about the best you can do. If it's all non-magic methods that have side-effects, unittest.mock
can be used to patch the methods on library.HasSideEffects
directly.
And, FWIW, this is a very good reason why __init__
methods should never have side effects :-).
Upvotes: 2