10

Using the (partial) polls app from the Django tutorial as an example, I'm trying to get pytest-django to run.

Using the command django-admin startproject mysite2, I've created a project directory with the following structure:

.
├── db.sqlite3
├── manage.py
├── mysite2
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── polls
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
└── pytest.ini

My pytest.ini looks like

[pytest]
DJANGO_SETTINGS_MODULE = mysite2.settings
python_files = tests.py test_*.py *_tests.py

Following the tutorial, in polls/models.py I've created Question and Choice models:

import datetime

from django.db import models
from django.utils import timezone

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __str__(self):
        return self.choice_text

Now, if I make tests.py as described in the tutorial, which is based on Python's built-in unittest module,

import datetime

from django.utils import timezone
from django.test import TestCase

from .models import Question

class QuestionModelTests(TestCase):
    def test_was_published_recently_with_future_question(self):
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)

and I run python manage.py test from the command line, the test fails expected:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/kurtpeek/Documents/Scratch/mysite2/polls/tests.py", line 23, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

However, if I change the testing code to the (attempted) pytest equivalent (that is, without having to subclass TestCase and with ordinary assertions):

def test_was_published_recently_with_future_question():
    time = timezone.now() + datetime.timedelta(days=30)
    future_question = Question(pub_date=time)
    assert future_question.was_published_recently() is False

and run the pytest command, I get the following error:

================================= test session starts ==================================
platform darwin -- Python 3.6.3, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
rootdir: /Users/kurtpeek/Documents/Scratch/mysite2, inifile: pytest.ini
plugins: timeout-1.2.1
collected 0 items / 1 errors                                                            

======================================== ERRORS ========================================
___________________________ ERROR collecting polls/tests.py ____________________________
polls/tests.py:10: in <module>
    from .models import Question
polls/models.py:6: in <module>
    class Question(models.Model):
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/base.py:100: in __new__
    app_config = apps.get_containing_app_config(module)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/apps/registry.py:244: in get_containing_app_config
    self.check_apps_ready()
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/apps/registry.py:127: in check_apps_ready
    raise AppRegistryNotReady("Apps aren't loaded yet.")
E   django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 0.64 seconds ================================

So far I haven't been able to find a way to fix this. Any ideas on how to get the test to run?

Franey
  • 3,006
  • 2
  • 12
  • 11
Kurt Peek
  • 34,968
  • 53
  • 191
  • 361

5 Answers5

8

Out of the box, pytest doesn't know about the Django database, even with pytest-django installed. Never fear, though: pytest-django makes it easy for your tests to access the Django database using its django_db pytest mark.

Give this a try:

import pytest


@pytest.mark.django_db
def test_was_published_recently_with_future_question():
    time = timezone.now() + datetime.timedelta(days=30)
    future_question = Question(pub_date=time)
    assert future_question.was_published_recently() is False
Franey
  • 3,006
  • 2
  • 12
  • 11
4

According to Django: AppRegistryNotReady(), when not using manage.py one must call django.setup() explicitly. I verified this by running the pytest test from a manage.py shell:

Kurts-MacBook-Pro:mysite2 kurtpeek$ python3 manage.py shell
Python 3.6.3 (v3.6.3:2c5fed86e0, Oct  3 2017, 00:32:08) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import pytest

In [2]: pytest.main('polls/tests.py')
================================= test session starts ==================================
platform darwin -- Python 3.6.3, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
rootdir: /Users/kurtpeek/Documents/Scratch/mysite2, inifile: pytest.ini
plugins: timeout-1.2.1
collected 1 item                                                                        

polls/tests.py F

======================================= FAILURES =======================================
___________________ test_was_published_recently_with_future_question ___________________

    def test_was_published_recently_with_future_question():
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
>       assert future_question.was_published_recently() is False
E    assert True is False
E     +  where True = <bound method Question.was_published_recently of <Question: >>()
E     +    where <bound method Question.was_published_recently of <Question: >> = <Question: >.was_published_recently

polls/tests.py:18: AssertionError
=================================== warnings summary ===================================
None
  passing a string to pytest.main() is deprecated, pass a list of arguments instead.

-- Docs: http://doc.pytest.org/en/latest/warnings.html
========================= 1 failed, 1 warnings in 0.14 seconds =========================
Out[2]: 1

This is not really an acceptable solution, however, as the tests need to be runnable from the command line. Are there perhaps other pytest decorators to ensure the required setup?

Kurt Peek
  • 34,968
  • 53
  • 191
  • 361
4

I had a similar problem when invoking tests either with pytest or python setup.py test.

For pytest invocation installing pytest-django in my virtual env solved the problem.

For python setup.py install adding pytest-django to the tests_require argument of setup() solved it.

Here's the snippet of setup.py:

TEST_REQUIREMENTS = [
    'pytest',
    'pytest-django',
    'pylint',
    'pylint_django',
    'git-pylint-commit-hook',
]

setup(
    name='foo',
    version='0.0.1',
    description='Foo package',
    author='...',
    author_email='...',
    packages=['foo'],
    install_requires=INSTALL_REQUIREMENTS,
    setup_requires=SETUP_REQUIREMENTS,
    tests_require=TEST_REQUIREMENTS,
)
  • This should be the accepted answer imho. pytest-django is the glue that solves the problem and makes Django and pytest friends. – jaywink Aug 15 '18 at 13:52
1

For me, setting the DJANGO_SETTINGS_MODULE as an export on the command line or in the pytest.ini solved the problem. It seems to ignore the export of that env var in conftest.py If I figure it out I will update this post.

0

Does it say somewhere in the docs that the test should work without subclassing django.test.TestCase? I don't think that django-pytest does anything special in regards to loading django apps. So, if your class continues to inherit from TestCase, you should be able to use everything else from pytest, such as it's assertions, fixtures, etc.

MrName
  • 1,887
  • 11
  • 23
  • 1
    Interesting idea, but according to the [docs](https://pytest-django.readthedocs.io/en/latest/), "Less boilerplate: no need to import unittest, create a subclass with methods. Just write tests as regular functions." should be one of the advantages of `pytest-django`, so the subclassing should not be necessary. – Kurt Peek Jan 05 '18 at 17:43