#27. TDD with TestClient
Let's test our own Telepact server directly, without even starting HTTP.
#Install what this example needs
pip install --pre telepact pytest#Reuse the minimum server, but leave one bug in it
Create server.py:
from telepact import FunctionRouter, Message, Server, TelepactSchema
schema = TelepactSchema.from_directory('./api')
options = Server.Options()
options.auth_required = False
async def hello(function_name: str, request_message: Message) -> Message:
name = request_message.body[function_name]['name']
return Message({}, {'Ok_': {'message': name}})
function_router = FunctionRouter({'fn.hello': hello})
telepact_server = Server(schema, function_router, options)That is intentionally wrong. We want Hello, Telepact!, but the server only returns Telepact.
#Point a client at telepact_server.process(...)
Create test_server.py:
import asyncio
import pytest
from telepact import Client, Message, Serializer, TestClient
from server import telepact_server
async def adapter(message: Message, serializer: Serializer) -> Message:
request_bytes = serializer.serialize(message)
response = await telepact_server.process(request_bytes)
return serializer.deserialize(response.bytes)
def make_test_client() -> TestClient:
client = Client(adapter, Client.Options())
return TestClient(client, TestClient.Options())
def test_hello_shows_the_actual_payload() -> None:
test_client = make_test_client()
with pytest.raises(AssertionError) as error_info:
asyncio.run(
test_client.assert_request(
Message({}, {'fn.hello': {'name': 'Telepact'}}),
{'Ok_': {'message': 'Hello, Telepact!'}},
True,
)
)
assert "Actual: {'Ok_': {'message': 'Telepact'}}" in str(error_info.value)
def test_hello_can_keep_going_with_multiple_assertions() -> None:
test_client = make_test_client()
response = asyncio.run(
test_client.assert_request(
Message({}, {'fn.hello': {'name': 'Telepact'}}),
{'Ok_': {'message': 'Hello, Telepact!'}},
False,
)
)
greeting = response.body['Ok_']['message']
assert greeting.startswith('Hello, ')
extracted_name = greeting.removeprefix('Hello, ')
assert extracted_name == 'Telepact!'#Run the tests
pytest -qThe first test is the usual red phase: the assertion fails, and the error text includes the actual payload that came back from the server.
The second test shows the more unusual part. It uses expect_match=False, which means we currently expect the server to not match yet. In that case, TestClient returns a schema-valid response built from the expected payload we supplied, so the test can keep going.
That matters when one assertion depends on the result of an earlier one. Here, the server really returned Telepact, but TestClient hands the test a response whose message still satisfies the expected shape. That lets us first assert startswith('Hello, ') and then use that result for the next assertion on Telepact!, all in one red-phase test.
#Fix the server
Once we are ready to make the test green, change the handler:
async def hello(function_name: str, request_message: Message) -> Message:
name = request_message.body[function_name]['name']
return Message({}, {'Ok_': {'message': f'Hello, {name}!'}})Now we can switch back to expect_match=True and keep the strict assertion.