5 Easy Ways To Read JSON Input Data In Pytest
Javascript Object Notation (JSON) is arguably one of the most popular data exchange formats over the web.
Web services use serialisation to convert data from low-level data structures to JSON format that allows receiving services to easily deserialise it.
When writing Unit tests the need for testing JSON input and outputs is one of high importance.
Test data, API Responses and sometimes even config files are defined in JSON which makes it necessary to understand how to read and write to it, using Pytest.
In this article, we’ll explore 5 easy ways to read JSON data in Pytest so you can use it to validate input test data, config files, API responses and much more.
Let’s get into it.
Objectives
By the end of this tutorial you should be able to:
- Parse JSON data in Pytest using 5 different ways
- Understand how to use Pytest fixtures and parameters to parse JSON data into your unit tests
Project Set Up
The project has the following structure
Getting Started
To get started, clone the repo here, or create one by making a folder and running git init
to initialise it.
Prerequisites
In this project, we’ll be using Python 3.10.10.
Create a virtual environment and install the requirements (packages) using1
pip install -r requirements.txt
No special libraries are required here, just pytest and a lint formatter.
Now let’s look at 5 different ways to parse JSON inputs in Pytest.
Source Code
Our source code is an extremely simple example, that takes a JSON string input and calculates the birth_year
from the age
field.
read_json/core.py
1
2
3
4
5
6
7
8
9
10import json
def compute_birth_year(input_data: str):
# Parse the JSON data into a Python dictionary
data = json.loads(input_data)
# Compute birth year
birth_year = 2023 - data["age"]
return f"{data['name']} was born in {birth_year}"
Let’s look at how to test this in a few different ways.
1. Define JSON in Test
One of the easiest ways to test this is to define the input JSON in our Unit Test.
tests/unit/test_read_json.py
1
2
3
4def test_compute_birth_year_1():
input_data = '{"name": "John", "age": 30, "city": "New York"}'
expected = "John was born in 1993"
assert compute_birth_year(input_data) == expected
While this is easy, it offers less flexibility.
If you wish to run your test with new JSON data, we have to make changes to our test module (which is inconvenient).
2. Read An External JSON File
An interesting and convenient way to read a JSON file is to keep it separate, outside the test module itself.
This way you can make changes to it dynamically, without changing the tests themselves.
tests/unit/test_read_json.py
1
2
3
4
5
6def test_compute_birth_year_2():
file = pathlib.Path("tests/unit/test_data/input1.json")
with open(file) as f:
input_data = f.read()
expected = "Jerry was born in 1968"
assert compute_birth_year(input_data) == expected
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.
3. Provide JSON Data as a Fixture
Another way (and one I’m a big fan of) is to provide your input data as a Pytest fixture.
If you’re not familiar with pytest fixtures and how they work, this article provides an excellent base.
In short, fixtures are Pytest functions (often defined in conftest.py
) that can be easily used across one or more test modules and state-controlled via the scope
parameter.
We define the JSON fixture
tests/unit/conftest.py
1
2
3
def input_json():
return json.dumps({"name": "Eric", "age": 32, "city": "London"})
Now we can use it within our unit test.
tests/unit/test_read_json.py
1
2
3def test_compute_birth_year_3(input_json):
expected = "Eric was born in 1991"
assert compute_birth_year(input_json) == expected
4. Define JSON Using Pytest Parameters
You can also make use of parameters within pytest to define test inputs.
For this, we use the @pytest.mark.parametrize
marker. Let’s take a look.
tests/unit/test_read_json.py
1
2
3
4
5
6
)
def test_compute_birth_year_4(input_data, expected):
assert compute_birth_year(input_data) == expected
Within the marker, we define our input data and expected value, then define a list of input values (each element being a tuple).
Pytest will pass this list of input values to the test. Pretty neat!
5. Provide JSON At Runtime
The final way to parse JSON to your unit tests is to provide it at runtime, as a CLI input.
To do this, we define a couple of fixtures within conftest.py
.
conftest.py
1
2
3
4
5
6
7
8def pytest_addoption(parser):
parser.addoption(
"--input-json", action="store", default='{"name":"Rick", "age": 50, "city": "NY"}', help="Input JSON"
)
def input_json_command_line(request):
return request.config.getoption("--input-json")
We use the inbuilt parser to parse the flag --input-json
to pytest, specify a default value and lastly, use that as a fixture.
We can then use it within the unit test.
tests/unit/test_read_json.py
1
2
3
def input_json_command_line(request):
return request.config.getoption("--input-json")
This approach although easy, doesn’t scale well especially if you need to parse multiple JSONs.
Running The Unit Test
To run the unit tests, simply run1
pytest tests/unit/test_read_json.py -v -s --input-json="{'name': 'Ravi', 'age': 90, 'city': 'New Delhi'}"
Conclusion
In this article, we looked at 5 ways to parse JSON into your unit tests.
Knowing how to parse JSON is particularly important when writing unit, integration, end-end or API testing as almost all data exchange is via JSON format.
You learnt various techniques including the use of fixtures and parameters, passing JSON via external files and the CLI.
Equipped with this knowledge, you can now efficiently parse JSON data in your Python Unit Tests.
If you have ideas for improvement or like for me to cover anything specific, please send me a message via Twitter, GitHub or Email.
Till the next time… Cheers!