Imagine a method that makes an HTTP request. Let's call him MethodX.
MethodX is being called from another method. Guess what.. this another method will be called MethodY.
If we are testing MethodY, can we mock MethodX so our test doesn't make the HTTP request?
In this example we are going to mock a GET HTTP request that fetches a list of countries from a world population API.
Also, we will assert if our method is calling the right url http://api.population.io/1.0/countries
.
With that in mind, we disobey TDD write our method to be tested before the test itself:
def get_countries():
countries_url = 'http://api.population.io/1.0/countries'
request_response = requests.get(countries_url).json()
return request_response['countries']
Look at this filthy piece of code: requests.get(countries_url).json()
.
This is where the HTTP request will be made.
You will use the @patch('package.module.method')
decorator just before the test method.
This will create the mock that will be passed as parameter to your test function.
import unittest
from unittest.mock import patch
from examples.mocking_a_method_call.get_countries import get_countries
class TestGetCountriesWithPatchAsDecorator(unittest.TestCase):
@patch('examples.mocking_a_method_call.get_countries.requests.get')
def test_get_countries(self, mock_http_get):
expected_countries = ['Brazil', 'The rest of the world']
fake_url = 'http://ima.fake.url'
mock_http_get_response = mock_http_get.return_value
mock_http_get_response.json.return_value = {
'countries': expected_countries
}
actual_countries = get_countries(fake_url)
mock_http_get.assert_called_once_with(fake_url)
self.assertEqual(expected_countries, actual_countries)
class TestGetCountriesWithStraightForward(unittest.TestCase):
def setUp(self):
patcher = patch('examples.mocking_a_method_call.get_countries.requests.get')
self.mock_http_get = patcher.start()
self.addCleanup(patch.stopall)
def test_get_countries(self):
expected_countries = ['Brazil', 'The rest of the world']
fake_url = 'http://ima.fake.url'
mock_http_get_response = self.mock_http_get.return_value
mock_http_get_response.json.return_value = {
'countries': expected_countries
}
actual_countries = get_countries(fake_url)
self.mock_http_get.assert_called_once_with(fake_url)
self.assertEqual(expected_countries, actual_countries)
class TestGetCountriesWithContextManagerPatch(unittest.TestCase):
def test_get_countries(self):
with patch('examples.mocking_a_method_call.get_countries.requests.get') as mock_http_get:
expected_countries = ['Brazil', 'The rest of the world']
fake_url = 'http://ima.fake.url'
mock_http_get_response = mock_http_get.return_value
mock_http_get_response.json.return_value = {
'countries': expected_countries
}
actual_countries = get_countries(fake_url)
mock_http_get.assert_called_once_with(fake_url)
self.assertEqual(expected_countries, actual_countries)
See the source code
Yeah, there is.
Actually, all of this wibbly wobbly timey wimey stuff is centered on the unittest Mock object.
Further details here.
Also, see the official docs
- See other examples
- Go to advanced mocking
- Go to the essentials of mocking
- Go to mocking main menu
- Go to summary