labthings_fastapi.testing ========================= .. py:module:: labthings_fastapi.testing .. autoapi-nested-parse:: Test harnesses to help with writitng tests for things.. Attributes ---------- .. autoapisummary:: labthings_fastapi.testing.Params labthings_fastapi.testing.ReturnType labthings_fastapi.testing.ThingSubclass Classes ------- .. autoapisummary:: labthings_fastapi.testing.MockThingServerInterface Functions --------- .. autoapisummary:: labthings_fastapi.testing.create_thing_without_server labthings_fastapi.testing.mock_thing_instance labthings_fastapi.testing._mock_slots labthings_fastapi.testing.use_dummy_url_for labthings_fastapi.testing.manually_connect_thing_slot Module Contents --------------- .. py:data:: Params .. py:data:: ReturnType .. py:class:: MockThingServerInterface(name: str, class_name: str, settings_folder: str | None = None, enable_global_lock: bool = False) Bases: :py:obj:`labthings_fastapi.thing_server_interface.ThingServerInterface` A mock class that simulates a ThingServerInterface without the server. This allows a `~lt.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. :param name: The name of the Thing we're providing an interface to. :param class_name: The name of the class of the Thing, used as part of the settings filename. :param settings_folder: The location where we should save settings. By default, this is a temporary directory. :param enable_global_lock: Whether to create a global lock object, to mock the server setting of the same name. .. py:attribute:: _name :type: str .. py:attribute:: _settings_tempdir :type: tempfile.TemporaryDirectory | None :value: None .. py:attribute:: _settings_folder :value: None .. py:attribute:: _global_lock .. py:attribute:: _mocks :type: list[labthings_fastapi.thing.Thing] :value: [] .. py:attribute:: _class_name .. py:method:: _get_server() -> labthings_fastapi.server.ThingServer :abstractmethod: Raise `NotImplementedError` as this is not mocked. :return: the server to which we are connected. :raises NotImplementedError: because this function is not mocked. .. py:method:: 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 `~lt.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 `~lt.ThingServerInterface` without the overhead of actually starting an HTTP server. :param async_function: the asynchronous function to call. :param \*args: positional arguments to be provided to the function. :returns: a `concurrent.futures.Future` object that has been cancelled. .. py:method:: publish(message: labthings_fastapi.message_broker.Message) -> None Silently ignore published events. :param message: a message to publish. .. py:property:: settings_folder :type: 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. .. py:property:: path :type: 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. .. py:method:: get_thing_states() -> Mapping[str, Any] Return an empty dictionary to mock the metadata dictionary. :returns: an empty dictionary. .. py:property:: _action_manager :type: labthings_fastapi.actions.ActionManager :abstractmethod: Raise an error, as there's no action manager without a server. :raises NotImplementedError: always. .. py:property:: application_config :type: None Return an empty application configuration when mocking. :return: None .. py:property:: global_lock :type: labthings_fastapi.global_lock.GlobalLock | None Return a global lock. .. py:data:: ThingSubclass .. py:function:: 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 `~lt.Thing` and supply a mock ThingServerInterface. This function is intended for use in testing, where it will enable a `~lt.Thing` to be created without a server, by supplying a `.MockThingServerInterface` instead of a real `~lt.ThingServerInterface`\ . The name of the Thing will be taken from the class name, lowercased. :param cls: The `~lt.Thing` subclass to instantiate. :param \*args: positional arguments to ``__init__``. :param settings_folder: The path to the settings folder. A temporary folder is used by default. :param 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. :param enable_global_lock: Whether a global lock should be provided. :param \**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. .. py:function:: 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. :param spec: the Thing subclass we're mocking an instance of. Pass `lt.Thing` if it doesn't matter. :return: a Mock instance that pretends to be an instance of `spec`. .. py:function:: _mock_slots(thing: labthings_fastapi.thing.Thing) -> None Mock the slots of a thing created by create_thing_without_server. :param thing: The thing to mock the slots of. :raises TypeError: If this was called on a Thing with a real ThingServerInterface .. py:function:: use_dummy_url_for() -> collections.abc.Iterator[None] Use the dummy URL for function in the context variable. .. py:function:: 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 `~lt.thing_slot` being mocked. Mock instances must also provide a unique ``name`` attribute. :param host: the `~lt.Thing` on which the slot is defined. :param slot_name: the name of the `~lt.thing_slot`. :param target: the `~lt.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.