Let’s assert a field from the response. It sounds like an easy task to do. So why bother writing the article? Well, I received last week a simple question.
What in your opinion will be better?
assert response.json()["field"] == "expected value"
or
assert "field" in response.json()
assert response.json()["field"] == "expected value"
And as usual, we can say out loud “IT DEPENDS” - end of the conversation. But as it is true, I hate to leave such a response alone without explanation and therefore I wrote a simple answer. It ended up within two sentences, but for the whole day, I was thinking about what is behind this choice. Now I’m wondering how long this article will be because there is a ton to say!
Unconditional field #
First of all, it depends on the context. So w need to write the whole example test:
def test_get_user_returns_user_name(client, user_factory):
user_factory(user_id=1, user_name="John Doe")
response = client.get("/user/1")
assert response.json()["user_name"] == "John Doe"
The test name tells us that calling get
on the user
API will return user_name
. There are no ifs
in that sentence so the presence of user_name
is unconditional. That’s the context in which I would choose a simpler, single assertion. There is no point in adding an extra line that will double-check something that I’m not expecting.
Conditional field #
Now it’s time for a different context and again like good developers, we will start from the test:
def test_get_user_sensitive_data_returns_additional_sensitive_field(
client, user_factory
):
user_factory(user_id=1, sensitive_field="private_value")
response = client.get("/user/1", params={"sensitive_data": True})
assert "sensitive_field" in response.json()
assert response.json()["sensitive_field"] == "private_value"
This time we are testing a field that is present only when we pass the ``sensitive_data` parameter to our API. In such a context, for me, it’s worth adding this extra assertion. I would go even further and make it more expressive.
assert "sensitive_field" in response.json(), "Expecting sensitive_field in the response after passing sensitive_data=true parameter"
This extra argument in assertion is a message that will be displayed if it fails the check. Maybe it is not super-required, but you would likely appreciate a situation where the problem can be identified solely based on the failed test report. I’m always positively surprised to see such care about details in tests 🙂
Is it enough? #
Rhetorical question … of course not! While this assert "field" in response
is super detailed and legible, we are missing very important tests!
We need to be sure that this field is missing if we set sensitive data to false and what is the default value of this parameter?
def test_get_user_without_sensitive_data_doesnt_contain_sensitive_field(
client, user_factory
):
user_factory(user_id=1, sensitive_field="private_value")
response = client.get("/user/1", params={"sensitive_data": False})
assert "sensitive_field" not in response.json()
def test_get_user_by_default_doesnt_return_sensitive_data(
client, user_factory
):
user_factory(user_id=1, sensitive_field="private_value")
response = client.get("/user/1")
assert "sensitive_field" not in response.json()
Only after adding those two tests, we covered what’s needed for us to sleep well at night.
Summary #
Direct verifying the value of a field that is always returned is all you need, but when dealing with optional fields, additional assertions and tests may be necessary to ensure complete verification.
Another way of answering this question is checking what will be more legible in case of failure. But that will be covered in PART 2.
PS. I’m preparing a workshop about Test Driven Development in Python. If you want to learn more, subscribe not to miss the opportunity!
PS2. There will be more about tests and TDD on the blog and social media.