Skip to content

psygnal.testing #

Utilities for testing psygnal Signals.

Classes:

  • SignalTester

    A tester object that listens to a signal and records its emissions.

Functions:

ConnectKwargs #

Bases: TypedDict

Kwargs for SignalInstance.connect.

SignalTester #

SignalTester(
    signal: SignalInstance | SignalGroup,
    connect_kwargs: ConnectKwargs | None = None,
)

A tester object that listens to a signal and records its emissions.

This class wraps a SignalInstance and a unittest.mock.Mock object. It provides methods to connect and disconnect the mock from the signal, and to assert that the signal was emitted with the expected arguments. It also behaves as a context manager, so you can monitor emissions of a signal within a specific context.

Important

The signal is not automatically connected to the mock when the SignalTester is created. You must call connect() or use the context manager to connect the mock to the signal.

Parameters:

Attributes:

  • signal_instance (SignalInstance) –

    The signal instance being tested. If a SignalGroup is passed, it uses the _psygnal_relay attribute to get the underlying SignalInstance.

  • mock (Mock) –

    The mock object that will be connected to the signal.

Examples:

from psygnal import Signal
from psygnal.testing import SignalTester


class MyObject:
    value_changed = Signal(int)


obj = MyObject()
tester = SignalTester(obj.value_changed)
tester.assert_not_emitted()

with tester:
    obj.value_changed.emit(1)

tester.assert_emitted()
tester.assert_emitted_once()
tester.assert_emitted_once_with(1)
assert tester.emit_count == 1
tester.reset()
assert tester.emit_count == 0

Methods:

Source code in src/psygnal/testing.py
100
101
102
103
104
105
106
107
108
109
110
111
112
def __init__(
    self,
    signal: SignalInstance | SignalGroup,
    connect_kwargs: ConnectKwargs | None = None,
) -> None:
    super().__init__()
    self.mock = Mock()
    if isinstance(signal, SignalGroup):
        signal_instance: SignalInstance = signal._psygnal_relay
    else:
        signal_instance = signal
    self.signal_instance: SignalInstance = signal_instance
    self.connect_kwargs = connect_kwargs or {}

emit_args property #

emit_args: tuple[Any, ...]

Return the arguments of the last emission of the signal.

emit_args_list property #

emit_args_list: list[tuple[Any, ...]]

Return the arguments of all emissions of the signal.

emit_count property #

emit_count: int

Return the number of times the signal was emitted.

signal_name property #

signal_name: str

Return the name of the signal.

assert_emitted #

assert_emitted() -> None

Assert that the signal was emitted at least once.

Source code in src/psygnal/testing.py
169
170
171
172
def assert_emitted(self) -> None:
    """Assert that the signal was emitted at least once."""
    if self.mock.call_count == 0:
        raise AssertionError(f"Expected {self.signal_name!r} to have been emitted.")

assert_emitted_once #

assert_emitted_once() -> None

Assert that the signal was emitted exactly once.

Source code in src/psygnal/testing.py
174
175
176
177
178
179
180
def assert_emitted_once(self) -> None:
    """Assert that the signal was emitted exactly once."""
    if not self.mock.call_count == 1:
        raise AssertionError(
            f"Expected {self.signal_name!r} to have been emitted once. "
            f"Emitted {self.mock.call_count} times."
        )

assert_emitted_once_with #

assert_emitted_once_with(*args: Any) -> None

Assert that the signal was emitted exactly once with the given arguments.

Source code in src/psygnal/testing.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def assert_emitted_once_with(self, /, *args: Any) -> None:
    """Assert that the signal was emitted exactly once with the given arguments."""
    if not self.mock.call_count == 1:
        raise AssertionError(
            f"Expected {self.signal_name!r} to have been emitted exactly once. "
            f"Emitted {self.mock.call_count} times."
        )

    actual = self.mock.call_args[0]
    if actual != args:
        raise AssertionError(
            f"Expected {self.signal_name!r} to have been emitted once with "
            f"arguments {args!r}.\nActual: {safe_repr(actual)}"
        )

assert_emitted_with #

assert_emitted_with(*args: Any) -> None

Assert that the last emission of the signal had the given arguments.

Source code in src/psygnal/testing.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def assert_emitted_with(self, /, *args: Any) -> None:
    """Assert that the *last* emission of the signal had the given arguments."""
    if self.mock.call_args is None:
        raise AssertionError(
            f"Expected {self.signal_name!r} to have been emitted with arguments "
            f"{args!r}.\nActual: not emitted"
        )

    actual = self.mock.call_args[0]
    if actual != args:
        raise AssertionError(
            f"Expected {self.signal_name!r} to have been emitted with arguments "
            f"{args!r}.\nActual: {actual}"
        )

assert_ever_emitted_with #

assert_ever_emitted_with(*args: Any) -> None

Assert that the signal was emitted ever with the given arguments.

Source code in src/psygnal/testing.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def assert_ever_emitted_with(self, /, *args: Any) -> None:
    """Assert that the signal was emitted *ever* with the given arguments."""
    if self.mock.call_args is None:
        raise AssertionError(
            f"Expected {self.signal_name!r} to have been emitted at least once "
            f"with arguments {args!r}.\nActual: not emitted"
        )

    actual = [call[0] for call in self.mock.call_args_list]
    if not any(call == args for call in actual):
        _actual: tuple | list = actual[0] if len(actual) == 1 else actual
        raise AssertionError(
            f"Expected {self.signal_name!r} to have been emitted at least once "
            f"with arguments {args!r}.\nActual: {safe_repr(_actual)}"
        )

assert_not_emitted #

assert_not_emitted() -> None

Assert that the signal was never emitted.

Source code in src/psygnal/testing.py
158
159
160
161
162
163
164
165
166
167
def assert_not_emitted(self) -> None:
    """Assert that the signal was never emitted."""
    if self.mock.call_count != 0:
        if self.mock.call_count == 1:
            n = "once"
        else:
            n = f"{self.mock.call_count} times"
        raise AssertionError(
            f"Expected {self.signal_name!r} to not have been emitted. Emitted {n}."
        )

connect #

connect() -> None

Connect the mock to the signal.

Source code in src/psygnal/testing.py
136
137
138
def connect(self) -> None:
    """Connect the mock to the signal."""
    self.signal_instance.connect(self.mock, **self.connect_kwargs)

disconnect #

disconnect() -> None

Disconnect the mock from the signal.

Source code in src/psygnal/testing.py
140
141
142
def disconnect(self) -> None:
    """Disconnect the mock from the signal."""
    self.signal_instance.disconnect(self.mock)

reset #

reset() -> None

Reset the underlying mock object.

Source code in src/psygnal/testing.py
114
115
116
def reset(self) -> None:
    """Reset the underlying mock object."""
    self.mock.reset_mock()

assert_emitted #

assert_emitted(
    signal: SignalInstance | SignalGroup,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]

Assert that a signal was emitted at least once.

Parameters:

Raises:

Source code in src/psygnal/testing.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
@contextmanager
def assert_emitted(
    signal: SignalInstance | SignalGroup, connect_kwargs: ConnectKwargs | None = None
) -> Iterator[SignalTester]:
    """Assert that a signal was emitted at least once.

    Parameters
    ----------
    signal : SignalInstance | SignalGroup
        The signal instance or group to test.
    connect_kwargs : ConnectKwargs
        Keyword arguments to pass to the
        [`SignalInstance.connect()`][psygnal.SignalInstance.connect] method.

    Raises
    ------
    AssertionError
        If the signal was never emitted.
    """
    with SignalTester(signal, connect_kwargs) as mock:
        yield mock
        mock.assert_emitted()

assert_emitted_once #

assert_emitted_once(
    signal: SignalInstance | SignalGroup,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]

Assert that a signal was emitted exactly once.

Parameters:

Raises:

Source code in src/psygnal/testing.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
@contextmanager
def assert_emitted_once(
    signal: SignalInstance | SignalGroup, connect_kwargs: ConnectKwargs | None = None
) -> Iterator[SignalTester]:
    """Assert that a signal was emitted exactly once.

    Parameters
    ----------
    signal : SignalInstance | SignalGroup
        The signal instance or group to test.
    connect_kwargs : ConnectKwargs
        Keyword arguments to pass to the
        [`SignalInstance.connect()`][psygnal.SignalInstance.connect] method.

    Raises
    ------
    AssertionError
        If the signal was emitted more than once.
    """
    with SignalTester(signal, connect_kwargs) as mock:
        yield mock
        mock.assert_emitted_once()

assert_emitted_once_with #

assert_emitted_once_with(
    signal: SignalInstance | SignalGroup,
    *args: Any,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]

Assert that the signal was emitted exactly once with the given arguments.

Parameters:

Raises:

  • AssertionError

    If the signal was not emitted or was emitted more than once or if the last emission did not have the expected arguments.

Source code in src/psygnal/testing.py
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
@contextmanager
def assert_emitted_once_with(
    signal: SignalInstance | SignalGroup,
    *args: Any,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]:
    """Assert that the signal was emitted exactly once with the given arguments.

    Parameters
    ----------
    signal : SignalInstance | SignalGroup
        The signal instance or group to test.
    *args : Any
        The arguments to check for in the last emission of the signal.
    connect_kwargs : ConnectKwargs
        Keyword arguments to pass to the
        [`SignalInstance.connect()`][psygnal.SignalInstance.connect] method.

    Raises
    ------
    AssertionError
        If the signal was not emitted or was emitted more than once or if the last
        emission did not have the expected arguments.
    """
    with assert_emitted_once(signal, connect_kwargs) as mock:
        yield mock
        mock.assert_emitted_once_with(*args)

assert_emitted_with #

assert_emitted_with(
    signal: SignalInstance | SignalGroup,
    *args: Any,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]

Assert that the last emission of the signal had the given arguments.

Parameters:

Raises:

  • AssertionError

    If the signal was never emitted or if the last emission did not have the expected arguments.

Source code in src/psygnal/testing.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
@contextmanager
def assert_emitted_with(
    signal: SignalInstance | SignalGroup,
    *args: Any,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]:
    """Assert that the *last* emission of the signal had the given arguments.

    Parameters
    ----------
    signal : SignalInstance | SignalGroup
        The signal instance or group to test.
    *args : Any
        The arguments to check for in the last emission of the signal.
    connect_kwargs : ConnectKwargs
        Keyword arguments to pass to the
        [`SignalInstance.connect()`][psygnal.SignalInstance.connect] method.

    Raises
    ------
    AssertionError
        If the signal was never emitted or if the last emission did not have the
        expected arguments.
    """
    with assert_emitted(signal, connect_kwargs) as mock:
        yield mock
        mock.assert_emitted_with(*args)

assert_ever_emitted_with #

assert_ever_emitted_with(
    signal: SignalInstance | SignalGroup,
    *args: Any,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]

Assert that the signal was emitted ever with the given arguments.

Parameters:

Raises:

  • AssertionError

    If the signal was never emitted or if it was emitted but not with the expected arguments.

Source code in src/psygnal/testing.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
@contextmanager
def assert_ever_emitted_with(
    signal: SignalInstance | SignalGroup,
    *args: Any,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]:
    """Assert that the signal was emitted *ever* with the given arguments.

    Parameters
    ----------
    signal : SignalInstance | SignalGroup
        The signal instance or group to test.
    *args : Any
        The arguments to check for in any emission of the signal.
    connect_kwargs : ConnectKwargs
        Keyword arguments to pass to the
        [`SignalInstance.connect()`][psygnal.SignalInstance.connect] method.

    Raises
    ------
    AssertionError
        If the signal was never emitted or if it was emitted but not with the expected
        arguments.
    """
    with assert_emitted(signal, connect_kwargs) as mock:
        yield mock
        mock.assert_ever_emitted_with(*args)

assert_not_emitted #

assert_not_emitted(
    signal: SignalInstance | SignalGroup,
    connect_kwargs: ConnectKwargs | None = None,
) -> Iterator[SignalTester]

Assert that a signal was never emitted.

Parameters:

Raises:

Source code in src/psygnal/testing.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
@contextmanager
def assert_not_emitted(
    signal: SignalInstance | SignalGroup, connect_kwargs: ConnectKwargs | None = None
) -> Iterator[SignalTester]:
    """Assert that a signal was never emitted.

    Parameters
    ----------
    signal : SignalInstance | SignalGroup
        The signal instance or group to test.
    connect_kwargs : ConnectKwargs
        Keyword arguments to pass to the
        [`SignalInstance.connect()`][psygnal.SignalInstance.connect] method.

    Raises
    ------
    AssertionError
        If the signal was emitted at least once.
    """
    with SignalTester(signal, connect_kwargs) as mock:
        yield mock
        mock.assert_not_emitted()