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.

Answer from Hanxue on Stack Overflow
🌐
PyPI
pypi.org › project › pytest-json-report
pytest-json-report · PyPI
A pytest plugin to report test results as JSON files
      » pip install pytest-json-report
    
Published   Mar 15, 2022
Version   1.5.0
🌐
PyPI
pypi.org › project › pytest-json
pytest-json · PyPI
A formatted example of the jsonapi output can be found in example_jsonapi.json · Contributions are very welcome. Tests can be run with tox, please ensure the coverage at least stays the same before you submit a pull request. Distributed under the terms of the MIT license, “pytest-json” is free and open source software
      » pip install pytest-json
    
Published   Jan 18, 2016
Version   0.4.0
🌐
Medium
medium.com › grammofy › testing-your-python-api-app-with-json-schema-52677fe73351
Testing Your Python API App with JSON Schema | by Paul Götze | Grammofy | Medium
August 21, 2020 - Let’s suppose we have a simple JSON response for a user endpoint GET /users/:id: Here is an example how you would naively test this response by checking the presence of the properties (client would be a pytest fixture, e.g.
🌐
Pytest with Eric
pytest-with-eric.com › pytest-best-practices › pytest-read-json
5 Easy Ways To Read JSON Input Data In Pytest | Pytest with Eric
January 22, 2026 - Here we’ve used the pathlib module to parse the file but you can also use the json module. The benefit here is we decouple the input from the test module, but you’ll run into test errors if the input file is unavailable. Another way (and one I’m a big fan of) is to provide your input data as a Pytest fixture.
🌐
Serge-m
serge-m.github.io › posts › testing json responses in flask rest apps with pytest
Testing json responses in Flask REST apps with pytest | sergem's personal public notebook
November 27, 2016 - Test for POST endpoint. Checking resulting json: Imaging you have an endpoint that accepts POST requests with multipart files: ... Connextion is a wrapper around Flask that handles Oauth2 security and responses validation.
🌐
GitHub
github.com › numirias › pytest-json-report
GitHub - numirias/pytest-json-report: 🗒️ A pytest plugin to report test results as JSON
This pytest plugin creates test reports as JSON.
Starred by 153 users
Forked by 45 users
Languages   Python 100.0% | Python 100.0%
Top answer
1 of 1
23

Your test regimen might look something like this.

First I suggest creating a fixture to be used in your various method tests. The fixture sets up an instance of your class to be used in your tests rather than creating the instance in the test itself. Keeping tasks separated in this way helps to make your tests both more robust and easier to read.

from my_package import MyClass
import pytest

@pytest.fixture
def a_test_object():
    return MyClass()

You can pass the test object to your series of method tests:

def test_something(a_test_object):
    # do the test

However if your test object requires some resources during setup (such as a connection, a database, a file, etc etc), you can mock it instead to avoid setting up the resources for the test. See this talk for some helpful info on how to do that.

By the way: if you need to test several different states of the user defined object being created in your fixture, you'll need to parametrize your fixture. This is a bit of a complicated topic, but the documentation explains fixture parametrization very clearly.

The other thing you need to do is make sure any .get calls to Requests are intercepted. This is important because it allows your tests to be run without an internet connection, and ensures they do not fail as a result of a bad connection, which is not the thing you are trying to test.

You can intercept Requests.get by using the monkeypatch feature of pytest. All that is required is to include monkeypatch as an input parameter to the test regimen functions.

You can employ another fixture to accomplish this. It might look like this:

import Requests
import pytest

@pytest.fixture
def patched_requests(monkeypatch):
    # store a reference to the old get method
    old_get = Requests.get
    def mocked_get(uri, *args, **kwargs):
        '''A method replacing Requests.get
        Returns either a mocked response object (with json method)
        or the default response object if the uri doesn't match
        one of those that have been supplied.
        '''
        _, id = uri.split('/users/', 1)
        try:
            # attempt to get the correct mocked json method
            json = dict(
            with_address1 = lambda: {'user': {'address': 123}},
            with_address2 = lambda: {'user': {'address': 456}},
            no_address = lambda: {'user': {}},
            no_user = lambda: {},
            )[id]
        except KeyError:
            # fall back to default behavior
            obj = old_get(uri, *args, **kwargs)
        else:
            # create a mocked requests object
            mock = type('MockedReq', (), {})()
            # assign mocked json to requests.json
            mock.json = json
            # assign obj to mock
            obj = mock
        return obj
    # finally, patch Requests.get with patched version
    monkeypatch.setattr(Requests, 'get', mocked_get)

This looks complicated until you understand what is happening: we have simply made some mocked json objects (represented by dictionaries) with pre-determined user ids and addresses. The patched version of Requests.get simply returns an object- of type MockedReq- with the corresponding mocked .json() method when its id is requested.

Note that Requests will only be patched in tests that actually use the above fixture, e.g.:

def test_something(patched_requests):
    # use patched Requests.get

Any test that does not use patched_requests as an input parameter will not use the patched version.

Also note that you could monkeypatch Requests within the test itself, but I suggest doing it separately. If you are using other parts of the requests API, you may need to monkeypatch those as well. Keeping all of this stuff separate is often going to be easier to understand than including it within your test.

Write your various method tests next. You'll need a different test for each aspect of your method. In other words, you will usually write a different test for the instance in which your method succeeds, and another one for testing when it fails.

First we test method success with a couple test cases.

@pytest.mark.parametrize('id, result', [
    ('with_address1', 123),
    ('with_address2', 456),
])
def test_get_user_info_success(patched_requests, a_test_object, id, result):
    address = a_test_object.get_user_info(id)
    assert address == result

Next we can test for raising the BadId exception using the with pytest.raises feature. Note that since an exception is raised, there is not a result input parameter for the test function.

@pytest.mark.parametrize('id', [
    'no_address',
    'no_user',
])
def test_get_user_info_failure(patched_requests, a_test_object, id):
    from my_package import BadId
    with pytest.raises(BadId):
        address = a_test_object.get_user_info(id)

As posted in my comment, here also are some additional resources to help you learn more about pytest:

link

link

Also be sure to check out Brian Okken's book and Bruno Oliveira's book. They are both very helpful for learning pytest.

Find elsewhere
Top answer
1 of 2
5

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

2 of 2
1

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
🌐
Blogger
hanxue-it.blogspot.com › 2017 › 10 › pytest-testing-and-comparing-json-response-using-pytest-flask.html
Hanxue and IT: Pytest: Testing and Comparing JSON Response Using Pytest-Flask
October 18, 2017 - Let's say we have a Flask API endpoint that returns this JSON { "message": "Staff name and password pair not match", "errors": {
🌐
automation hacks
automationhacks.io › home › 2020 › 12 › 25
Python API test automation framework (Part 5) Working with JSON and JsonPath - automation hacks
December 25, 2020 - @pytest.fixture def create_data(): payload = read_file('create_person.json') random_no = random.randint(0, 1000) last_name = f'Olabini{random_no}' payload['lname'] = last_name yield payload def test_person_can_be_added_with_a_json_template(create_data): create_person_with_unique_last_name(create_data) response = requests.get(BASE_URI) peoples = loads(response.text) # Get all last names for any object in the root array # Here $ = root, [*] represents any element in the array # Read full syntax: https://pypi.org/project/jsonpath-ng/ jsonpath_expr = parse("$.[*].lname") result = [match.value for
🌐
FastAPI
fastapi.tiangolo.com › tutorial › testing
Testing - FastAPI
Write simple assert statements with the standard Python expressions that you need to check (again, standard pytest). ... from fastapi import FastAPI from fastapi.testclient import TestClient app = FastAPI() @app.get("/") async def read_main(): return {"msg": "Hello World"} client = TestClient(app) def test_read_main(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"msg": "Hello World"}
🌐
GitHub
github.com › aio-libs › aiohttp › issues › 2239
response.json() in testing with pytest using uvloop · Issue #2239 · aio-libs/aiohttp
September 4, 2017 - from app import make_app async def test_hello(test_client, loop): app = make_app() client = await test_client(app) # resp = await client.get('/', headers={'content-type': 'application/json'}) # data = await resp.json(content_type='application/json') resp = await client.get('/') data = await resp.json() assert resp.status == 200 assert data is not None assert type(data) is list
Author   dennypenta
🌐
GitHub
github.com › getsentry › responses
GitHub - getsentry/responses: A utility for mocking out the Python Requests library. · GitHub
import responses import requests @responses.activate def test_simple(): # Register via 'Response' object rsp1 = responses.Response( method="PUT", url="http://example.com", ) responses.add(rsp1) # register via direct arguments responses.add( responses.GET, "http://twitter.com/api/1/foobar", json={"error": "not found"}, status=404, ) resp = requests.get("http://twitter.com/api/1/foobar") resp2 = requests.put("http://example.com") assert resp.json() == {"error": "not found"} assert resp.status_code == 404 assert resp2.status_code == 200 assert resp2.request.method == "PUT" If you attempt to fetch a url which doesn't hit a match, responses will raise a ConnectionError: import responses import requests from requests.exceptions import ConnectionError @responses.activate def test_simple(): with pytest.raises(ConnectionError): requests.get("http://twitter.com/api/1/foobar")
Starred by 4.3K users
Forked by 362 users
Languages   Python
🌐
Rip Tutorial
riptutorial.com › testing a json api implemented in flask
Flask Tutorial => Testing a JSON API implemented in Flask
We can test it with pytest · # test_hello_add.py from hello_add import app from flask import json def test_add(): response = app.test_client().post( '/add', data=json.dumps({'a': 1, 'b': 2}), content_type='application/json', ) data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert data['sum'] == 3 ·
🌐
Qabash
qabash.com › practical-json-patterns-api-to-assertions-in-pytest
Practical JSON Patterns: API to Assertions in PyTest - QAbash.com
August 13, 2025 - ❌ Hardcoding response keys Fix: Always check if key exists using response.get(“key”, default) ❌ Unclear or long assertions Fix: Use helper functions or custom matchers for clarity · ❌ Ignoring optional fields Fix: Validate presence using if conditions or use try/except for optional data · ❌ Comparing entire JSON blobs Fix: Compare only required parts using JMESPath or DeepDiff · ❌ No retry mechanism for flaky APIs Fix: Use PyTest retries with exponential backoff for unstable endpoints
🌐
On Test Automation
ontestautomation.com › writing-tests-for-restful-apis-in-python-using-requests-part-1-basic-tests
Writing tests for RESTful APIs in Python using requests – part 1: basic tests | On Test Automation
December 12, 2019 - def test_get_locations_for_us_90210_check_content_type_equals_json(): response = requests.get("http://api.zippopotam.us/us/90210") assert response.headers["Content-Type"] == "application/json" In the response object, the headers are available as a dictionary (a list of key-value pairs) headers, which makes extracting the value for a specific header a matter of supplying the right key (the header name) to obtain its value. We can then assert on its value using a pytest assertion and the expected value of application/json.
🌐
NashTech Blog
blog.nashtechglobal.com › home › api testing with pytest and python requests: a beginner’s guide
API Testing with Pytest and Python Requests: A Beginner’s Guide - NashTech Blog
December 5, 2024 - Pytest supports “assert” function so that we can validate the response from the API. ... We can get the JSON response payload by using “json.loads(response.request.body)” and use “assert” to validate response payload with payload