tumb1er / django-testing-utils

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Django-testing-utils

Django-Testing-Utils is a package providing test helpers for django app testing.

build codecov PyPI version

Installation

pip install django-testing-utils

Usage

TestCase metaclass

Django 3.2 introduces 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.

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 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.

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
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.

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())

About

License:MIT License


Languages

Language:Python 100.0%