More unit testing in Python. Covers more advanced concepts not discussed in part 1.

Assertions on more complex data

A common scenario is to perform assertions on more complex data. Consider the following method and its associated test:

# project/myclass.py

def shuffle(self, my_list):
    random.shuffle(my_list)
    return my_list


# tests/test_myclass.py

def test_list(self):
    expected = [1, 2, 3, 4]
    actual = self._myclass.shuffle(expected[:])
    self.assertCountEqual(expected, actual)

assertCountEqual is used in place of assertEqual in order to ensure that the elements in the list remain the same. However, assertCountEqual has its limitations and will not work on much more complicated objects. If there was a need to test the equality of a dictionary of lists, a custom function has to be written in order to ensure equality.

Handling multiple calls

Some methods need to make multiple external calls to the same service. A simple example is provided below:

# project/myclass.py

def multi_add(self, a, b, c):
    ab = self._addHelper.add(a, b)
    abc = self._addHelper.add(ab, c)
    return abc


# tests/test_myclass.py

def test_multicalls(self):
    # GIVEN
    calls = [
        call(2, 3),
        call(5, 7)
    ]

    def side_effect(*args):
        if args[0] == 2:
            return 5
        return 12

    self._addhelpermock.add = MagicMock(side_effect=side_effect)

    # WHEN
    actual = self._myclass.multi_add(2, 3, 7)

    # THEN
    self._addhelpermock.add.assert_has_calls(calls)
    self.assertEqual(12, actual)

Exceptions

When testing with exceptions (e.g. bad response from server, wrongly formatted input etc.), a context manager is used when asserting the exception is raised. The context captured by the context manager can then be examined to make assertions upon the exception.

# project/myclass.py

class MyException(Exception):
    def __init__(self, message):
        super(MyException, self).__init__(message)
        self.message = message

def create_exception(self):
    my_dict = {}
    try:
        my_dict['key'] += 4
    except KeyError:
        raise MyException('Invalid key')

# tests/test_myclass.py

def test_exception(self):
    with self.assertRaises(MyException) as cm:
        self._myclass.create_exception()

    self.assertEqual('Invalid key', cm.exception.message)