The Magic of Mocking in Python

Published Apr 02, 2018Last updated Apr 20, 2018
The Magic of Mocking in Python

According to its official documentation page, mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used.

Before Python 3.3, mock used to be a separate library. Now it's available as unittest.mock from Python 3.3. In this post, we'll discuss three of the coolest magics about mocking in python. These are talked about briefly below:

  1. Setting return value on function call — One of the cool things about mocking is that you can set the return value of any function call when writing tests. This allows you to test things like varying results of API calls, certain edge cases that might not have been possible to produce, as well as values that are uncertain or gotten randomly. (e.g. random.choice, random.randint, uuid.uuid4 etc).

For example:

import uuid

def generate_unique_ids(n):
  return [uuid.uuid4() for _ in range(n)]


# Test the generate_unique_ids function
import unittest
import uuid
from mock import patch

class TestMockFunctionReturnValue(unittest.TestCase):
    
    @patch('uuid.uuid4')
    def test_mock_return_value(self, uuid_mock):
        unique_id = uuid.uuid4()
        uuid_mock.return_value = unique_id
        
        generated_ids = generate_unique_ids(1)
        self.assertEqual([unique_id], generated_ids)

Explanation: We defined a function generate_unique_ids to generate a number of unique IDs. However, because the return value of uuid.uuid4 is unpredicatable, we are not able to fully test the behavior of the function.

Since we can use the mock library, it provides us a way to set the return value, we can be sure of what the generate_unique_ids function returns.

2. Checking that a function was called — mock has an assert method (assert_called) that helps us test that a function was called. This could be useful when testing that one of many functions called is made if a condition is met or to test functions with unpredictable behavior, like: random.choice, random.shuffle, random.randrange, uuid.uuid4, request.post etc.

For example:

# Using Python 3
import random

def generate_and_shuffle(n):
    # Generate list of numbers from 1 up to n and shuffle them
    numbers = list(range(1,n+1))
    random.shuffle(numbers)
    return numbers

# Test that the random.shuffle function call was called
import unittest
from mock import patch

class TestMockFunctionCall(unittest.TestCase):
    
    @patch('random.shuffle')
    def test_mock_function_call(self, random_shuffle_mock):
        generate_and_shuffle(5)
        
        # Assert that random.shuffle was called 
        random_shuffle_mock.assert_called()

Explanation: We defined a function generate_and_shuffle that generates a list of numbers and shuffles the list. However, testing what the function returns could become difficult due to the unpredictability of the call to random.shuffe.

Mocking saves us in this case. With an assert method assert_called available on the mock object random_shuffle_mock, we could just simply check that there is a call to the random.shuffle method. It gets more interesting with another magic of mocking discussed next. We could assert that the random.shuffle was called with the expected arguments.

3. Checking that a function was called with specific argument(s) — In magic 2, we only tested that a function was called. However, mock provides another assert_method (assert_called_with) that goes a step further to test that a function was called with expected arguments.

In the example above, random.shuffle was called to shuffle a list. Since we are quite certain of the value of the list, we can test that the random.shuffle was called with the expected list.

See the example below:

# Using Python 3

import random

def generate_and_shuffle(n):
    # Generate list of numbers from 1 up to n and shuffle them
    numbers = list(range(1,n+1))
    random.shuffle(numbers)
    return numbers

# Test that the function was called with expected arguments
import unittest
from mock import patch

class TestMockFunctionCallWithExpectedArgument(unittest.TestCase):
    
    @patch('random.shuffle')
    def test_mock_function_call_with_expected_arguments(self, random_shuffle_mock):
        number_to_expected_list_tuple = ((0, []), (1, [1]), (4, [1,2,3,4]))
        for number, expected_list in number_to_expected_list_tuple:
          generate_and_shuffle(number)
        
    	    # Assert that random.shuffle was called 
        	random_shuffle_mock.assert_called_with(expected_list)

I have only provided a bit of little information about what mocking could help achieve. You can check out the mock documentation for a detailed overview of the mock library.

Discover and read more posts from Abiodun Hassan Oyeboade
get started