feat: add decorator for writing component tests (#1008)

* feat: add decorator for writing component tests

* refactor: udpate changelog + update deps pins

* refactor: fix deps

* refactor: make cached_ref into generic and fix linter errors

* refactor: fix coverage testing

* refactor: use global var instead of env var for is_testing state
This commit is contained in:
Juro Oravec 2025-03-02 19:46:12 +01:00 committed by GitHub
parent 81ac59f7fb
commit 7dfcb447c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 4428 additions and 3661 deletions

View file

@ -8,4 +8,5 @@ nav:
- Typing and validation: typing_and_validation.md
- Custom template tags: template_tags.md
- Tag formatters: tag_formatter.md
- Testing: testing.md
- Authoring component libraries: authoring_component_libraries.md

View file

@ -0,0 +1,111 @@
_New in version 0.131_
The [`@djc_test`](../../../reference/testing_api#djc_test) decorator is a powerful tool for testing components created with `django-components`. It ensures that each test is properly isolated, preventing components registered in one test from affecting others.
## Usage
The [`@djc_test`](../../../reference/testing_api#djc_test) decorator can be applied to functions, methods, or classes. When applied to a class, it recursively decorates all methods starting with `test_`, including those in nested classes. This allows for comprehensive testing of component behavior.
### Applying to a Function
To apply [`djc_test`](../../../reference/testing_api#djc_test) to a function,
simply decorate the function as shown below:
```python
import django
from django_components.testing import djc_test
django.setup()
@djc_test
def test_my_component():
@register("my_component")
class MyComponent(Component):
template = "..."
...
```
### Applying to a Class
When applied to a class, `djc_test` decorates each `test_` method individually:
```python
import django
from django_components.testing import djc_test
django.setup()
@djc_test
class TestMyComponent:
def test_something(self):
...
class Nested:
def test_something_else(self):
...
```
This is equivalent to applying the decorator to each method individually:
```python
import django
from django_components.testing import djc_test
django.setup()
class TestMyComponent:
@djc_test
def test_something(self):
...
class Nested:
@djc_test
def test_something_else(self):
...
```
### Arguments
See the API reference for [`@djc_test`](../../../reference/testing_api#djc_test) for more details.
### Setting Up Django
Before using [`djc_test`](../../../reference/testing_api#djc_test), ensure Django is set up:
```python
import django
from django_components.testing import djc_test
django.setup()
@djc_test
def test_my_component():
...
```
## Example: Parametrizing Context Behavior
You can parametrize the [context behavior](../../../reference/settings#django_components.app_settings.ComponentsSettings.context_behavior) using [`djc_test`](../../../reference/testing_api#djc_test):
```python
from django_components.testing import djc_test
@djc_test(
# Settings applied to all cases
components_settings={
"app_dirs": ["custom_dir"],
},
# Parametrized settings
parametrize=(
["components_settings"],
[
[{"context_behavior": "django"}],
[{"context_behavior": "isolated"}],
],
["django", "isolated"],
)
)
def test_context_behavior(components_settings):
rendered = MyComponent().render()
...
```

View file

@ -11,3 +11,4 @@ nav:
- Template tags: template_tags.md
- Template vars: template_vars.md
- URLs: urls.md
- Testing API: testing_api.md

View file

@ -0,0 +1,9 @@
<!-- Autogenerated by reference.py -->
# Testing API
::: django_components.testing.djc_test
options:
show_if_no_docstring: true

View file

@ -108,6 +108,40 @@ def gen_reference_api():
f.write("\n")
def gen_reference_testing_api():
"""
Generate documentation for the Python API of `django_components.testing`.
This takes all public symbols exported from `django_components.testing`.
"""
module = import_module("django_components.testing")
preface = "<!-- Autogenerated by reference.py -->\n\n"
preface += (root / "docs/templates/reference_testing_api.md").read_text()
out_file = root / "docs/reference/testing_api.md"
out_file.parent.mkdir(parents=True, exist_ok=True)
with out_file.open("w", encoding="utf-8") as f:
f.write(preface + "\n\n")
for name, obj in inspect.getmembers(module):
if (
name.startswith("_")
or inspect.ismodule(obj)
):
continue
# For each entry, generate a mkdocstrings entry, e.g.
# ```
# ::: django_components.testing.djc_test
# options:
# show_if_no_docstring: true
# ```
f.write(f"::: {module.__name__}.{name}\n" f" options:\n" f" show_if_no_docstring: true\n")
f.write("\n")
def gen_reference_exceptions():
"""
Generate documentation for the Exception classes included in the Python API of `django_components`.
@ -739,6 +773,7 @@ def gen_reference():
gen_reference_templatetags()
gen_reference_templatevars()
gen_reference_signals()
gen_reference_testing_api()
# This is run when `gen-files` plugin is run in mkdocs.yml

View file

@ -0,0 +1 @@
# Testing API