🙌
Pythonのunittestでmockを使用する
概要
- Pythonのunittestで関数をモック化する方法です
- モック化するにはunittest.mockのMagicMockもしくはpatchを使用します
- APIの呼び出しなどでrequestsを使用しており、requestsをモックにする場合はrequests-mockというライブラリを使用できます
バージョン情報
- Python: 3.10.7
- requests-mock: 1.10.0
各ライブラリの公式サイト
サンプルコード
サンプルコードはこちらです。
対象のコード
今回の記事では外部APIの呼び出し(api.call_api)をモックにすることを想定します。
sample.py
import api
def main(url):
response = api.call_api(url)
return response.text
api.py
import requests
# この関数をモック化する
def call_api(url):
response = requests.get(url)
return response
MagicMockを使用する
関数をmockにする
関数をmockにするにはMagicMockを使用します。
assert_called_withを使用することでmockにした関数が呼ばれたかどうかを検証できます。
test_sample.py
class TestSample(unittest.TestCase):
def test_mock_called(self):
# api.call_apiをmockにする
mock = MagicMock()
api.call_api = mock
url = "https://example.com"
main(url)
# call_apiが、"https://example.com"の引数で呼ばれたことを検証する
mock.assert_called_with("https://example.com")
戻り値を設定する
mockの戻り値を設定する場合は、MagicMockのreturn_valueを設定します。
test_sample.py
class ResponseMock:
def __init__(self, text):
self.text = text
class TestSample(unittest.TestCase):
def test_mock_return_value(self):
# call_apiをモック化して、戻り値をResponseMock("mocked_text")に設定する
mock = MagicMock(return_value=ResponseMock("mocked_text"))
api.call_api = mock
url = "https://example.com"
result = main(url)
self.assertEqual(result, "mocked_text")
戻り値を変える
関数の呼び出し方法によってmockの戻り値を変えたい場合は、MagicMockのside_effectを設定します。
argsには関数を呼び出したときの引数が入っているため、以下の例ではargsの値によって戻り値を変えています。
test_sample.py
class TestSample(unittest.TestCase):
def test_mock_side_effect(self):
# argsにはcall_apiを呼び出したときの引数が入る
# 引数によって戻り値を変える
def mock_return_value(*args, **kwargs):
if args[0] == "https://example.com":
result = "mocked_text"
else:
result = "not_found_text"
return ResponseMock(result)
mock = MagicMock(side_effect=mock_return_value)
api.call_api = mock
# test1
url = "https://example.com"
result = main(url)
self.assertEqual(result, "mocked_text")
# test2
url = "https://notfound.com"
result = main(url)
self.assertEqual(result, "not_found_text")
patchを使う
特定のテストを実行しているときだけmockにしたいことがあります。
そういった場合はpatchを使用します。
context managerとして使用する
patchをcontext managerとして使用する方法です。
test_sample.py
class TestSamplePatch(unittest.TestCase):
def test_mock_patch(self):
mock = MagicMock(return_value=ResponseMock("mocked_text"))
with patch('api.call_api', mock):
print(api.call_api)
# output: <MagicMock id='xxxxxxxxxx'>
url = "https://example.com"
result = main(url)
self.assertEqual(result, "mocked_text")
print(api.call_api)
# output: <function call_api at xxxxxxxxx>
decoratorを使用する
また、patchはdecoratorとしても使用できます。
test_sample.py
class TestSamplePatch(unittest.TestCase):
mock = MagicMock(return_value=ResponseMock("mocked_text"))
@patch('api.call_api', mock)
def test_mock_patch_decorator(self):
url = "https://example.com"
result = main(url)
self.assertEqual(result, "mocked_text")
requests-mockを使用する
外部APIの呼び出しにrequestsを使用している場合はrequests-mockを使用すれば簡単にrequestsをmock化することができます。
test_sample.py
class TestSampleRequestMock(unittest.TestCase):
def test_mock_request(self):
url = "https://example.com"
with requests_mock.Mocker() as mock:
mock.get(url, text="mocked_text")
result = main(url)
self.assertEqual(result, "mocked_text")
Discussion