UniteLabs
Guides

Testing

How to test your connector.

For this guide we will build upon the example AwesomeInstrumentProtocol that we declared in the Walkthrough to explore the topic of testing.

Protocol Unit Testing

Within the Omnibus package there are pre-configured fixtures designed to simplify the complexity of testing your protocol.

To access Omnibus fixtures via pytest, simply add the following to your conftest file:

tests/conftest.py
pytest_plugins = ["unitelabs.bus.testing.fixtures"]

One such fixture is mk_stubbed_protocol, which takes a Protocol subclass, a dictionary mapping between request bytes and return byte values, and the kwargs required for protocol initialization and returns an instance of your Protocol that mocks out device interaction and instead responds to commands sent via execute based on the provided dictionary mapping. In this way we can test that our protocol is handling all the different responses that the device can send.

Here we will show how to use the fixture mk_stubbed_protocol to create an instance of the provided Protocol and mocks out the device responses based on a provided mapping between requests and responses. We can wrap this fixture into our own fixture as follows:

1. Protocol Test Fixture

tests/conftest.py
import typing

import pytest

from unitelabs.awesome_instrument.io.awesome_instrument_protocol import AwesomeInstrumentProtocol

pytest_plugins = ["unitelabs.bus.testing.fixtures"]


@pytest.fixture
def mk_my_protocol(mk_stubbed_protocol):
    def _mk_my_protocol(data: dict[bytes, typing.Union[bytes, list[bytes]]], **kwargs) -> AwesomeInstrumentProtocol:
        return mk_stubbed_protocol(AwesomeInstrumentProtocol, data, **kwargs)

    return _mk_my_protocol

By creating a fixture that returns a function, we are now able to call that function in all of our tests with a custom data dict and test our Protocols behavior under different conditions and when different responses are sent from the device. (Alternatively we could also just use the mk_stubbed_protocol fixture directly, but creating a fixture will reduce repetition in our code).

2. Mock Device Communication

tests/test_awesome_instrument_protocol.py
import pytest

from unitelabs.awesome_instrument.io.awesome_instrument_protocol import AwesomeInstrumentProtocol
from unitelabs.awesome_instrument.io.errors import NotOkException


async def test_should_get_status(mk_my_protocol):
    protocol: AwesomeInstrumentProtocol = mk_my_protocol({b"status": b"ok"})

    await protocol.open()
    response = await protocol.get_status()
    print(response)

    assert response == b"ok"


async def test_should_raise_if_not_ok(mk_my_protocol):
    protocol: AwesomeInstrumentProtocol = mk_my_protocol({b"status": b"not ok"})

    await protocol.open()
    with pytest.raises(NotOkException, match=r"Everything is not ok\."):
        print(await protocol.get_status())

Here we use our mk_my_protocol fixture to test all possible return values from the device and ensure that the our protocol method is behaving as expected given the known responses from the device.


Copyright © 2025