Testing MicroPie applications
=============================

This guide shows practical approaches for testing MicroPie applications.
MicroPie does not ship with a bespoke test client, but because it is a
regular ASGI application you can exercise it using familiar Python
libraries such as :mod:`unittest`, :mod:`pytest` and
:mod:`httpx`'s ASGI tools.

Choosing a test framework
-------------------------

MicroPie itself uses :class:`unittest.IsolatedAsyncioTestCase` in
``tests.py`` to run asynchronous tests. ``pytest`` with the
``pytest-asyncio`` plugin offers a similar developer experience. Pick the
library that best matches your project's conventions—the examples below
work with either.

Unit testing handlers directly
------------------------------

Because handlers are regular functions, you can instantiate your
:class:`~micropie.App` subclass and call methods directly. Use the
:data:`micropie.current_request` context variable to set up any request
state that your handler expects.

.. code-block:: python

   from micropie import App, Request, current_request

   class MyApp(App):
       async def greet(self, name="World"):
           return f"Hello {name}!"

   async def test_greet_uses_default():
       app = MyApp()
       scope = {"type": "http", "method": "GET", "path": "/"}
       request = Request(scope)
       token = current_request.set(request)
       try:
           response = await app.greet()
       finally:
           current_request.reset(token)
       assert response == "Hello World!"

Testing through the ASGI interface
----------------------------------

For higher confidence, drive the full ASGI stack. ``httpx`` provides an
``ASGITransport`` class that can mount a MicroPie app. Install ``httpx``
with ``pip install httpx``. The example below uses ``pytest`` style
asserts, but the structure works in ``unittest`` with
``self.assertEqual``.

.. code-block:: python

   import pytest
   import httpx

   from micropie import App

   class MyApp(App):
       async def index(self):
           return {"status": "ok"}

   @pytest.mark.asyncio
   async def test_index_returns_json():
       app = MyApp()
       async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app)) as client:
           response = await client.get("http://test/")
       assert response.status_code == 200
       assert response.json() == {"status": "ok"}

Simulating sessions and middleware
----------------------------------

To assert session behaviour, populate ``scope["headers"]`` with a
``cookie`` header and inspect the response headers for the updated
``Set-Cookie`` value. Middleware can be tested by attaching it to your
app instance before issuing requests.

.. code-block:: python

   import httpx

   from micropie import App, HttpMiddleware

   class AddHeader(HttpMiddleware):
       async def before_request(self, request):
           return None

       async def after_request(self, request, status_code, response_body, extra_headers):
           extra_headers.append(("X-Test", "1"))
           return {"headers": extra_headers}

   class MyApp(App):
       async def index(self):
           return "hi"

   async def test_middleware_header():
       app = MyApp()
       app.middlewares.append(AddHeader())
       transport = httpx.ASGITransport(app=app)
       async with httpx.AsyncClient(transport=transport) as client:
           response = await client.get("http://test/")
       assert response.headers["x-test"] == "1"

Handling lifespan events
------------------------

If your application registers startup or shutdown handlers, wrap your
ASGI client in a lifespan manager. ``httpx`` exposes
:class:`httpx.ASGITransport` with a ``lifespan="auto"`` mode that will
run lifespan events before the first request and after the client exits
(on versions that support this option).

.. code-block:: python

   async with httpx.AsyncClient(
       transport=httpx.ASGITransport(app=app, lifespan="auto")
   ) as client:
       ...

Further reading
---------------

* Browse ``tests.py`` in the MicroPie source tree for additional
  patterns, including WebSocket testing helpers.
* The `httpx documentation <https://www.python-httpx.org/advanced/#calling-into-python-web-apps>`_
  has more on driving ASGI apps from tests.
