When writing unit tests in Python, you'll often want to patch an entire class. In my case, I've got a function that initializes a class, and that class needs to be patched in its entirety.
Note: This example will be used throughout this guide.
I want to test a function called add_numbers
:
from utils import Slack
def add_numbers(x, y):
try:
result = x + y
except TypeError:
result = 'Could not add: {0} and {1}'.format(x, y)
slack = Slack()
slack.send(result)
Basically it's a function that adds two numbers, and sends the response to a slack channel via an API request. That API request is handled through another class called Slack
(located in utils.py
):
import json
import requests
SLACK_HOOK = 'https://hooks.slack.com/services/ab/cd/ef'
class Slack:
def __init__(self, *args, **kwargs):
# Use the hook_url pass or default to SLACK_HOOK
self.hook_url = kwargs.get('hook_url', SLACK_HOOK)
def send(self, msg):
headers = {'Content-type': 'application/json'}
payload = {'text': msg}
requests.post(
self.hook_url,
data=json.dumps(payload),
headers=headers,
)
When unit testing, we want to test the smallest pieces of application code in isolation. And even more importantly, we don't want to write unit tests that fire off API requests to outside services. So, we'll want to patch the Slack
class when writing unit tests for the add_numbers
function.
So here is how we patch the Slack
class for this test.
import unittest
from add_numbers import add_numbers
from unittest import mock, TestCase
class TestAddNumbers(TestCase):
@mock.patch('add_numbers.Slack')
def test_calls_slack_send(self, mock_slack):
_slack = mock_slack.return_value
add_numbers(1, 2)
# Assert send called
self.assertEqual(_slack.send.call_count, 1)
if __name__ == '__main__':
unittest.main()
From here on, I'll exclude the imports and the call to our test runner.
What I want to point out about this example is how the Slack
class is patched.
First, the patch
decorator is used. If you're a keen observer, you'll notice we're patching add_numbers.Slack
as opposed to utils.Slack
(where the class actually lives). We'll discuss this in a later step.
Notice that mock_slack
is passed in as an argument to our test method. This argument is of type MagicMock, and represents the mocked class. In order to get the instantiated object, you need to called mock_slack.return_value
. And with this, we have access to mocked class methods, like send
.