I have an API on Flask (Python) that interacts with a third-party API, for example, creating documents.
My code:
def my_function(data):
response = requests.post("https://some_api.com", json=data)
json_response = response.json()
if json_response["message"] == "Successfully created":
return "Successfully created"
elif json_response["message"] == "Already exists":
return "Already exists"
elif json_response["message"] == "Wrong data":
return "Wrong data"
else:
return json_response["message"]
I have already created tests to cover cases where the third-party API works as expected(where "message" equals to "Successfully created", "Already exists" or "Wrong data").
However, I also want to cover case where the third-party API does not work as expected. I want to create a test that covers the 'else' branch, but I cannot reach it while the third-party API is working properly.
Is there a way to obtain a response that the third-party API would not normally provide?
Can anyone suggest how I can achieve this?
Thank you in advance.
You would typically use a mock to replace your interaction with the actual API with synthesized behavior that you control.
For example, in the following code we're using mock.patch("requests.post")
(where mock.patch
comes from the unittest
module) so that calling requests.post
doesn't actually make an HTTP request to a remote site; instead requests.post
-- while inside our test function -- is a fake object for which we can control the return value and other aspects:
import pytest
import requests
from unittest import mock
def my_function(data):
response = requests.post("https://some_api.com", json=data)
json_response = response.json()
if json_response["message"] == "Successfully created":
return "Successfully created"
elif json_response["message"] == "Already exists":
return "Already exists"
elif json_response["message"] == "Wrong data":
return "Wrong data"
else:
return json_response["message"]
@mock.patch("requests.post")
@pytest.mark.parametrize(
"json_response,expected_return",
[
({"message": "Successfully created"}, "Successfully created"),
({"message": "Already exists"}, "Already exists"),
({"message": "Wrong data"}, "Wrong data"),
({"message": "Some other message"}, "Some other message"),
],
)
def test_my_function_suceeds(mock_post, json_response, expected_return):
mock_response = mock.MagicMock()
mock_response.json.return_value = json_response
mock_post.return_value = mock_response
res = my_function("")
assert res == expected_return
This is a parametrized test, meaning that while we have a single test function, pytest
will run it multiple times for each set of parameters. Running this with pytest -v
yields:
============================= test session starts ==============================
platform linux -- Python 3.11.1, pytest-7.2.1, pluggy-1.0.0 -- /home/lars/.local/share/virtualenvs/python-LD_ZK5QN/bin/python
cachedir: .pytest_cache
rootdir: /home/lars/tmp/python
plugins: anyio-3.6.2, base-url-2.0.0, playwright-0.3.0
collecting ... collected 4 items
testex.py::test_my_function_suceeds[json_response0-Successfully created] PASSED [ 25%]
testex.py::test_my_function_suceeds[json_response1-Already exists] PASSED [ 50%]
testex.py::test_my_function_suceeds[json_response2-Wrong data] PASSED [ 75%]
testex.py::test_my_function_suceeds[json_response3-Some other message] PASSED [100%]
============================== 4 passed in 0.04s ===============================
This is actually a bit more complicated than necessary, since in all cases your my_function
function is simply returning the value of the message
key, so we could in fact have written our test like this:
@mock.patch("requests.post")
@pytest.mark.parametrize(
"message",
[
"Successfully created",
"Already exists",
"Wrong data",
"Some other message",
],
)
def test_my_function_suceeds(mock_post, message):
mock_response = mock.MagicMock()
mock_response.json.return_value = {"message": message}
mock_post.return_value = mock_response
res = my_function("")
assert res == message
But I think the first example is more representative of how you would normally write this, since it's unlikely most functions would have such trivial behavior.