Django-testing-utils
==================
Django-Testing-Utils is a package providing test helpers for django app testing.
![build](https://github.com/just-work/django-testing-utils/workflows/build/badge.svg?branch=master)
[![codecov](https://codecov.io/gh/just-work/django-testing-utils/branch/master/graph/badge.svg)](https://codecov.io/gh/just-work/django-testing-utils)
[![PyPI version](https://badge.fury.io/py/django-testing-utils.svg)](https://badge.fury.io/py/django-testing-utils)
Installation
------------
```shell script
pip install django-testing-utils
```
Usage
-----
## TestCase metaclass
Django 3.2 [introduces](https://docs.djangoproject.com/en/3.2/releases/3.2/#tests)
setUpTestData attributes isolation, but django-testing-utils has slightly
different way of resetting class attributes between tests. It collects all
django model objects created in any TestCase class method and runs
refresh_from_db() when necessary. It also clears fields_cache for such objects.
```python
from django_testing_utils import mixins
from testproject.testapp import models
class SomeTestCase(mixins.BaseTestCase):
""" Some test case that uses django models."""
@classmethod
def setUpTestData(cls):
super().setUpTestData()
# In large code django docs recommend to create common objects in
# this method to speedup tests setup by reusing objects in db.
cls.project = models.Project.objects.create(name='project')
def test_something(self):
# in this test self.project instance is independent from other tests
...
```
## Time mocking
There are multiple ways to freeze time in tests:
* ad-hoc mocking with `unittest.mock`
* [freezegun](https://github.com/spulec/freezegun) library
* any system approach that puts working with time in order
django-testing-utils provides a way to use last approach in test with some
limitations:
* Project code must work with `django.utils.timezone` methods only
* All tests should inherit `TimeMixin` from django-testing-utils
This leads to a systematic way of datetime and timezone usage in the project
and it's tests.
```python
from django.test import TestCase
from django_testing_utils.mixins import TimeMixin, second
class MyTimeTestCase(TimeMixin, TestCase):
@classmethod
def setUpTestData(cls):
# time is not mocked here yet
...
def setUp(self) -> None:
# not yet...
super().setUp()
# ... and here time has been frozen to `self.now`
def test_something(self):
# simulate time run
self.now += second
```
## Helpers for django objects
There are some helper methods to work with django objects
* `update_object` - performs an `UPDATE` on django model in a database. This
does not affect field values for an object passed to this method.
* `reload` - fetches a new model instance from a database via primary key.
* `assert_object_fields` fetches an actual version from a database and
compares all fields with values passed as named arguments
```python
from django_testing_utils.mixins import BaseTestCase
from testapp import models
class MyTimeTestCase(BaseTestCase):
def test_something(self):
obj = models.Project.objects.create()
# change something in db
self.update_object(obj, name='new')
# fetch updated version
new = self.reload(obj)
# old object is untouched
self.assertNotEqual(new.name, obj.name)
# you could fetch and object and compare some fields in a single call
self.assert_object_fields(obj, name='new')
```
## Decorators
* `override_defaults` - a test case/test method decorator to change default
values that are stored in `app.defaults` module. The idea of `app.defaults`
is to reduce a number of rarely changed variables in django settings module by
moving it to application-specific settings
* `disable_patcher` - a context manager / decorator to temporarily disable
some `unittest.patch` instances defined in TestCase instance. This breaks
open/close principle but allows postponing tests refactoring when some
mocks are too generic.
```python
from django.test import TestCase
from django.utils import timezone
from django_testing_utils.mixins import TimeMixin
from django_testing_utils.utils import override_defaults, disable_patchers
from testapp import defaults
import testapp
class MyTestCase(TimeMixin, TestCase):
@override_defaults(testapp.__name__, setting_1=42)
def test_setting_value(self):
self.assertEqual(defaults.setting_1, 42)
@disable_patchers('now_patcher')
def test_real_time(self):
# now patcher from TimeMixin is now disabled
with disable_patchers(self.timezone_datetime_patcher):
# timezone.datetime patcher is not also disabled
self.assertNotEqual(timezone.now(), timezone.now())
```
Raw data
{
"_id": null,
"home_page": null,
"name": "django-testing-utils",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "django, tests",
"author": null,
"author_email": "Sergey Tikhonov <zimbler@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/bc/b3/5af395b4dc45889c285693554b5f4ba50d4bd6c4e90d40d20466dd9f90a8/django-testing-utils-0.7.0.tar.gz",
"platform": null,
"description": "Django-testing-utils\n==================\n\nDjango-Testing-Utils is a package providing test helpers for django app testing.\n\n![build](https://github.com/just-work/django-testing-utils/workflows/build/badge.svg?branch=master)\n[![codecov](https://codecov.io/gh/just-work/django-testing-utils/branch/master/graph/badge.svg)](https://codecov.io/gh/just-work/django-testing-utils)\n[![PyPI version](https://badge.fury.io/py/django-testing-utils.svg)](https://badge.fury.io/py/django-testing-utils)\n\nInstallation\n------------\n\n```shell script\npip install django-testing-utils\n```\n\nUsage\n-----\n\n## TestCase metaclass\n\nDjango 3.2 [introduces](https://docs.djangoproject.com/en/3.2/releases/3.2/#tests)\nsetUpTestData attributes isolation, but django-testing-utils has slightly \ndifferent way of resetting class attributes between tests. It collects all \ndjango model objects created in any TestCase class method and runs \nrefresh_from_db() when necessary. It also clears fields_cache for such objects.\n\n```python\nfrom django_testing_utils import mixins\nfrom testproject.testapp import models\n\n\nclass SomeTestCase(mixins.BaseTestCase):\n \"\"\" Some test case that uses django models.\"\"\"\n\n @classmethod\n def setUpTestData(cls):\n super().setUpTestData()\n # In large code django docs recommend to create common objects in \n # this method to speedup tests setup by reusing objects in db.\n cls.project = models.Project.objects.create(name='project')\n\n def test_something(self):\n # in this test self.project instance is independent from other tests\n ...\n\n```\n\n## Time mocking\n\nThere are multiple ways to freeze time in tests:\n\n* ad-hoc mocking with `unittest.mock`\n* [freezegun](https://github.com/spulec/freezegun) library\n* any system approach that puts working with time in order\n\ndjango-testing-utils provides a way to use last approach in test with some \nlimitations:\n\n* Project code must work with `django.utils.timezone` methods only\n* All tests should inherit `TimeMixin` from django-testing-utils\n\nThis leads to a systematic way of datetime and timezone usage in the project \nand it's tests.\n\n```python\nfrom django.test import TestCase\nfrom django_testing_utils.mixins import TimeMixin, second\n\n\nclass MyTimeTestCase(TimeMixin, TestCase):\n @classmethod\n def setUpTestData(cls):\n # time is not mocked here yet\n ...\n\n def setUp(self) -> None:\n # not yet...\n super().setUp()\n # ... and here time has been frozen to `self.now`\n \n def test_something(self):\n # simulate time run\n self.now += second\n```\n\n\n## Helpers for django objects\n\nThere are some helper methods to work with django objects\n\n* `update_object` - performs an `UPDATE` on django model in a database. This \n does not affect field values for an object passed to this method.\n* `reload` - fetches a new model instance from a database via primary key.\n* `assert_object_fields` fetches an actual version from a database and \n compares all fields with values passed as named arguments\n \n```python\nfrom django_testing_utils.mixins import BaseTestCase\nfrom testapp import models\n\nclass MyTimeTestCase(BaseTestCase):\n \n def test_something(self):\n obj = models.Project.objects.create()\n # change something in db\n self.update_object(obj, name='new')\n # fetch updated version\n new = self.reload(obj)\n # old object is untouched\n self.assertNotEqual(new.name, obj.name)\n # you could fetch and object and compare some fields in a single call \n self.assert_object_fields(obj, name='new')\n```\n\n## Decorators\n\n* `override_defaults` - a test case/test method decorator to change default \n values that are stored in `app.defaults` module. The idea of `app.defaults`\n is to reduce a number of rarely changed variables in django settings module by\n moving it to application-specific settings\n \n* `disable_patcher` - a context manager / decorator to temporarily disable \n some `unittest.patch` instances defined in TestCase instance. This breaks \n open/close principle but allows postponing tests refactoring when some \n mocks are too generic. \n \n```python\nfrom django.test import TestCase\nfrom django.utils import timezone\nfrom django_testing_utils.mixins import TimeMixin\nfrom django_testing_utils.utils import override_defaults, disable_patchers\nfrom testapp import defaults\nimport testapp\n\nclass MyTestCase(TimeMixin, TestCase):\n \n @override_defaults(testapp.__name__, setting_1=42)\n def test_setting_value(self):\n self.assertEqual(defaults.setting_1, 42)\n \n @disable_patchers('now_patcher')\n def test_real_time(self):\n # now patcher from TimeMixin is now disabled\n with disable_patchers(self.timezone_datetime_patcher):\n # timezone.datetime patcher is not also disabled\n self.assertNotEqual(timezone.now(), timezone.now())\n\n```\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Testing utils for Django-based projects",
"version": "0.7.0",
"project_urls": {
"homepage": "https://github.com/just-work/django-testing-utils"
},
"split_keywords": [
"django",
" tests"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f365d087afdf9a18a216e9f6a70f829bece704d89399b98ebbbea2a6adfe6d85",
"md5": "81d36976bbc507a935674791d6c477ab",
"sha256": "70c67b82cdb35fd49ef3b0a63ad6c97f41516449933189e413ab88693841ae42"
},
"downloads": -1,
"filename": "django_testing_utils-0.7.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "81d36976bbc507a935674791d6c477ab",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 8339,
"upload_time": "2024-03-26T10:56:32",
"upload_time_iso_8601": "2024-03-26T10:56:32.831030Z",
"url": "https://files.pythonhosted.org/packages/f3/65/d087afdf9a18a216e9f6a70f829bece704d89399b98ebbbea2a6adfe6d85/django_testing_utils-0.7.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "bcb35af395b4dc45889c285693554b5f4ba50d4bd6c4e90d40d20466dd9f90a8",
"md5": "b65c096822f1e25d3c584c11a995ae89",
"sha256": "c1eceeede958249bdc5a1fc264786fc59c6e026954dbafcb97bc9c514ee33f9b"
},
"downloads": -1,
"filename": "django-testing-utils-0.7.0.tar.gz",
"has_sig": false,
"md5_digest": "b65c096822f1e25d3c584c11a995ae89",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 9136,
"upload_time": "2024-03-26T10:56:34",
"upload_time_iso_8601": "2024-03-26T10:56:34.711419Z",
"url": "https://files.pythonhosted.org/packages/bc/b3/5af395b4dc45889c285693554b5f4ba50d4bd6c4e90d40d20466dd9f90a8/django-testing-utils-0.7.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-03-26 10:56:34",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "just-work",
"github_project": "django-testing-utils",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "Django",
"specs": [
[
"==",
"5.0.3"
]
]
},
{
"name": "dj-inmemorystorage",
"specs": [
[
"==",
"2.1.0"
]
]
}
],
"tox": true,
"lcname": "django-testing-utils"
}