How to Fix a Fatal Python Error in Your First pytest-qt Test

When I wrote my very first test with pytest qt, I was greeted not with a helpful failure but with a scary red banner:

Fatal Python error: Aborted

At first, I thought I had broken something in my test logic. But after reducing the code, I realized the crash was happening before my test body even ran. That’s when I discovered the real culprit: mismatched Qt libraries.

My First Test

Here’s the minimal snippet I started with:

from PyQt5 import QtCore as qtc

class Sut(qtc.QObject):
    sig_sig_sputnik = qtc.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)

    def listen(self):
        pass

def test_emit_should_timeout(qtbot):
    uut = Sut()
    with qtbot.waitSignal(uut.sig_sig_sputnik, raising=True, timeout=200):
        uut.listen()

This test should just fail with a timeout (because listen never emits). But instead, I got a Fatal Python error deep inside pytestqt/plugin.py’s qapp fixture.

Why the Fatal Abort Happen

The problem wasn’t my code. It was my environment.

  • My pip-installed PyQt5==5.10.1 was compiled against Qt 5.9.x.
  • On my Ubuntu system, I had Qt 5.12.x libraries on disk.
  • I had LD_LIBRARY_PATH pointing at the 5.12 runtime.

That meant:

  • Python loaded PyQt5 (expecting Qt 5.9),
  • Qt found a mix of 5.9 and 5.12 libs,
  • and Qt aborted immediately because it doesn’t allow ABI mismatches.

That’s why pytest reported it as a Fatal Python error—Qt killed the process before the test even started.

How I Fix It

I learned the hard way that you must keep Qt consistent. Here are three approaches that worked:

All-pip (my choice)

python3 -m venv .venv
source .venv/bin/activate
pip install -U pip setuptools wheel
pip install "PyQt5==5.12.3" pytest pytest-qt
unset LD_LIBRARY_PATH
pytest -q

All-system
Remove pip’s PyQt5, install python3-pyqt5 and friends from apt, and don’t mess with LD_LIBRARY_PATH.

Conda

conda create -n qt-tests python=3.10 pyqt pytest pytest-qt
conda activate qt-tests
pytest -q

I also ran sanity checks:

import PyQt5.QtCore as qtc
print(qtc.QT_VERSION_STR)  # Compile-time
print(qtc.qVersion())      # Runtime

Both should match. If they don’t, you’ll crash.

A Passing Test Example

Once my environment was fixed, this code passed perfectly:

from PyQt5 import QtCore as qtc

class Sut(qtc.QObject):
    sig_sig_sputnik = qtc.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)

    def listen(self):
        self.sig_sig_sputnik.emit()

def test_emit_passes(qtbot):
    uut = Sut()
    with qtbot.waitSignal(uut.sig_sig_sputnik, raising=True, timeout=500):
        uut.listen()

Practice Leveling Up with pytest-qt

To get more comfortable, I built a few practice tests.

Signals with Arguments

class Worker(qtc.QObject):
    done = qtc.pyqtSignal(int, str)

    def run(self):
        self.done.emit(42, "answer")

def test_signal_with_args(qtbot):
    w = Worker()
    with qtbot.waitSignal(w.done, raising=True) as blocker:
        w.run()
    assert blocker.args == [42, "answer"]

Asynchronous QTimer

class Ticker(qtc.QObject):
    tick = qtc.pyqtSignal()

    def start(self, delay_ms=50):
        qtc.QTimer.singleShot(delay_ms, self.tick.emit)

def test_async_timer(qtbot):
    t = Ticker()
    with qtbot.waitSignal(t.tick, raising=True, timeout=500):
        t.start(100)

Slots That Mutate State

class Counter(qtc.QObject):
    tick = qtc.pyqtSignal()
    valueChanged = qtc.pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self._value = 0
        self.tick.connect(self._on_tick)

    def _on_tick(self):
        self._value += 1
        self.valueChanged.emit(self._value)

def test_slot_and_state(qtbot):
    c = Counter()
    with qtbot.waitSignal(c.valueChanged, raising=True) as blocker:
        c.tick.emit()
    assert blocker.args == [1]

Negative Case with assertNotEmitted

def test_expected_timeout(qtbot):
    c = Counter()
    with qtbot.assertNotEmitted(c.valueChanged, timeout=100):
        pass

Final Thought

In the end, the fatal error wasn’t about pytest-qt at all it was about my mismatched Qt setup. Once I aligned my environment, the crashes disappeared and my tests behaved as expected. The real takeaway is simple: keep your toolchain consistent, and pytest-qt will let you test signals and slots with confidence.

Related blog posts