Testing guidelines#
Tips for testing napari plugins#
Testing is a big topic! If you are completely new to writing tests in Python, consider reading this post on Getting Started With Testing in Python
We recommend using pytest for testing your plugin. Aim for 100% test coverage!
The make_napari_viewer_proxy
fixture#
Testing a napari Viewer
requires some setup and teardown each time. We have
created a pytest fixture called
make_napari_viewer_proxy
that you can use (this requires that you have napari
installed in your environment).
To use a fixture in pytest, you simply include the name of the fixture in the test parameters (oddly enough, you don’t need to import it!). For example, to create a napari viewer for testing:
def test_something_with_a_viewer(make_napari_viewer_proxy):
viewer = make_napari_viewer_proxy()
... # carry on with your test
If you embed the viewer in your own application and need to access private attributes,
you can use the make_napari_viewer
fixture.
Prefer smaller unit tests when possible#
The most common issue people run into when designing tests for napari plugins is that they try to test everything as a full “integration test”, starting from the napari event or action that would trigger their plugin to do something. For example, let’s say you have a dock widget that connects a mouse callback to the viewer:
class MyWidget:
def __init__(self, viewer: 'napari.Viewer'):
self._viewer = viewer
@viewer.mouse_move_callbacks.append
def _on_mouse_move(viewer, event):
if 'Shift' in event.modifiers:
...
@napari_hook_implementation
def napari_experimental_provide_dock_widget():
return MyWidget
You might think that you need to somehow simulate a mouse movement in napari in
order to test this, but you don’t! Just trust that napari will call this
function with a Viewer
and an Event
when a mouse move has been made, and
otherwise leave napari
out of it.
Instead, focus on “unit testing” your code: just call the function directly with objects that emulate, or “mock” the objects that your function expects to receive from napari. You may also need to slightly reorganize your code. Let’s modify the above widget to make it easier to test:
class MyWidget:
def __init__(self, viewer: 'napari.Viewer'):
self._viewer = viewer
# connecting to a method rather than a local function
# makes it easier to test
viewer.mouse_move_callbacks.append(self._on_mouse_move)
def _on_mouse_move(self, viewer, event):
if 'Shift' in event.modifiers:
...
To test this, we can often just instantiate the widget with our own viewer, and
then call the methods directly. As for the event
object, notice that all we
care about in this plugin is that it has a modifiers
attribute that may or may
not contain the string "Shift"
. So let’s just fake it!
class FakeEvent:
modifiers = {'Shift'}
def test_mouse_callback(make_napari_viewer):
viewer = make_napari_viewer()
wdg = MyWidget(viewer)
wdg._on_mouse_move(viewer, FakeEvent())
# assert that what you expect to happen actually happened!