pytest-dev / pytest-django

A Django plugin for pytest.
https://pytest-django.readthedocs.io/
Other
1.39k stars 342 forks source link

Client: testing exceptions in views (client.store_exc_info) #130

Open blueyed opened 10 years ago

blueyed commented 10 years ago

I want to test a view that throws an exception, and came up with the following code to make it work, so that the exception is not re-thrown by the client (code ref: https://github.com/django/django/blob/master/django/test/client.py#L421-443):

def test_foo(client):
    def store_exc_info(*args, **kwargs):
        pass
    # client.store_exc_info = lambda *args, **kwargs: True
    client.store_exc_info = store_exc_info

    client.get('/raises-500')

Given that it was not trivial to figure this out I wonder if this is the correct approach and if pytest-django could help in this regard (documentation, helper, ...)

I will look into providing a documentation update for Django itself, but want to get some feedback first.

pelme commented 10 years ago

Are you looking to silence that exception? Or catch it? Would you like that test to fail?

blueyed commented 10 years ago

I want to get the rendered content of the page, not the Exception (IndexError) from the view.

It's related to this commit in Django: https://github.com/django/django/commit/f9cdde0cb4

E.g.:

from django.conf.urls import patterns, url
from config.urls import urlpatterns as config_urls
from django.views.debug import CLEANSED_SUBSTITUTE

class MockUrlConf500():
    urlpatterns = config_urls \
        + patterns('', url(r'^500$', lambda x: [][0]))

@mock.patch('django.core.handlers.base.logger')
@pytest.mark.urls(urls=MockUrlConf500)
def test_technical_500(mock_logger, live_server, client, submitter_client, admin_client,
                       settings):
    # Prevent the client from re-raising the exception.
    # Related to this commit: https://github.com/django/django/commit/f9cdde0cb4
    client.store_exc_info = lambda *args, **kwargs: True

    from django.template.loader import render_to_string
    pretty_template = render_to_string('500.html')  # via CustomErrorHandler
    mark_technical_500 = '<pre class="exception_value">'

    settings.DEBUG = True
    response = client.get('/500')
    assert response.status_code == 500
    assert response.content != pretty_template
    assert mark_technical_500 in response.content
    assert "because you have <code>DEBUG = True</code>" in response.content
    assert CLEANSED_SUBSTITUTE in response.content
    assert settings.SECRET_KEY not in response.content
    assert mock_logger.error.call_count == 1
dieselmachine commented 9 years ago

You are a hero for figuring this out. I've been struggling for hours trying to figure out how to test django-tastypie's canned_response, and the django test client interfered with the test (as I'm sure you know). Your solution completely solved the problem, thank you so much! You are awesome!

Also, anyone who considers this interference by the test client to be a "feature" should be ashamed of themselves.

pelme commented 9 years ago

I think a "fix" for this belongs in Django and in the test Client itself. I.e. initiating client objects with client = Client(raise_view_exceptions=False) or something like that seems like the proper way to handle this.