Videos
» pip install pytest-json-report
» pip install pytest-json
Instead of converting the JSON response into Object, I use json.loads() to convert it into a Dictionary, and compare them.
def test_login(self, client):
res = return client.post('/login',
data=json.dumps(dict(staff_name='no_such_user', staff_password='password')),
content_type='application/json',
follow_redirects=True)
assert res.status_code == 422
invalid_password_json = dict(message="Staff name and password pair not match",
errors=dict(
resource="Login",
code="invalid",
field="staff_authentication",
stack_trace=None,),
)
assert json.loads(res.data) == invalid_password_json
This way, I do not have to worry about whitespace differences in the JSON response, as well as ordering of the JSON structure. Simply let Python's Dictionary comparison function check for equality.
If you do indeed require literal, value-to-value equality between two doctionaries, it would be simpler to compare their json serialization results, otherwise you would need some recursive comparison of dicts and their values
Note: since dicts in python are unsorted collections, you would require passing sort_keys=True to json.dumps, see this question for more details
You can use pytest_generate_tests hook for parametrizing with dynamic data.
First create a fixture that can be called by the test function to get the test data.
# in conftest.py
@pytest.fixture()
def test_data(request):
return request.param
Now you can parametrize it so that it is called for each test data set:
#in conftest.py
def pytest_generate_tests(metafunc):
testdata = get_test_data('test_data.json')
metafunc.parametrize('test_data', testdata, indirect=True)
The testdata passed to the parametrize function has to be a list. So you need to modify the input data a bit before passing it. I modified the get_test_data function a bit.
#in conftest.py
def get_test_data(filename):
folder_path = os.path.abspath(os.path.dirname(__file__))
folder = os.path.join(folder_path, 'TestData')
jsonfile = os.path.join(folder, filename)
with open(jsonfile) as file:
data = json.load(file)
valid_data = [(item, 1) for item in data['valid_data']]
invalid_data = [(item, 0) for item in data['invalid_data']]
# data below is a list of tuples, with first element in the tuple being the
# arguments for the API call and second element specifies if this is a test
# from valid_data set or invalid_data. This can be used for putting in the
# appropriate assert statements for the API response.
data = valid_data + invalid_data
return data
And now your test function could look like:
#in test_API.py
def test_(test_data):
response = database_api.get_user_info(test_data[0])
# Add appropriate asserts.
# test_data[1] == 1 means 200 response should have been received
# test_data[1] == 0 means 400 response should have been received
I just wrote a package called parametrize_from_file to solve exactly this problem. Here's how it would work for this example:
import parametrize_from_file
# If the JSON file has the same base name as the test module (e.g. "test_api.py"
# and "test_api.json"), this parameter isn't needed.
path_to_json_file = ...
@parametrize_from_file(path_to_json_file, 'valid_data')
def test_valid_data(id, name):
request = dict(id=id, name=name)
response = database_api.get_user_info(request)
assert response.status_code == 200
@parametrize_from_file(path_to_json_file, 'invalid_data')
def test_invalid_data(id, name):
request = dict(id=id, name=name)
response = database_api.get_user_info(request)
assert response.status_code == 400
You could simplify this code a bit by reorganizing the JSON file slightly:
# test_api.json
{
"test_id_name_requests": [
{
"request": {
"id": "1234",
"name": "John"
},
"status_code": 200
},
{
"request": {
"id": "2234",
"name": "Mary"
},
"status_code": 200
},
{
"request": {
"id": "3234",
"name": "Kenny"
},
"status_code": 200
},
{
"request": {
"id": "1234",
"name": "Mary"
},
"status_code": 400
},
{
"request": {
"id": "2234",
"name": "Kenny"
},
"status_code": 400
},
{
"request": {
"id": "3234",
"name": "John"
},
"status_code": 400
},
],
}
With this file, only one test function is needed and no arguments need to be given to the @parametrize_from_file decorator:
# test_api.py
import parametrize_from_file
@parametrize_from_file
def test_id_name_requests(request, status_code):
response = database_api.get_user_info(request)
assert response.status_code == status_code
@hoefling answered my question with quite an easy solution of using the following hooks in conftest:
def pytest_assertrepr_compare(op, left, right):...
def pytest_assertion_pass(item, lineno, orig, expl):...
According to the Pytest documentation, I have to add to pytest.ini enable_assertion_pass_hook=true and erase .pyc files, but other than that it works like a charm. Now I have left and right comparisons with the operator used (pytest_assertrepr_compare) and if the test passed (pytest_assertion_pass).
You could try this:
import inspect
import json
from typing import Callable
import pytest
@pytest.fixture(scope="function")
def send_to_test() -> Callable:
return lambda x: (x ** 2, x ** 3)
# Helper function to run and monitor a test
def run_test(test_func, variable, expected_value, actual_value, data):
try:
with open("./data.json", "r") as f:
data = json.load(f)
except FileNotFoundError:
data = []
data.append(
{
"func": test_func,
"variable": variable,
"expected_value": expected_value,
"actual_value": actual_value,
}
)
with open("./data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
assert actual_value == expected_value
# Reformatted tests
def test_me_1(send_to_test):
# setup
data = []
x, y = send_to_test(3)
# tests
run_test(
test_func=inspect.stack()[0][3],
variable="x",
expected_value=3 ** 2,
actual_value=x,
data=data,
)
run_test(
test_func=inspect.stack()[0][3],
variable="y",
expected_value=3 ** 3,
actual_value=y,
data=data,
)
run_test(
test_func=inspect.stack()[0][3],
variable="y",
expected_value=3 ** 2,
actual_value=y,
data=data,
)
And so, when you run pytest, a new data.jsonfile is created with the expected content:
[
{
"func": "test_me_1",
"variable": "x",
"expected_value": 9,
"actual_value": 9
},
{
"func": "test_me_1",
"variable": "y",
"expected_value": 27,
"actual_value": 27
},
{
"func": "test_me_1",
"variable": "y",
"expected_value": 9,
"actual_value": 27
}
]