mirror of
https://github.com/jazzband/django-constance.git
synced 2026-03-16 22:40:24 +00:00
Allow override_config for pytest (#338)
* provides: base override class; unittest and pytest overrides * raise invalid config error earlier * update AUTHORS * avoid AttributeError * fix comment * add tests * fix tests, update docstring * update docs, improve tests * fix docs * fix markdown * refactor pytest override, use hidden fixture, refactor base and unittest classes * improve docstring and error * refactor pytest override to use hooks * set minimum pytest version * revert empty lines removal * introduce pytest test runner for package, refactoring * WIP * Finalize tox config, refactor docs, add global fixture * skip py35 * pytest command: remove unnecessary ignore * address comments * Update constance/test/pytest.py * address comments * add test for checking nested markers Co-authored-by: Camilo Nova <camilo.nova@gmail.com> Co-authored-by: Paweł Zarębski <ppjzarebski@gmail.com>
This commit is contained in:
parent
4de4114bbd
commit
bd8041c55f
10 changed files with 247 additions and 8 deletions
|
|
@ -1,6 +1,8 @@
|
|||
[run]
|
||||
source = constance
|
||||
branch = 1
|
||||
omit =
|
||||
*/pytest.py
|
||||
|
||||
[report]
|
||||
omit = *tests*,*migrations*
|
||||
omit = *tests*,*migrations*,.tox/*,setup.py,*settings.py
|
||||
|
|
|
|||
1
AUTHORS
1
AUTHORS
|
|
@ -40,6 +40,7 @@ saw2th <stephen@saw2th.co.uk>
|
|||
trbs <trbs@trbs.net>
|
||||
vl <1844144@gmail.com>
|
||||
vl <vl@u64.(none)>
|
||||
Vladas Tamoshaitis <amd.vladas@gmail.com>
|
||||
Dmitriy Tatarkin <mail@dtatarkin.ru>
|
||||
Alexandr Artemyev <mogost@gmail.com>
|
||||
Elisey Zanko <elisey.zanko@gmail.com>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
from .utils import override_config
|
||||
from .unittest import override_config # pragma: no cover
|
||||
|
|
|
|||
79
constance/test/pytest.py
Normal file
79
constance/test/pytest.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
"""
|
||||
Pytest constance override config plugin.
|
||||
|
||||
Inspired by https://github.com/pytest-dev/pytest-django/.
|
||||
"""
|
||||
import pytest
|
||||
from contextlib import ContextDecorator
|
||||
from constance import config as constance_config
|
||||
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_configure(config): # pragma: no cover
|
||||
"""
|
||||
Register override_config marker.
|
||||
"""
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
(
|
||||
"override_config(**kwargs): "
|
||||
"mark test to override django-constance config"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_call(item): # pragma: no cover
|
||||
"""
|
||||
Validate constance override marker params. Run test with overrided config.
|
||||
"""
|
||||
marker = item.get_closest_marker("override_config")
|
||||
if marker is not None:
|
||||
if marker.args:
|
||||
pytest.fail(
|
||||
"Constance override can not not accept positional args"
|
||||
)
|
||||
with override_config(**marker.kwargs):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
class override_config(ContextDecorator):
|
||||
"""
|
||||
Override config while running test function.
|
||||
|
||||
Act as context manager and decorator.
|
||||
"""
|
||||
def enable(self):
|
||||
"""
|
||||
Store original config values and set overridden values.
|
||||
"""
|
||||
for key, value in self._to_override.items():
|
||||
self._original_values[key] = getattr(constance_config, key)
|
||||
setattr(constance_config, key, value)
|
||||
|
||||
def disable(self):
|
||||
"""
|
||||
Set original values to the config.
|
||||
"""
|
||||
for key, value in self._original_values.items():
|
||||
setattr(constance_config, key, value)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._to_override = kwargs.copy()
|
||||
self._original_values = {}
|
||||
|
||||
def __enter__(self):
|
||||
self.enable()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.disable()
|
||||
|
||||
|
||||
@pytest.fixture(name="override_config")
|
||||
def _override_config():
|
||||
"""
|
||||
Make override_config available as a function fixture.
|
||||
"""
|
||||
return override_config
|
||||
|
|
@ -38,3 +38,73 @@ method level and also as a
|
|||
def test_what_is_your_favourite_color(self):
|
||||
with override_config(YOUR_FAVOURITE_COLOR="Blue?"):
|
||||
self.assertEqual(config.YOUR_FAVOURITE_COLOR, "Blue?")
|
||||
|
||||
|
||||
Pytest usage
|
||||
~~~~~
|
||||
|
||||
Django-constance provides pytest plugin that adds marker
|
||||
:class:`@pytest.mark.override_config()`. It handles config override for
|
||||
module/class/function, and automatically revert any changes made to the
|
||||
constance config values when test is completed.
|
||||
|
||||
.. py:function:: pytest.mark.override_config(**kwargs)
|
||||
|
||||
Specify different config values for the marked tests in kwargs.
|
||||
|
||||
Module scope override
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytestmark = pytest.mark.override_config(API_URL="/awesome/url/")
|
||||
|
||||
def test_api_url_is_awesome():
|
||||
...
|
||||
|
||||
Class/function scope
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from constance import config
|
||||
|
||||
@pytest.mark.override_config(API_URL="/awesome/url/")
|
||||
class SomeClassTest:
|
||||
def test_is_awesome_url(self):
|
||||
assert config.API_URL == "/awesome/url/"
|
||||
|
||||
@pytest.mark.override_config(API_URL="/another/awesome/url/")
|
||||
def test_another_awesome_url(self):
|
||||
assert config.API_URL == "/another/awesome/url/"
|
||||
|
||||
If you want to use override as a context manager or decorator, consider using
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from constance.test.pytest import override_config
|
||||
|
||||
def test_override_context_manager():
|
||||
with override_config(BOOL_VALUE=False):
|
||||
...
|
||||
# or
|
||||
@override_config(BOOL_VALUE=False)
|
||||
def test_override_context_manager():
|
||||
...
|
||||
|
||||
Pytest fixture as function or method parameter (
|
||||
NOTE: no import needed as fixture is available globally)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_api_url_is_awesome(override_config):
|
||||
with override_config(API_URL="/awesome/url/"):
|
||||
...
|
||||
|
||||
Any scope, auto-used fixture alternative can also be implemented like this
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True) # e.g. module scope
|
||||
def api_url(override_config):
|
||||
with override_config(API_URL="/awesome/url/"):
|
||||
yield
|
||||
|
||||
|
|
|
|||
7
setup.py
7
setup.py
|
|
@ -56,5 +56,10 @@ setup(
|
|||
extras_require={
|
||||
'database': ['django-picklefield'],
|
||||
'redis': ['redis'],
|
||||
}
|
||||
},
|
||||
entry_points={
|
||||
'pytest11': [
|
||||
'pytest-django-constance = constance.test.pytest',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
|
|
|||
78
tests/test_pytest_overrides.py
Normal file
78
tests/test_pytest_overrides.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import unittest
|
||||
|
||||
|
||||
try:
|
||||
import pytest
|
||||
|
||||
from constance import config
|
||||
from constance.test.pytest import override_config
|
||||
|
||||
|
||||
class TestPytestOverrideConfigFunctionDecorator:
|
||||
"""Test that the override_config decorator works correctly for Pytest classes.
|
||||
|
||||
Test usage of override_config on test method and as context manager.
|
||||
"""
|
||||
|
||||
def test_default_value_is_true(self):
|
||||
"""Assert that the default value of config.BOOL_VALUE is True."""
|
||||
assert config.BOOL_VALUE
|
||||
|
||||
@pytest.mark.override_config(BOOL_VALUE=False)
|
||||
def test_override_config_on_method_changes_config_value(self):
|
||||
"""Assert that the pytest mark decorator changes config.BOOL_VALUE."""
|
||||
assert not config.BOOL_VALUE
|
||||
|
||||
def test_override_config_as_context_manager_changes_config_value(self):
|
||||
"""Assert that the context manager changes config.BOOL_VALUE."""
|
||||
with override_config(BOOL_VALUE=False):
|
||||
assert not config.BOOL_VALUE
|
||||
|
||||
assert config.BOOL_VALUE
|
||||
|
||||
@override_config(BOOL_VALUE=False)
|
||||
def test_method_decorator(self):
|
||||
"""Ensure `override_config` can be used as test method decorator."""
|
||||
assert not config.BOOL_VALUE
|
||||
|
||||
|
||||
@pytest.mark.override_config(BOOL_VALUE=False)
|
||||
class TestPytestOverrideConfigDecorator:
|
||||
"""Test that the override_config decorator works on classes."""
|
||||
|
||||
def test_override_config_on_class_changes_config_value(self):
|
||||
"""Asser that the class decorator changes config.BOOL_VALUE."""
|
||||
assert not config.BOOL_VALUE
|
||||
|
||||
@pytest.mark.override_config(BOOL_VALUE='True')
|
||||
def test_override_config_on_overrided_value(self):
|
||||
"""Ensure that method mark decorator changes already overrided value for class."""
|
||||
assert config.BOOL_VALUE == 'True'
|
||||
|
||||
|
||||
def test_fixture_override_config(override_config):
|
||||
"""
|
||||
Ensure `override_config` fixture is available globally
|
||||
and can be used in test functions.
|
||||
"""
|
||||
with override_config(BOOL_VALUE=False):
|
||||
assert not config.BOOL_VALUE
|
||||
|
||||
@override_config(BOOL_VALUE=False)
|
||||
def test_func_decorator():
|
||||
"""Ensure `override_config` can be used as test function decorator."""
|
||||
assert not config.BOOL_VALUE
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class PytestTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.skipTest('Skip all pytest tests when using unittest')
|
||||
|
||||
def test_do_not_skip_silently(self):
|
||||
"""
|
||||
If no at least one test present, unittest silently skips module.
|
||||
"""
|
||||
pass
|
||||
14
tox.ini
14
tox.ini
|
|
@ -1,8 +1,8 @@
|
|||
[tox]
|
||||
envlist =
|
||||
py{35,36,37,pypy3}-django{22}
|
||||
py{36,37,38}-django{30}
|
||||
py{36,37,38}-django-master
|
||||
py{35,36,37,pypy3}-django{22}-unittest
|
||||
py{36,37,38}-django{30,-master}-unittest
|
||||
py{36,37,38,pypy3}-django{22,30,-master}-pytest
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
|
|
@ -13,12 +13,16 @@ deps =
|
|||
django-22: Django>=2.2,<3.0
|
||||
django-30: Django>=3.0,<3.1
|
||||
django-master: https://github.com/django/django/archive/master.tar.gz
|
||||
pytest: pytest
|
||||
pytest: pytest-cov
|
||||
pytest: pytest-django
|
||||
usedevelop = True
|
||||
ignore_outcome =
|
||||
django-master: True
|
||||
commands =
|
||||
coverage run {envbindir}/django-admin test -v2
|
||||
coverage report
|
||||
unittest: coverage run {envbindir}/django-admin test -v2
|
||||
unittest: coverage report
|
||||
pytest: pytest --cov=. --ignore=.tox --disable-pytest-warnings {toxinidir}
|
||||
setenv =
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
DJANGO_SETTINGS_MODULE=tests.settings
|
||||
|
|
|
|||
Loading…
Reference in a new issue