This guide covers how to run the test suite, understand the test structure, and write new tests for the Puzzel SMS Gateway Python Client.
Dev dependencies are declared in pyproject.toml under [dependency-groups] dev and installed automatically by uv:
| Package | Purpose |
|---|---|
pytest |
Test runner |
pytest-asyncio |
Async test support (asyncio_mode = "auto") |
pytest-cov |
Coverage reporting |
Install them with:
uv syncAll commands below should be run from the project root (Generated/python/).
uv run pytestuv run pytest --cov=src --cov-report=term-missingTo generate an HTML report in htmlcov/:
uv run pytest --cov=src --cov-report=html# Run a single test file
uv run pytest tests/unit/test_models.py
# Run a single test by name
uv run pytest tests/unit/test_models.py::test_message_default_values
# Run a single test by full path
uv run pytest tests/unit/test_client.py::test_client_raises_type_error_when_adapter_is_none# Run all tests whose name contains "originator"
uv run pytest -k originator
# Run all tests whose name contains "serialize"
uv run pytest -k serializetests/
└── unit/
├── conftest.py # Shared fixtures
├── test_client.py # MtHttpClient initialization and properties
├── test_models.py # Data model field access and serialization
└── test_request_builders.py # Request building and HTTP method verification
asyncio_mode = "auto" is set in pyproject.toml, so any async def test_* function is automatically treated as an asyncio test — no @pytest.mark.asyncio decorator is needed.
MtHttpClient(None)raisesTypeError- Each route property (
gw,management) returns the correct request-builder type - The injected
request_adapteris stored and accessible
For every model (OriginatorType, Message, OriginatorSettings, GasSettings, SendWindow, Parameter, Settings, GatewayRequest, GatewayResponse, MessageStatus):
| Test category | What it verifies |
|---|---|
| Default values | All optional fields are None when not supplied |
| Field assignment | Values passed to the constructor are stored correctly |
create_from_discriminator_value(None) |
Raises TypeError |
serialize(None) |
Raises TypeError |
serialize(writer) |
Calls the writer with the correct camelCase JSON key for each field |
get_field_deserializers() |
Returns a dict whose keys match the camelCase JSON property names |
OriginatorType (enum) is additionally tested to confirm each member serialises to the expected string value.
tests/unit/test_request_builders.py
post(None)raisesTypeError(async guard)to_post_request_information(None)raisesTypeErrorto_post_request_information(body)returns aRequestInformationwithMethod.POSTand anAccept: application/jsonheaderto_get_request_information()returns aRequestInformationwithMethod.GETwith_url(None)raisesTypeErrorwith_url(url)returns a newSendMessagesRequestBuilderinstance- Smoke test confirming
client.gw.rs.send_messagesreturns the correct builder type
All shared fixtures are defined in tests/unit/conftest.py.
| Fixture | Type | Description |
|---|---|---|
mock_adapter |
MagicMock |
A mock RequestAdapter with base_url set and get_serialization_writer_factory() returning a JsonSerializationWriterFactory |
client |
MtHttpClient |
A client built with mock_adapter |
basic_message |
Message |
recipient="+4799999999", content="Hello from tests!" |
originator_settings |
OriginatorSettings |
International type with originator="+4799999999" |
gas_settings |
GasSettings |
service_code="01010", description="Test service" |
send_window |
SendWindow |
Start 2024-01-15 08:00:00, stop 2024-01-16 20:00:00 |
parameter |
Parameter |
key="someKey", value="someValue" |
settings |
Settings |
Fully populated using the fixtures above |
gateway_request |
GatewayRequest |
Uses basic_message, SERVICE_ID=12345, BATCH_REFERENCE="test-batch-001" |
message_status |
MessageStatus |
status_code=200, message_id="msg-001" |
gateway_response |
GatewayResponse |
batch_reference="test-batch-001" with one message_status |
from src.models.message import Message
def test_message_has_correct_recipient() -> None:
msg = Message(recipient="+4799999999")
assert msg.recipient == "+4799999999"Because asyncio_mode = "auto", simply declare the function as async:
import pytest
from src.gw.rs.send_messages.send_messages_request_builder import (
SendMessagesRequestBuilder,
)
async def test_post_raises_on_none_body(
send_messages_builder: SendMessagesRequestBuilder,
) -> None:
with pytest.raises(TypeError):
await send_messages_builder.post(None)Use @pytest.mark.parametrize to cover multiple inputs with a single test function:
import pytest
from src.models.originator_settings import OriginatorSettings
from src.models.originator_type import OriginatorType
@pytest.mark.parametrize(
"originator,originator_type",
[
("+4799999999", OriginatorType.International),
("Puzzel", OriginatorType.Alphanumeric),
("1960", OriginatorType.Network),
],
)
def test_originator_settings(
originator: str,
originator_type: OriginatorType,
) -> None:
settings = OriginatorSettings(
originator=originator,
originator_type=originator_type,
)
assert settings.originator == originator
assert settings.originator_type == originator_type