Mocking in unittests in Python
Example - Using mock¶
Imports¶
import sys
import unittest
# Python compatibility
if sys.version_info < (3,3):
import mock
else:
import unittest.mock as mock
The mock object¶
Create a mock object:
m = mock.Mock()
Show the attributes of the object:
dir(m)
Print a fake attribute. It doesn't exist, but will be shown.
m.fake_attribute
Again show the object. This time the fake_attribute
will be shown too.
dir(m)
Set a return value for the newly introduced attribute and retrieve it.
m.fake_attribute.return_value = "Fake return value"
m.fake_attribute()
Create another attribute, but this time assign a (fake) function to its return_value
.
def print_fake_value():
print("Fake function is called!")
m.another_attribute.return_value = print_fake_value
m.another_attribute()
Same exercise with a function with an argument:
def print_fake_value_with_arg(argument):
print("Fake argument %s" % argument)
m.the_third_attribute.return_value = print_fake_value_with_arg
m.the_third_attribute('Print me')
You can also create a custom exception by using the side_effect
. It can be an exception, callable or an iterable.
m.some_function.side_effect = ValueError("Super error")
m.some_function()
To make it an iterable, the following can be used. By calling the mock several times, it will return the values until the limit of the range is reached.
m.some_iteration_thing.side_effect = range(2)
m.some_iteration_thing()
m.some_iteration_thing()
m.some_iteration_thing()
Finally you can also pass a callable to the side_effect
, by doing the following:
def side_function():
print('This is a side function!')
m.some_simple_function.side_effect = side_function()
m.some_simple_function()
def side_function_with_arg(argument):
print('This is a side function with argument: %s' % argument)
m.some_simple_function_with_arg.side_effect = side_function_with_arg
m.some_simple_function_with_arg('No argument!')
An important function of the side_effect
is that you can pass it a class, which can be helpful if you are testing code and verify the behaviour of the class
class Car(object):
def __init__(self, name):
self._name = name
def print_name(self):
print("Name: %s" % self._name)
m.a_car_attribute.side_effect = Car
car = m.a_car_attribute.side_effect('My red car')
car
car.print_name()
class Castle(object):
def __init__(self, name):
self.name = name
self.boss = 'Bowser'
self.world = 'Grass Land'
def access(self, character):
if character.powerup == 'Super Mushroom':
return True
else:
return False
def get_boss(self):
return self.boss
def get_world(self):
return self.world
We will also define a character class:
class Character(object):
def __init__(self, name):
self.name = name
self.powerup = ''
def powerup(self, powerup):
self.powerup = powerup
def get_powerup(self):
return self.powerup
Finally we will define a testclass to test the functionality of the classes.
class CharacterTestClass(unittest.TestCase):
""" Defines the tests for the Character class """
def setUp(self):
""" Set the castle for the test cases """
self.castle = Castle('Bowsers Castle')
def test_mock_access_denied(self):
""" Access denied for star powerup """
mock_character = mock.Mock(powerup = 'Starman')
self.assertFalse(self.castle.access(mock_character))
def test_mock_access_granted(self):
""" Access granted for mushroom powerup """
mock_character = mock.Mock(powerup = 'Super Mushroom')
self.assertTrue(self.castle.access(mock_character))
def test_default_castle_boss(self):
""" Verifty the default boss is Bowser """
self.assertEqual(self.castle.get_boss(), "Bowser")
def test_default_castle_world(self):
""" Verify the default world is Grass Land """
self.assertEqual(self.castle.get_world(), "Grass Land")
# Mock a class method
@mock.patch.object(Castle, 'get_boss')
def test_mock_castle_boss(self, mock_get_boss):
mock_get_boss.return_value = "Hammer Bro"
self.assertEqual(self.castle.get_boss(), "Hammer Bro")
self.assertEqual(self.castle.get_world(), "Grass Land")
# Mock an instance
@mock.patch(__name__+'.Castle')
def test_mock_castle(self, MockCastle):
instance = MockCastle
instance.get_boss.return_value = "Toad"
instance.get_world.return_value = "Desert Land"
self.castle = Castle
self.assertEqual(self.castle.get_boss(), "Toad")
self.assertEqual(self.castle.get_world(), "Desert Land")
# Mock an instance method
def test_mock_castle_instance_method(self):
# Boss is still Bowser
self.assertNotEqual(self.castle.get_boss(), "Koopa Troopa")
# Set a return_value for the get_boss method
self.castle.get_boss = mock.Mock(return_value = "Koopa Troopa")
# Boss is Koopa Troopa now
self.assertEqual(self.castle.get_boss(), "Koopa Troopa")
def test_castle_with_more_bosses(self):
multi_boss_castle = mock.Mock()
# Set a list as side_effect for the get_boss method
multi_boss_castle.get_boss.side_effect = ["Goomba", "Boo"]
# First value is Goomba
self.assertEqual(multi_boss_castle.get_boss(), "Goomba")
# Second value is Boo
self.assertEqual(multi_boss_castle.get_boss(), "Boo")
# Third value does not exist and raises a StopIteration
self.assertRaises(StopIteration, multi_boss_castle.get_boss)
def test_calls_to_castle(self):
self.castle.access = mock.Mock()
self.castle.access.return_value = "No access"
# We should retrieve no access for everybody
self.assertEqual(self.castle.access('Let me in'), "No access")
self.assertEqual(self.castle.access('Let me in, please'), "No access")
self.assertEqual(self.castle.access('Let me in, please sir!'), "No access")
# Verify the length of the arguments list
self.assertEqual(len(self.castle.access.call_args_list), 3)
Run the test suite¶
import sys
suite = unittest.TestLoader().loadTestsFromTestCase(CharacterTestClass)
unittest.TextTestRunner(verbosity=4,stream=sys.stderr).run(suite)
class CharacterCastleTestClass(unittest.TestCase):
""" Defines the tests for the Character and Castle class together """
@mock.patch(__name__+'.Castle')
@mock.patch(__name__+'.Character')
def test_mock_castle_and_character(self, MockCharacter, MockCastle):
# Note the order of the arguments of this test
MockCastle.name = 'Mocked Castle'
MockCharacter.name = 'Mocked Character'
self.assertEqual(Castle.name, 'Mocked Castle')
self.assertEqual(Character.name, 'Mocked Character')
def test_fake_powerup(self):
character = Character("Sentinel Character")
character.powerup = mock.Mock()
character.powerup.return_value = mock.sentinel.fake_superpower
self.assertEqual(character.powerup(), mock.sentinel.fake_superpower)
def test_castle_with_more_powerups(self):
self.castle = Castle('Beautiful Castle')
multi_characters = mock.Mock()
# Set a list as side_effect for the get_boss method
multi_characters.get_powerup.side_effect = ["mushroom", "star"]
# First value is mushroom
self.assertEqual(multi_characters.get_powerup(), "mushroom")
# Second value is star
self.assertEqual(multi_characters.get_powerup(), "star")
# Third value does not exist and raises a StopIteration
self.assertRaises(StopIteration, multi_characters.get_powerup)
suite = unittest.TestLoader().loadTestsFromTestCase(CharacterCastleTestClass)
unittest.TextTestRunner(verbosity=2,stream=sys.stderr).run(suite)