Buttons840
Buttons840

Reputation: 9637

How do a mock a superclass that is part of a library?

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

Answers (2)

Michele d&#39;Amico
Michele d&#39;Amico

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

  1. If the class is subclassed and you need patch it when build a subclass object you must patch __init__ directly instead of class definition.
  2. __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

mgilson
mgilson

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

Related Questions