pythonqtpytestpysidepyside6

pytest isn't catching exception found in Qt Event Loop?


I am writing an integration test for saving data disk as JSON. The desired behavior: when a record already exists in the JSON file, the application should raise an exception before overwriting existing JSON records. Since the exception being raised is desired behavior, the integration test should catch this exception, and pass.

Although the error is raised within the Qt Event Loop, it appears that pytest does not catch it. What can I do to ensure that pytest catches the ValueError I raise?

The Error

Pytest did not catch the exception...

        records = json.loads(database.read_text())["records"].keys()
        if record_name in records:
>           with pytest.raises(ValueError) as execinfo:
E           Failed: DID NOT RAISE <class 'ValueError'>

... but it does appear in stderr -- caught by the qt event loop.

---------------------------- Captured stderr call -----------------------------
Exceptions caught in Qt event loop:
________________________________________________________________________________
Traceback (most recent call last):
  File "<string>", line 3, in save
  File "C:\Users\me\AppData\Local\Programs\Python\Python312\Lib\unittest\mock.py", line 1137, in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\me\AppData\Local\Programs\Python\Python312\Lib\unittest\mock.py", line 1141, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\me\AppData\Local\Programs\Python\Python312\Lib\unittest\mock.py", line 1202, in _execute_mock_call
    result = effect(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\me\Desktop\ProjectDir\.venv\Lib\site-packages\pytest_mock\plugin.py", line 177, in wrapper
    r = method(*args, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\me\Desktop\ProjectDir\record\home.py", line 132, in save
    record.toJSON()
  File "c:\Users\me\Desktop\ProjectDir\record\record.py", line 53, in toJSON
    raise ValueError(f"record '{self.name}' already exists in database")
ValueError: record 'existing_record_name' already exists in database

The Code

The Integration Test: this snippet is part of a larger integration test. I expect this mouse click signal to trigger a slot method that raises a ValueError.


## code within the test_save_new_record() integration test
    records = json.loads(database.read_text())["records"].keys()
    if record_name in records:
        with pytest.raises(ValueError) as execinfo:
            qtbot.mouseClick(home_widget.save_button, Qt.MouseButton.LeftButton) # << exception SHOULD be raised
    else:
        # rest of test...

The slot to which the signal is connected:

class HomeWidget(QWidget, Ui_widget):
    def __init__(self):
        self.save_button.clicked.connect(self.save) # <<< 

The body of the slot method:

def save(self):
    selected_record = self.record_list.currentItem()
    if selected_record:
        self.record_builder.name_edited = selected_record.text()
    record = self.record_builder.build()
    record.toJSON() # <<< this raises the exception

Finally, we come to the method where data is saved to disk, and the exception is raised:

def toJSON(self):
    # if the record already exists, raise ValueError exception
    db = json.loads(self.record_db.read_text(encoding="UTF-8"))
    if self.name in db["records"].keys():
        raise ValueError(f"record '{self.name}' already exists in database") # <<< 
    else:
        # rest of method ...

Solution

  • Instead of using pytest.raises(ValueError), use pytestqt to intercept exceptions in Qt's event loop when testing:

    with qtbot.capture_exceptions() as exceptions:
        qtbot.mouseClick(
            button, QtCore.MouseButton.LeftButton
        )
    

    See the documentation here.