.. note::

   Ophyd async is included on a provisional basis until the v1.0 release and 
   may change API on minor release numbers before then

Write Tests for Devices
=======================

Testing ophyd-async devices using tools like mocking, patching, and fixtures can become complicated very quickly. The library provides several utilities to make it easier.

Async Tests
-----------

`pytest-asyncio <https://github.com/pytest-dev/pytest-asyncio>`_ is required for async tests. It is should be included as a dev dependency of your project. Tests can either be decorated with ``@pytest.mark.asyncio`` or the project can be automatically configured to detect async tests.

.. code:: toml

   # pyproject.toml

   [tool.pytest.ini_options]
   ...
   asyncio_mode = "auto"

Mock Backend
------------

Ophyd devices initialized with a mock backend behave in a similar way to mocks, without requiring you to mock out all the dependencies and internals. The `DeviceCollector` can initialize any number of devices, and their signals and sub-devices (recursively), with a mock backend.

.. literalinclude:: ../../tests/epics/demo/test_demo.py
   :pyobject: mock_sensor


Mock Utility Functions
----------------------

Mock signals behave as simply as possible, holding a sensible default value when initialized and retaining any value (in memory) to which they are set. This model breaks down in the case of read-only signals, which cannot be set because there is an expectation of some external device setting them in the real world. There is a utility function, ``set_mock_value``, to mock-set values for mock signals, including read-only ones.

In addition this example also utilizes helper functions like ``assert_reading`` and ``assert_value`` to ensure the validity of device readings and values. For more information see: :doc:`API.core<../generated/ophyd_async.core>`

.. literalinclude:: ../../tests/epics/demo/test_demo.py
   :pyobject: test_sensor_reading_shows_value


Given that the mock signal holds a ``unittest.mock.Mock`` object you can retrieve this object and assert that the device has been set correctly using ``get_mock_put``. You are also free to use any other behaviour that ``unittest.mock.Mock`` provides, such as in this example which sets the parent of the mock to allow ordering across signals to be asserted:

.. literalinclude:: ../../tests/epics/demo/test_demo.py
   :pyobject: test_retrieve_mock_and_assert

There are several other test utility functions:

Use ``callback_on_mock_put``, for hooking in logic when a mock value changes (e.g. because someone puts to it). This can be called directly, or used as a context, with the callbacks ending after exit.

.. literalinclude:: ../../tests/epics/demo/test_demo.py
   :pyobject: test_mover_stopped


Testing a Device in a Plan with the RunEngine
---------------------------------------------
.. literalinclude:: ../../tests/epics/demo/test_demo.py
   :pyobject: test_sensor_in_plan


This test verifies that the sim_sensor behaves as expected within a plan. The plan we use here is a ``count``, which takes a specified number of readings from the ``sim_sensor``. Since we set the ``repeat`` to two in this test, the sensor should emit two "event" documents along with "start", "stop" and "descriptor" documents. Finally, we use the helper function ``assert_emitted`` to confirm that the emitted documents match our expectations.
