Cliente de Teste¶
O Lilya vem com um cliente de teste para os testes da sua aplicação. Não é obrigatório usá-lo, pois cada aplicação e equipa de desenvolvimento tem sua própria maneira de testar, mas caso precise, está disponível.
Requisitos¶
Esta secção requer que a suíte de testes do Lilya esteja instalada. Pode fazer isso executando:
$ pip install Lilya[test]
O cliente de teste¶
from lilya.responses import HTMLResponse
from lilya.testclient import TestClient
async def app(scope, receive, send):
assert scope["type"] == "http"
response = HTMLResponse("<html><body>Hello, world!</body></html>")
await response(scope, receive, send)
def test_application():
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
Pode usar qualquer uma das APIs padrão do httpx
, como autenticação, cookies de sessão e envio de ficheiros.
from lilya.responses import HTMLResponse
from lilya.testclient import TestClient
async def app(scope, receive, send):
assert scope["type"] == "http"
response = HTMLResponse("<html><body>Hello, world!</body></html>")
await response(scope, receive, send)
client = TestClient(app)
# Set headers on the client for future requests
client.headers = {"Authorization": "..."}
response = client.get("/")
# Set headers for each request separately
response = client.get("/", headers={"Authorization": "..."})
TestClient
from lilya.responses import HTMLResponse
from lilya.testclient import TestClient
async def app(scope, receive, send):
assert scope["type"] == "http"
response = HTMLResponse("<html><body>Hello, world!</body></html>")
await response(scope, receive, send)
client = TestClient(app)
# Send a single file
with open("example.txt", "rb") as f:
response = client.post("/form", files={"file": f})
# Send multiple files
with open("example.txt", "rb") as f1:
with open("example.png", "rb") as f2:
files = {"file1": f1, "file2": ("filename", f2, "image/png")}
response = client.post("/form", files=files)
httpx
é uma ótima biblioteca criada pelo mesmo autor do Starlette
e do Django Rest Framework
.
Info
Por defeito, o TestClient lança qualquer exceção que ocorra na aplicação.
Ocasionalmente, pode querer testar o conteúdo das respostas de erros 500, em vez de permitir que o cliente lance a exceção do servidor. Nesse caso, deve utilizar client = TestClient(app, raise_server_exceptions=False)
.
Eventos de ciclo de vida¶
Note
O Lilya suporta todos os eventos de ciclo de vida disponíveis e, portanto, on_startup
, on_shutdown
e lifespan
também são suportados pelo TestClient
mas se
precisar testá-los, precisará executar o TestClient
como um gestor de contexto, caso contrário, os eventos não serão acionados quando o TestClient
for instanciado.
O Lilya também traz uma funcionalidade pronta a usar que pode ser utilizada como um gestor de contexto para testes, o create_client
.
Gestor de contexto create_client
¶
Esta função está preparada para ser utilizada como um gestor de contexto para testes e está pronta para ser utilizada a qualquer momento.
import pytest
from lilya.enums import MediaType
from lilya.responses import JSONResponse, Response
from lilya.routing import Include, Path, WebSocketPath
from lilya.testclient import create_client
from lilya.websockets import WebSocket
async def allow_access():
return JSONResponse("Hello, world")
async def homepage():
return Response("Hello, world")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
await websocket.send_text("Hello, world!")
await websocket.close()
routes = [
Path("/", handler=homepage, name="homepage"),
Include(
"/nested",
routes=[
Include(
path="/test/",
routes=[Path(path="/", handler=homepage, name="nested")],
),
Include(
path="/another",
routes=[
Include(
path="/test",
routes=[Path(path="/{param}", handler=homepage, name="nested")],
)
],
),
],
),
Include(
"/static",
app=Response("xxxxx", media_type=MediaType.PNG, status_code=200),
),
WebSocketPath("/ws", handler=websocket_endpoint, name="websocket_endpoint"),
Path("/allow", handler=allow_access, name="allow_access"),
]
@pytest.mark.filterwarnings(
r"ignore"
r":Trying to detect encoding from a tiny portion of \(5\) byte\(s\)\."
r":UserWarning"
r":charset_normalizer.api"
)
def test_router():
with create_client(routes=routes) as client:
response = client.get("/")
assert response.status_code == 200
assert response.text == "Hello, world"
response = client.post("/")
assert response.status_code == 405
assert response.json()["detail"] == "Method POST not allowed."
assert response.headers["content-type"] == MediaType.JSON
response = client.get("/foo")
assert response.status_code == 404
assert response.json()["detail"] == "The resource cannot be found."
response = client.get("/static/123")
assert response.status_code == 200
assert response.text == "xxxxx"
response = client.get("/nested/test")
assert response.status_code == 200
assert response.text == "Hello, world"
response = client.get("/nested/another/test/fluid")
assert response.status_code == 200
assert response.text == "Hello, world"
with client.websocket_connect("/ws") as session:
text = session.receive_text()
assert text == "Hello, world!"
Os testes funcionam tanto com funções sync
quanto async
.
Info
O exemplo acima também é usado para mostrar que os testes podem ser tão complexos quanto desejar e funcionarão com o gestor de contexto.
override_settings¶
Este é um decorator especial do Lilya e serve como auxiliar para os testes quando precisa atualizar/mudar as configurações temporariamente para testar qualquer cenário que exija valores específicos para as configurações terem valores diferentes.
O override_settings
atua como um decorator normal ou como um gestor de contexto.
As configurações que pode substituir são as declaradas nas configurações.
from lilya.testclient import override_settings
Vejamos um exemplo.
from lilya.apps import Lilya
from lilya.middleware import DefineMiddleware
from lilya.middleware.clickjacking import XFrameOptionsMiddleware
from lilya.responses import PlainText
from lilya.routing import Path
from lilya.testclient.utils import override_settings
@override_settings(x_frame_options="SAMEORIGIN")
def test_xframe_options_same_origin_responses(test_client_factory):
def homepage():
return PlainText("Ok", status_code=200)
app = Lilya(
routes=[Path("/", handler=homepage)],
middleware=[DefineMiddleware(XFrameOptionsMiddleware)],
)
client = test_client_factory(app)
response = client.get("/")
assert response.headers["x-frame-options"] == "SAMEORIGIN"
Ou como gestor de contexto.
from lilya.apps import Lilya
from lilya.middleware import DefineMiddleware
from lilya.middleware.clickjacking import XFrameOptionsMiddleware
from lilya.responses import PlainText
from lilya.routing import Path
from lilya.testclient.utils import override_settings
def test_xframe_options_same_origin_responses(test_client_factory):
def homepage():
return PlainText("Ok", status_code=200)
with override_settings(x_frame_options="SAMEORIGIN"):
app = Lilya(
routes=[Path("/", handler=homepage)],
middleware=[DefineMiddleware(XFrameOptionsMiddleware)],
)
client = test_client_factory(app)
response = client.get("/")
assert response.headers["x-frame-options"] == "SAMEORIGIN"