59

I'm writing a reusable django app and I need to ensure that its models are only sync'ed when the app is in test mode. I've tried to use a custom DjangoTestRunner, but I found no examples of how to do that (the documentation only shows how to define a custom test runner).

So, does anybody have an idea of how to do it?

EDIT

Here's how I'm doing it:

#in settings.py
import sys
TEST = 'test' in sys.argv

Hope it helps.

Herberth Amaral
  • 3,139
  • 3
  • 30
  • 35
  • 1
    Disclaimer: I'm here because I have the same requirement. However, I just want to point out that having your code behave differently when a test is running is a really bi NO-NO. As much as possible, you want to test the code the way it will actually work. – brianmearns Dec 13 '13 at 16:48
  • Possible duplicate of [django - how to detect test environment](http://stackoverflow.com/questions/4088253/django-how-to-detect-test-environment) – Amir Ali Akbari Jan 09 '17 at 08:19

6 Answers6

68

I think the answer provided here https://stackoverflow.com/a/7651002/465673 is a much cleaner way of doing it:

Put this in your settings.py:

import sys

TESTING = sys.argv[1:2] == ['test']
Community
  • 1
  • 1
jjmaestro
  • 720
  • 6
  • 5
  • 36
    Today I'm following a similar approach: `TEST = 'test' in sys.argv` Same effect, but a bit more cleaner to read :-) – Herberth Amaral Jul 07 '12 at 01:59
  • 4
    @jjmaestro is there any reason for not writing `TESTING = sys.argv[1] == 'test'`. I thinks it's exactly the same and simpler – glarrain Jun 11 '13 at 22:28
  • 9
    @glarrain: using a slice (as in `[1:2]`) means you won't get an index out of bounds error if by change `sys.argv` has fewer than two elements in it. – brianmearns Dec 13 '13 at 16:32
  • 1
    @HerberthAmaral, your suggestion in the first comment should become an answer by itself. – Jorge Leitao Mar 27 '15 at 14:21
  • 3
    Doesn't work when something else is running tests, like django jenkins. – dalore Dec 04 '15 at 10:54
  • This won't work if started as "./manage.py test" instead of "python manage.py test". Many developers prefer that since it's shorter. – Alex K Mar 01 '19 at 13:21
  • @AlexK For "./manage.py test" , sys.argv=['./manage.py', 'test'] For "python manage.py test" , sys.argv=['manage.py', 'test'] So it works for both. Otherwise you can use "TESTING = True if 'test' in sys.argv[0:3] else False" – Nguyen Anh Vu Nov 25 '19 at 09:34
  • ``TESTING = 'test' in sys.argv or 'pytest' in sys.argv[0]`` – mirek Jan 23 '20 at 16:04
52

The selected answer is a massive hack. :)

A less-massive hack would be to create your own TestSuiteRunner subclass and change a setting or do whatever else you need to for the rest of your application. You specify the test runner in your settings:

TEST_RUNNER = 'your.project.MyTestSuiteRunner'

In general, you don't want to do this, but it works if you absolutely need it.

from django.conf import settings
from django.test.simple import DjangoTestSuiteRunner

class MyTestSuiteRunner(DjangoTestSuiteRunner):
    def __init__(self, *args, **kwargs):
        settings.IM_IN_TEST_MODE = True
        super(MyTestSuiteRunner, self).__init__(*args, **kwargs)

NOTE: As of Django 1.8, DjangoTestSuiteRunner has been deprecated. You should use DiscoverRunner instead:

from django.conf import settings
from django.test.runner import DiscoverRunner


class MyTestSuiteRunner(DiscoverRunner):
    def __init__(self, *args, **kwargs):
        settings.IM_IN_TEST_MODE = True
        super(MyTestSuiteRunner, self).__init__(*args, **kwargs)
Corentin S.
  • 5,381
  • 1
  • 13
  • 18
Travis Jensen
  • 5,112
  • 3
  • 33
  • 38
  • That should do the trick, but I'm all for simpler solutions :-) – Herberth Amaral Dec 26 '13 at 12:20
  • 3
    I like this approach, but note that this code will run after your other code has been imported. So if you need to detect the condition in module-level code this won't work. – Kevin Christopher Henry Oct 09 '14 at 17:37
  • 4
    Modifying `django.conf.settings` is strongly discouraged by Django docs, see: https://docs.djangoproject.com/en/1.11/topics/settings/#altering-settings-at-runtime – David Avsajanishvili Aug 25 '17 at 12:28
  • 3
    The selected answer is a massive hack *but so is this!* – user31415629 Sep 13 '18 at 14:53
  • It's a massive hack I'm finding in production code here but I don't think it's particularly robust against different ways of invoking python, invoking testing and/or swapping out `manage.py test` for something such as `pytest`. – Alper Dec 17 '20 at 13:06
22

Not quite sure about your use case but one way I've seen to detect when the test suite is running is to check if django.core.mail has a outbox attribute such as:

from django.core import mail

if hasattr(mail, 'outbox'):
    # We are in test mode!
    pass
else:
    # Not in test mode...
    pass

This attributed is added by the Django test runner in setup_test_environment and removed in teardown_test_environment. You can check the source here: https://code.djangoproject.com/browser/django/trunk/django/test/utils.py

Edit: If you want models defined for testing only then you should check out Django ticket #7835 in particular comment #24 part of which is given below:

Apparently you can simply define models directly in your tests.py. Syncdb never imports tests.py, so those models won't get synced to the normal db, but they will get synced to the test database, and can be used in tests.

Mark Lavin
  • 22,998
  • 4
  • 71
  • 67
  • It is not the usual way. But I think it'll work. I've seen this code inside django's core but I thought that it would have a more "specific" solution. About the use case: the app I am writing uses some variations of models and form fields. So I need the models working only in test mode in order to have a good test coverage; The app is opensource and you can see it here: https://github.com/herberthamaral/django-jqgrid – Herberth Amaral Aug 06 '11 at 01:33
  • 1
    Ah yes now I understand what you are asking. I've updated my answer to include handling test only models. I have a similar use case in one of my project's test suite: https://bitbucket.org/mlavin/django-selectable – Mark Lavin Aug 06 '11 at 02:15
  • Thank you very much, Mark. This is a very useful resource and I think it should go on Django's doc. – Herberth Amaral Aug 06 '11 at 12:13
  • This one is a hack worse even that the accepted answer. It only works as long as you do not change your email backend. – lullis Dec 07 '18 at 19:58
  • 2
    I'll agree that this is a bit of a hack but "It only works as long as you do not change your email backend" is simply not true. Django's test suite environment always changes the email backend to use the in-memory backend which is why this works. See https://docs.djangoproject.com/en/stable/topics/testing/tools/#email-services – Mark Lavin Dec 11 '18 at 16:00
10

I'm using settings.py overrides. I have a global settings.py, which contains most stuff, and then I have overrides for it. Each settings file starts with:

from myproject.settings import settings

and then goes on to override some of the settings.

  • prod_settings.py - Production settings (e.g. overrides DEBUG=False)
  • dev_settings.py - Development settings (e.g. more logging)
  • test_settings.py

And then I can define UNIT_TESTS=False in the base settings.py, and override it to UNIT_TESTS=True in test_settings.py.

Then whenever I run a command, I need to decide which settings to run against (e.g. DJANGO_SETTINGS_MODULE=myproject.test_settings ./manage.py test). I like that clarity.

Amichai Schreiber
  • 1,249
  • 15
  • 15
  • 1
    Nice and explicit, this is how I do it as well. You can also do DJANGO_ENV=test ./manage.py test, and pick that up with sys.env in your code. – Nick Sweeting May 05 '17 at 17:05
2

Well, you can just simply use environment variables in this way:

export MYAPP_TEST=1 && python manage.py test

then in your settings.py file:

import os

TEST = os.environ.get('MYAPP_TEST')

if TEST:
    # Do something
turkus
  • 3,567
  • 19
  • 27
0

I've been using Django class based settings. I use the 'switcher' from the package and load a different config/class for testing=True:

switcher.register(TestingSettings, testing=True)

In my configuration, I have a BaseSettings, ProductionSettings, DevelopmentSettings, TestingSettings, etc. They subclass off of each other as needed. In BaseSettings I have IS_TESTING=False, and then in TestingSettings I set it to True.

It works well if you keep your class inheritance clean. But I find it works better than the import * method Django developers usually use.

rrauenza
  • 5,161
  • 4
  • 27
  • 45