Fixtures in pytest are very versatile, combine them with closures they become very powerful. Let us write a fixture for creating an API request body dynamically using a combination of a closure fixture.
The API we are using is a POST endpoint at https://gorest.co.in/public/v1/user. The API creates a new user in the system. the request body looks something like this.
{ "name": "name", "email": "dummy@email.com", "status": "active", "gender": "male" }
Now if we have to create multiple users, we can use the parameterize
construct to manage the creation of users.
But the question is, how to provide the request body to the tests. Well, the easier approach would be to simply add a payload for every request in the parameterize call.
@pytest.mark.parameterize("request", [ ({ "name": "name", "email": "dummy@email.com", "status": "active", "gender": "male" }, { "name": "name2", "email": "dummy2@email.com", "status": "active", "gender": "male" }) ]) def test_create_user(build_request_body, base_url, authorization_token, request_body): url = f"{base_url}/users" header = {"Authorization": authorization_token} response = requests.post(url=url, data=request_body, headers=header) assert response.status_code == 200
This approach makes the test very clumsy and is not recommended and unmaintainable. Another approach is to create a closure fixture to dynamically generate the request body. We can leverage the conftest.py file to create this fixture.
@pytest.fixture(scope='session') def build_request_body(): def _build(name, email, status, gender): return { "name": name, "email": email, "status": status, "gender": gender } return _build
Observing the code, we can see that a function is wrapped inside another function. Also, the inner function is taking all the arguments necessary for building the request. This is a closure pattern.
During the test run, pytest evaluates this fixture and provides the inner function as a callable function object to the test. We can then call this function with the request params received from parameterized calls and dynamically build the request body.
Let us now look at the refactored test after the introduction of the build_request_body
fixture.
@pytest.mark.parametrize(("name", "email", "status", "gender"), [ ("mark", "mark@smith.com", "active", "male"), ("steve", 'steve@email.com', "active", "male") ]) def test_create_user(build_request_body, base_url, authorization_token, name, email, status, gender): api_request = build_request_body(name=name, email=email, status=status, gender=gender) url = f"{base_url}/users" header = {"Authorization": authorization_token} response = requests.post(url=url, data=api_request, headers=header) assert response.status_code == 200
Now the tests are much more manageable, if we want to improve the code further, we can inject the test data from a json file
Complete code will look something like this.
conftest.py
@pytest.fixture(scope='session') def build_request_body(): def _build(name, email, status, gender): return { "name": name, "email": email, "status": status, "gender": gender } return _build @pytest.fixture(scope='session') def base_url(): return "https://gorest.co.in/public/v1" @pytest.fixture(scope="session") def authorization_token(): return "Bearer YOUR_BEARER_TOKEN"
gorest.py
@pytest.mark.parametrize(("name", "email", "status", "gender"), [ ("mark", "mark@smith.com", "active", "male"), ("steve", 'steve@email.com', "active", "male") ]) def test_create_user(build_request_body, base_url, authorization_token, name, email, status, gender): api_request = build_request_body(name=name, email=email, status=status, gender=gender) url = f"{base_url}/users" header = {"Authorization": authorization_token} response = requests.post(url=url, data=api_request, headers=header) assert response.status_code == 201
Quick Links
Legal Information
Social Media