I am attempting to test various endpoints of my REST API. Some of the end points take values that are provided by other end points. For example:
/locations
to get a list of available locations and if they are enabled/inventory?loc_id=<id>
and pass in a location ID to get a list of inventory at specific location/inventory/<id>/details
to get a list of attributes associated with one particular inventory IDIn my tests, I want to walk this entire work flow (checking specific attributes of inventory items at specific locations). Normally, I'd build a pytest function with a couple @parameterize
decorators, but in this case, I don't know all the IDs ahead of time.
What I'd normally do:
@pytest.mark.parametrize('location', ['1', '2', '3', 'HQ'])
@pytest.mark.parametrize('inventory_id', [1, 2, 3])
def test_things(location, inventory_id):
# Call /inventory/inventory_id/details and check attributes
That second line is a problem because I don't know the inventory_id
s without calling /inventory
first. It's also entirely possible that the inventory_id
isn't available at a specific location.
What I'd like to do:
Query /location to build a list of IDs to add to the first parameterize line
Query `/inventory?loc_id=<id>` and build a list of IDs to pass to the second parameterize line
How can I dynamically build these lines?
pytest enables test parametrization at several levels: pytest.fixture () allows one to parametrize fixture functions. @pytest.mark.parametrize allows one to define multiple sets of arguments and fixtures at the test function or class.
# The `request` fixture in particular contains the `params` data! A similar refactoring would apply to the activity test input. Once we refactored the test inputs into dedicated fixtures, the pytest.mark.parametrize decorators can be removed—with the test run itself staying as-is.
pytest will build a string that is the test ID for each set of values in a parametrized test. These IDs can be used with -k to select specific cases to run, and they will also identify the specific case when one is failing. Running pytest with --collect-only will show the generated IDs.
In order to achieve multiple invocations of any test using our new fixtures, we pass our sample data to the params parameter of pytest.fixture. The fixture-version of our friend test input then looks as follow: # The `request` fixture in particular contains the `params` data! A similar refactoring would apply to the activity test input.
If it's really the case that you want to test each location with each inventory_id you can just calculate those lists ahead of test
def get_locations_list(...):
locations = []
# query locations
...
return locations
LOCATIONS = get_locations_list()
INVENTORY_IDS = get_inventory_ids()
@pytest.mark.parametrize('location', LOCATIONS)
@pytest.mark.parametrize('inventory_id', INVENTORY_IDS)
def test_things(location, inventory_id):
# test stuff
If inventory ids depend on location then you can prepare list of tuples:
def get_locations_and_ids():
list_of_tuples = []
...
for location in locations:
ids = ...
for id in ids:
list_of_tuples.append( (location, id) )
return list_of_tuples
LIST_OF_TUPLES = get_locations_and_ids()
@pytest.mark.parametrize(('location', 'inventory_id'), LIST_OF_TUPLES)
def test_things(location, inventory_id):
# ...
You can also use pytest-generate-tests pattern as described in:
https://docs.pytest.org/en/latest/parametrize.html#basic-pytest-generate-tests-example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With