# Testing partials The designer's refresh from [Why partials](why-partials.md) has landed. The view count has thousands separators, the author is a link, and because there is one card instead of fourteen, the change was one file. The remaining question is how you know it is right on every page, and the pleasant answer is that you do not have to look at every page. A partial has an explicit list of inputs and produces a string, which is about the most testable shape a piece of a web app can have. No browser, no full page, no database; you need the fragment's name and a few keyword arguments. The patterns below are the ones the library's own suite uses, so they are known to work. # Flask [render_partial](../reference/render_partial.md#jinja_partials.render_partial) with no renderer falls back to `flask.render_template`, which needs an app and a request context. A fixture provides both, and every test after that is two lines: ``` python import flask import pytest import jinja_partials @pytest.fixture def app(): app = flask.Flask(__name__, template_folder='templates') jinja_partials.register_extensions(app) with app.test_request_context('/'): yield app def test_video_square(app): html = jinja_partials.render_partial('shared/partials/video_square.html', video=FakeVideo()) assert '12,500 views' in html ``` That assertion is the thousands separators from the refresh, checked once, on behalf of every page that renders the card. A bad template name raises `jinja2.TemplateNotFound`, so `pytest.raises(TemplateNotFound)` is the right assertion when you want to prove a rename did not leave a dangling reference behind. # FastAPI and Starlette After registration, the bound function is the [render_partial](../reference/render_partial.md#jinja_partials.render_partial) global on the environment. You can call it directly, and you do not need the app running: ``` python from fastapi import FastAPI from fastapi.templating import Jinja2Templates import jinja_partials templates = Jinja2Templates(directory='templates') app = FastAPI() jinja_partials.register_fastapi_extensions(app, templates) def test_card_partial(): render = templates.env.globals['render_partial'] html = render('shared/partials/card.html', item={'title': 'Python Basics'}) assert 'Python Basics' in html ``` Outside a lifespan, async environments render through a per-registration fallback pool, so a plain synchronous test function is enough. When you do want the full request path, `with TestClient(app) as client:` runs the lifespan; the pool is created on entry and shut down on exit, and the app can be started and stopped as many times as the suite likes. That re-entrancy is tested, because it was once broken. # Quart Quart's `app.test_app()` context runs the `before_serving` and `after_serving` hooks, which is where the render pool lives. Drive it with `asyncio.run` from an ordinary synchronous test: ``` python import asyncio from quart import Quart import jinja_partials def test_quart_partial(): app = Quart(__name__, template_folder='templates') jinja_partials.register_quart_extensions(app) async def cycle(): async with app.test_app(): return app.jinja_env.globals['render_partial']('shared/partials/badge.html', tag='beginner') assert 'beginner' in asyncio.run(cycle()) ``` # Plain Jinja2 ``` python from jinja2 import Environment, FileSystemLoader import jinja_partials env = Environment(loader=FileSystemLoader('templates'), autoescape=True) jinja_partials.register_environment(env, markup=True) def test_fragment(): html = env.get_template('home/index.html').render(videos=[FakeVideo()]) assert '