starlette: AssertionError with middleware and TemplateResponse

diff --git i/tests/test_templates.py w/tests/test_templates.py
index b48373e..57dee91 100644
--- i/tests/test_templates.py
+++ w/tests/test_templates.py
@@ -20,6 +20,11 @@ def test_templates(tmpdir):
     async def homepage(request):
         return templates.TemplateResponse("index.html", {"request": request})

+    @app.middleware("http")
+    async def noop(request, call_next):
+        response = await call_next(request)
+        return response
+
     client = TestClient(app)
     response = client.get("/")
     assert response.text == "<html>Hello, <a href='http://testserver/'>world</a></html>"

causes:

tests/test_templates.py:29: in test_templates
    response = client.get("/")
.venv/lib/python3.7/site-packages/requests/sessions.py:546: in get
    return self.request('GET', url, **kwargs)
starlette/testclient.py:421: in request
    json=json,
.venv/lib/python3.7/site-packages/requests/sessions.py:533: in request
    resp = self.send(prep, **send_kwargs)
.venv/lib/python3.7/site-packages/requests/sessions.py:646: in send
    r = adapter.send(request, **kwargs)
starlette/testclient.py:238: in send
    raise exc from None
starlette/testclient.py:235: in send
    loop.run_until_complete(self.app(scope, receive, send))
/usr/lib/python3.7/asyncio/base_events.py:584: in run_until_complete
    return future.result()
starlette/applications.py:134: in __call__
    await self.error_middleware(scope, receive, send)
starlette/middleware/errors.py:122: in __call__
    raise exc from None
starlette/middleware/errors.py:100: in __call__
    await self.app(scope, receive, _send)
starlette/middleware/base.py:25: in __call__
    response = await self.dispatch_func(request, self.call_next)
tests/test_templates.py:21: in noop
    response = await call_next(request)
starlette/middleware/base.py:47: in call_next
    assert message["type"] == "http.response.start"
E   AssertionError

message is {'type': 'http.response.template', 'template': <Template 'index.html'>, 'context': {'request': <starlette.requests.Request object at 0x7ff4a2d21e48>}} there.

This appears to be an issue with the test client only though. Do I understand it correctly that the “http.response.template” extension is only used with tests?

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 16
  • Comments: 19 (11 by maintainers)

Commits related to this issue

Most upvoted comments

A slightly neater solution, which can be injected into any test which already has the client injected:

@pytest.fixture
def exclude_middleware():
    user_middleware = app.user_middleware.copy()
    app.user_middleware = []
    app.middleware_stack = app.build_middleware_stack()
    yield
    app.user_middleware = user_middleware
    app.middleware_stack = app.build_middleware_stack()

Keeping my fingers crossed that this problem gets fixed soon 😄

Huge thanks Fotis

I currently have a hacky workaround for this.

Subclass Jinja2Templates and modify it to not send the context, this means you can’t test against the template context, but at least doesn’t fail. Does anyone have a better workaround than this (e.g. doesn’t involve changing anything in the application code, still allows tests with context)?

from starlette.responses import Response
from starlette.templating import Jinja2Templates as _Jinja2Templates, _TemplateResponse


class TestableJinja2Templates(_Jinja2Templates):
    def TemplateResponse(
        self,
        name: str,
        context: dict,
        status_code: int = 200,
        headers: dict = None,
        media_type: str = None,
        background=None,
    ) -> _TemplateResponse:
        if "request" not in context:
            raise ValueError('context must include a "request" key')
        template = self.get_template(name)
        return CustomTemplateResponse(
            template,
            context,
            status_code=status_code,
            headers=headers,
            media_type=media_type,
            background=background,
        )


class CustomTemplateResponse(_TemplateResponse):
    async def __call__(self, scope, receive, send) -> None:
        # context sending removed
        await Response.__call__(self, scope, receive, send)

has there been any update on this issue @tomchristie ? In its current state it’s effectively erroring out on unit-tests on resources that return templated responses if a middleware is present.

I see the workaround used in https://github.com/atviriduomenys/spinta/pull/22 but I’d rather not override such an important piece

Here’s my hacky workaround to this for now in case it helps anyone:

@pytest.fixture
def client():
    return TestClient(app)


# TODO: I've had to create an app without middleware for testing due to the following bug
# https://github.com/encode/starlette/issues/472
@pytest.fixture
def client_without_middleware(request):
    def fin():
        app.user_middleware = user_middleware
        app.middleware_stack = app.build_middleware_stack()

    user_middleware = app.user_middleware.copy()
    app.user_middleware = []
    app.middleware_stack = app.build_middleware_stack()

    request.addfinalizer(fin)
    return TestClient(app)

I then inject client_without_middleware into tests which render Jinja2 templates.

Yeah, that’s why the issue was closed.

Are there any updates on this? It has been 3 years since this issue has been raised and the only solution to this date is to test apps without middleware. Surely this should be possible?

btw: I think in general more verbose asserts should be used, e.g. in this case:

assert message["type"] == "http.response.start", message

or

assert message["type"] == "http.response.start", message["type"]