A pytest plugin for creating fixtures that holds different forms between tests.
Project description
pytest-fixture-forms
A powerful pytest plugin that simplifies testing different forms of parameters through dynamic fixture generation. This plugin is particularly useful for API testing, integration testing, or any scenario where you need to verify behavior across multiple parameter variations.
Key Features
- Automatically generates fixtures based on class methods
- Supports dynamic test generation for parameter combinations
- Integrates seamlessly with pytest's parametrization
- Handles nested fixture dependencies elegantly
- Reduces boilerplate in test code
Installation
Install from PyPI:
pip install pytest-fixture-forms
Quick Start
Here's a simple example showing how to use pytest-fixture-forms:
from pytest_fixture_forms import FixtureForms
import pytest
class UserCredentials(FixtureForms):
@pytest.fixture
def valid_user(self):
return {"username": "john_doe", "password": "secure123"}
@pytest.fixture
def invalid_password(self):
return {"username": "john_doe", "password": "wrong"}
@pytest.fixture
def missing_username(self):
return {"username": "", "password": "secure123"}
def test_login(user_credentials):
# This test will run for each form defined in UserCredentials
response = login_service.authenticate(**user_credentials.value)
if user_credentials.form == "valid_user":
assert response.status_code == 200
else:
assert response.status_code == 401
Understanding FixtureForms
When you create a class that inherits from FixtureForms, the plugin automatically generates several fixtures:
<class_name_snake_case>- Returns an instance containing the current form and value<class_name_snake_case>_form- The name of the current form being tested<class_name_snake_case>_<form_name>- The value for a specific form
For example, given a class named ApiEndpoint:
class ApiEndpoint(FixtureForms):
@pytest.fixture
def get_users(self):
return "/api/v1/users"
@pytest.fixture
def create_user(self):
return "/api/v1/users/create"
def test_endpoint(api_endpoint):
# api_endpoint.form will be either "get_users" or "create_user"
# api_endpoint.value will be the corresponding URL
response = client.request("GET", api_endpoint.value)
Advanced Usage
Combining Multiple Forms
You can use multiple FixtureForms classes in a single test to test combinations:
class RequestMethod(FixtureForms):
@pytest.fixture
def get(self):
return "GET"
@pytest.fixture
def post(self):
return "POST"
class ApiPath(FixtureForms):
@pytest.fixture
def users(self):
return "/users"
@pytest.fixture
def products(self):
return "/products"
def test_api_combinations(request_method, api_path):
# This will generate tests for all combinations:
# GET /users
# GET /products
# POST /users
# POST /products
response = client.request(request_method.value, api_path.value)
Using with Parametrization
You can control which forms to test using pytest's parametrize:
@pytest.mark.parametrize("request_method_form", ["get"]) # Only test GET requests
@pytest.mark.parametrize("api_path_form", ["users", "products"])
def test_specific_combinations(request_method, api_path):
response = client.request(request_method.value, api_path.value)
Accessing Fixture Values
Each FixtureForms instance provides:
form- The current form namevalue- The value returned by the current form's fixturerequest- The pytest fixture request object
Working with Dependencies
Forms can depend on other fixtures:
class AuthenticatedEndpoint(FixtureForms):
@pytest.fixture
def user_profile(self, auth_token): # Depends on auth_token fixture
return f"/api/v1/profile", {"Authorization": auth_token}
@pytest.fixture
def auth_token():
return "Bearer xyz123"
How It Works
The plugin uses pytest's collection hooks to:
- Dynamically register fixtures based on
FixtureFormsclass methods - Generate test nodes for each combination of parameters
- Handle fixture dependencies and parametrization
Best Practices
- Keep form methods focused on a single variation
- Use clear, descriptive names for forms
- Group related forms in a single class
- Consider using parametrization to control test combinations
- Document expected behavior for each form
Understanding Instance Fixtures Lifecycle
When you create a class that inherits from FixtureForms, the plugin generates several instance-related fixtures that
work together to provide a robust testing framework. Let's understand these fixtures and their relationships using an
example:
class KeyId(FixtureForms):
@pytest.fixture
def arn(self):
return "arn:aws:123"
@pytest.fixture
def id(self):
return "123"
Instance Fixture Hierarchy
For the KeyId class above, the following instance fixtures are created:
-
key_id_initial_prototype- The most basic instance fixture
- Not parameterized
- Neither
formnorvalueare set - Used internally to create the base instance that will be passed to form methods
- Useful when form methods or dependent fixtures need early access to the instance
-
key_id_prototype- Built from
key_id_initial_prototype - Parameterized with forms ("arn", "id")
- Has
formset but novalue - Used when you need access to the instance and form name before the value is computed
- Helpful for fixtures that depend on the form but not the value
- Built from
-
key_id- The final, fully initialized instance
- Built from
key_id_prototype - Has both
formandvalueset - The value is computed by calling the corresponding form method
- This is typically what you'll use in your tests
Example: Working with Instance Fixtures
Here's how you might use different instance fixtures:
class KeyId(FixtureForms):
@pytest.fixture
def arn(self, set_region):
# self is an instance of KeyId with form="arn", and region="us-east-1" was set because we requested the set_region fixture
return f"arn:aws:{self.region}"
@pytest.fixture
def id(self):
return "123"
@pytest.fixture
def set_region(key_id_prototype):
# We can access the form before the value is computed
if key_id_prototype.form == "arn":
key_id_prototype.region = "us-east-1"
def test_key_id(key_id):
# key_id has both form and value set
assert key_id.form in ["arn", "id"]
if key_id.form == "arn":
assert key_id.value == "arn:aws:us-east-1"
else:
assert key_id.value == "123"
Instance Fixture Flow
The lifecycle of a FixtureForms instance follows this sequence:
key_id_initial_prototypecreates the base instancekey_id_prototypesets the form based on parametrization- Form method is called with the prototype instance as
self key_idreceives the computed value and becomes the final instance
This design allows for complex dependencies and interactions between fixtures while maintaining clarity and preventing circular dependencies.
Understanding test nodes generation
Take a look at this example test. It demonstrates how the plugin handles complex parameter combinations. The test uses 3 different parameters, each having 3 possible forms, resulting in 27 unique test nodes (3 x 3 x 3 combinations).
What makes this plugin special is its handling of parametrized forms. When a form is parametrized, it only multiplies the test nodes for that specific parameter form, not all combinations. This is different from pytest's standard parametrization and is why we generate test nodes dynamically.
Why this approach? In standard pytest, when a fixture is parametrized within a test node, those parameters become permanently linked to that node. This creates unwanted coupling between different parameter forms. By generating a unique node for each combination of parameter forms, we avoid this coupling and maintain independence between different parameter variations.
running the test would look like this:
Advanced Customization
you can also create a base class that inherits from FixtureForms and add custom behavior:
class AdvancedFixtureForms(FixtureForms):
def __init__(self, *args, **kwargs):
self.custom_form_property = None
super().__init__(*args, **kwargs)
then in your tests:
class SomeFixtureForm(AdvancedFixtureForms):
@pytest.fixture
def form1(self, set_custom_form_property):
assert self.custom_form_property == "custom form property value"
return "1"
@pytest.fixture
def set_custom_form_property(some_fixture_form_prototype: SomeFixtureForm):
some_fixture_form_prototype.custom_form_property = "custom form property value"
def test_advanced_fixture_forms(some_fixture_form: SomeFixtureForm):
assert some_fixture_form.custom_form_property == "custom form property value"
assert some_fixture_form.value == "1"
print(some_fixture_form)
Contributing
Contributions are welcome! This is a new project and there might be bugs or missing features. If you have any suggestions, bug reports, or feature requests, please open an issue or submit a pull request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_fixture_forms-0.2.1.tar.gz.
File metadata
- Download URL: pytest_fixture_forms-0.2.1.tar.gz
- Upload date:
- Size: 17.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.10.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
597c538a17786a363cf477d7ec62d37be5aab99640acb1c8993cbe381ae41268
|
|
| MD5 |
b0f6319556eb45040d829fb165292456
|
|
| BLAKE2b-256 |
8ec31a0535d68b658cb653ef740cce0acd563ee1e249c0fa8d5d19043b782a88
|
File details
Details for the file pytest_fixture_forms-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pytest_fixture_forms-0.2.1-py3-none-any.whl
- Upload date:
- Size: 16.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.10.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
209d6e3b1e3662baa9be299e97924155d21d14cd8c849e17fe605633f42cd2db
|
|
| MD5 |
5a5e0b17c6db1fd6c64abfa7facd6838
|
|
| BLAKE2b-256 |
257eb9ac9db0be991d6c29f77c8b714174c6caa7a3df38832a87dfcde7c281a0
|