labthings_fastapi.testing

Test harnesses to help with writitng tests for things..

Attributes

Params

ReturnType

ThingSubclass

Classes

MockThingServerInterface

A mock class that simulates a ThingServerInterface without the server.

Functions

create_thing_without_server(→ ThingSubclass)

Create a Thing and supply a mock ThingServerInterface.

mock_thing_instance(→ ThingSubclass)

Create a mock Thing instance, with some important attributes.

_mock_slots(→ None)

Mock the slots of a thing created by create_thing_without_server.

use_dummy_url_for(→ collections.abc.Iterator[None])

Use the dummy URL for function in the context variable.

manually_connect_thing_slot(→ None)

Manually connect a thing_slot.

Module Contents

labthings_fastapi.testing.Params
labthings_fastapi.testing.ReturnType
class labthings_fastapi.testing.MockThingServerInterface(name: str, class_name: str, settings_folder: str | None = None, enable_global_lock: bool = False)

Bases: labthings_fastapi.thing_server_interface.ThingServerInterface

A mock class that simulates a ThingServerInterface without the server.

This allows a Thing to be instantiated but not connected to a server. The methods normally provided by the server are mocked, specifically:

  • The name is set by an argument to __init__.

  • start_async_task_soon silently does nothing, i.e. the async function will not be run.

  • The settings folder will either be specified when the class is initialised, or a temporary folder will be created.

  • get_thing_states will return an empty dictionary.

Initialise a ThingServerInterface.

Parameters:
  • name – The name of the Thing we’re providing an interface to.

  • class_name – The name of the class of the Thing, used as part of the settings filename.

  • settings_folder – The location where we should save settings. By default, this is a temporary directory.

  • enable_global_lock – Whether to create a global lock object, to mock the server setting of the same name.

_name: str
_settings_tempdir: tempfile.TemporaryDirectory | None = None
_settings_folder = None
_global_lock
_mocks: list[labthings_fastapi.thing.Thing] = []
_class_name
abstract _get_server() labthings_fastapi.server.ThingServer

Raise NotImplementedError as this is not mocked.

Returns:

the server to which we are connected.

Raises:

NotImplementedError – because this function is not mocked.

start_async_task_soon(async_function: Callable[Params, Awaitable[ReturnType]], *args: Any) concurrent.futures.Future[ReturnType]

Do nothing, as there’s no event loop to use.

This returns a concurrent.futures.Future object that is already cancelled, in order to avoid accidental hangs in test code that attempts to wait for the future object to resolve. Cancelling it may cause errors if you need the return value.

If you need the async code to run, it’s best to add the Thing to a lt.ThingServer instead. Using a test client will start an event loop in a background thread, and allow you to use a real ThingServerInterface without the overhead of actually starting an HTTP server.

Parameters:
  • async_function – the asynchronous function to call.

  • *args – positional arguments to be provided to the function.

Returns:

a concurrent.futures.Future object that has been cancelled.

publish(message: labthings_fastapi.message_broker.Message) None

Silently ignore published events.

Parameters:

message – a message to publish.

property settings_folder: str

The path to a folder where persistent files may be saved.

This will create a temporary folder the first time it is called, and return the same folder on subsequent calls.

Returns:

the path to a temporary folder.

property path: str

The path, relative to the server’s base URL, of the Thing.

A ThingServerInterface is specific to one Thing, so this path points to the base URL of the Thing, i.e. the Thing Description’s endpoint.

get_thing_states() Mapping[str, Any]

Return an empty dictionary to mock the metadata dictionary.

Returns:

an empty dictionary.

property _action_manager: labthings_fastapi.actions.ActionManager
Abstractmethod:

Raise an error, as there’s no action manager without a server.

Raises:

NotImplementedError – always.

property application_config: None

Return an empty application configuration when mocking.

Returns:

None

property global_lock: labthings_fastapi.global_lock.GlobalLock | None

Return a global lock.

labthings_fastapi.testing.ThingSubclass
labthings_fastapi.testing.create_thing_without_server(cls: type[ThingSubclass], *args: Any, settings_folder: str | None = None, mock_all_slots: bool = False, enable_global_lock: bool = True, **kwargs: Any) ThingSubclass

Create a Thing and supply a mock ThingServerInterface.

This function is intended for use in testing, where it will enable a Thing to be created without a server, by supplying a MockThingServerInterface instead of a real ThingServerInterface.

The name of the Thing will be taken from the class name, lowercased.

Parameters:
  • cls – The Thing subclass to instantiate.

  • *args – positional arguments to __init__.

  • settings_folder – The path to the settings folder. A temporary folder is used by default.

  • mock_all_slots – Set to True to create a unittest.mock.Mock object connected to each thing slot. It follows the default of the specified to the slot. So if an optional slot has a default of None, no mock will be provided.

  • enable_global_lock – Whether a global lock should be provided.

  • **kwargs – keyword arguments to __init__.

Returns:

an instance of cls with a MockThingServerInterface so that it will function without a server.

Raises:

ValueError – if a keyword argument called ‘thing_server_interface’ is supplied, as this would conflict with the mock interface.

labthings_fastapi.testing.mock_thing_instance(spec: type[ThingSubclass]) ThingSubclass

Create a mock Thing instance, with some important attributes.

This provides __name__, __module__, and _thing_server_interface properties that work correctly, which is convenient when mocking lt.thing_slot connections.

Parameters:

spec – the Thing subclass we’re mocking an instance of. Pass lt.Thing if it doesn’t matter.

Returns:

a Mock instance that pretends to be an instance of spec.

labthings_fastapi.testing._mock_slots(thing: labthings_fastapi.thing.Thing) None

Mock the slots of a thing created by create_thing_without_server.

Parameters:

thing – The thing to mock the slots of.

Raises:

TypeError – If this was called on a Thing with a real ThingServerInterface

labthings_fastapi.testing.use_dummy_url_for() collections.abc.Iterator[None]

Use the dummy URL for function in the context variable.

labthings_fastapi.testing.manually_connect_thing_slot(host: labthings_fastapi.thing.Thing, slot_name: str, target: labthings_fastapi.thing.Thing | collections.abc.Sequence[labthings_fastapi.thing.Thing]) None

Manually connect a thing_slot.

This will accept either a single Thing instance or a sequence of Thing instances. If Mock instances are used, note that they must pass an isinstance test, so should use the spec argument to specify the correct class for the thing_slot being mocked. Mock instances must also provide a unique name attribute.

Parameters:
  • host – the Thing on which the slot is defined.

  • slot_name – the name of the thing_slot.

  • target – the Thing or sequence of Things it should be connected to. If a sequence of multiple Thing are passed, their names are used to create a mapping.

Raises:

KeyError – if multiple targets are specified, but they do not have unique names.