3

I'm using pytest-django to test some Django views.

I want to test that the response context contains certain values, but it's always None.

My view:

from django.views.generic import TemplateView

class MyView(TemplateView):
    template_name = 'my_template.html'

    def get_context_data(self, **kwargs):
        context = super(MyView, self).get_context_data(**kwargs)
        context['hello'] = 'hi'
        return context

My test:

def test_context(client):
    response = client.get('/test/')
    print('status', response.status_code)
    print('content', response.content)
    print('context', response.context)

If I run this with the -s flag to see the print statements, the status code is 200, and content contains the rendered template, including the "hi" that's in the context. But context is None.

I thought this client was the same as django.test.Client which should let me see the context... so what am I missing?

I've tried this answer but got

RuntimeError: setup_test_environment() was already called and can't be called again without first calling teardown_test_environment().

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
Phil Gyford
  • 13,432
  • 14
  • 81
  • 143
  • Are you creating a setup with super user? Because sometimes your application has not found this. Could you show more about your class Test and your urls.py, instead use hardcode urls try to use `from django.core.urlresolvers import reverse`. – Aipi Aug 10 '17 at 18:20
  • No, it's a publicly-accessible page. Normally I would use `reverse()` in tests and have tried it here with the same effect, I was just simplifying things as much as possible to try and find the problem. – Phil Gyford Aug 10 '17 at 18:21
  • Could you insert more about this test code? – Aipi Aug 10 '17 at 18:25
  • I'm not sure what you want to see. I've shown the entire view and the entire test. – Phil Gyford Aug 10 '17 at 18:26
  • You can try to use from `django.test import TestCase` and make a class which inherits this and this method inherits `test_context(self)`. Because apparently, this method is okay. I do not know how you are passing client to this. – Aipi Aug 10 '17 at 18:31
  • As stated I am using pytest-django. It is not a vanilla Django test. [Here is where `client` comes from.](https://pytest-django.readthedocs.io/en/latest/helpers.html#client-django-test-client) – Phil Gyford Aug 10 '17 at 18:33
  • I've had a similar problem and found out, that I had to use `client.login()` first, because my view required an authenticated user. That's not the solution in your case, because the template is rendered, so I've created a little Django project in order to find out what the problem might be here. I've used Python 3.7, Django 3.0, pytest 5.4.3, and pytest-django 3.9.0. At least with these versions, `response.context['hello']` was `"hi"` as expected. I can provide the code, if you are still interested. – mcrot Jun 18 '20 at 14:17

1 Answers1

1

In the client link you provided, states that the client is an instance of the django.test.Client so in reality it doesn't do anything special there and shouldn't be a problem.

You need to setup your environment as you correctly stated.
Let's have a look at the error now:

from the setup_test_environment() source code:

if hasattr(_TestState, 'saved_data'):
      # Executing this function twice would overwrite the saved values.
      raise RuntimeError(
          "setup_test_environment() was already called and can't be called "
          "again without first calling teardown_test_environment()."
)

And that is what raises your RuntimeError above.

Let's look now at the teardown_test_environment() method:

...
del _TestState.saved_data

So it deletes the culprit of the aforementioned exception.

Thus:

from django.test.utils import teardown_test_environment, setup_test_environment

try:
    # If setup_test_environment haven't been called previously this
    # will produce an AttributeError.
    teardown_test_environment()
except AttributeError:
    pass

setup_test_environment() 

...
John Moutafis
  • 22,254
  • 11
  • 68
  • 112
  • Thanks John. That all makes perfect sense... except when I try it, the line `saved_data = _TestState.saved_data` in `teardown_test_environment()` generates "AttributeError: type object '_TestState' has no attribute 'saved_data'"... – Phil Gyford Aug 11 '17 at 12:44
  • @PhilGyford Well seems to work the first time but from the second on it cruses with the runtime error. I made an edit and placed `teardown_test_environment` on a `try-except block` where if it crashes it will not take down your tests with it. – John Moutafis Aug 11 '17 at 12:48
  • Thanks... although now I'm back to the original RuntimeError I got in the first place. – Phil Gyford Aug 11 '17 at 15:05
  • @PhilGyford Can you place the `setup_test_environment` in a `try-except block` as well and inform me about the outcome? – John Moutafis Aug 11 '17 at 16:22
  • 1
    Whether I do that or not, I still get a RuntimeError, but I've just realised it's generated by the `setup_test_environment` call in `pytest_django/plugin.py` [here](https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/plugin.py#L340) – Phil Gyford Aug 12 '17 at 15:51
  • if that gets called then renders out call to `setup_test_environment` a dublicate call, try it without the call, keep only the `try: teardown_test_environment except` block – John Moutafis Aug 12 '17 at 15:56
  • So it's trying to do `teardown_test_environment`, whose exception is now quietly caught, so the test runs with no errors... but the context is still `None` when it shouldn't be. – Phil Gyford Aug 12 '17 at 20:53