django-testing-utils


Namedjango-testing-utils JSON
Version 0.7.0 PyPI version JSON
download
home_pageNone
SummaryTesting utils for Django-based projects
upload_time2024-03-26 10:56:34
maintainerNone
docs_urlNone
authorNone
requires_pythonNone
licenseMIT
keywords django tests
VCS
bugtrack_url
requirements Django dj-inmemorystorage
Travis-CI No Travis.
coveralls test coverage No coveralls.
            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"
}
        
Elapsed time: 0.26614s