In a subclass of QFileDialog
there is a method, on_dir_entered
, which should be called when the QFileDialog
's signal directoryEntered
fires, thus:
self.directoryEntered.connect(self.on_dir_entered)
The problem is that a signal takes a non-negligible time to take effect. Originally I was inspired by this answer by eyllanesc, a notable PyQt5 expert. With an isolated test this sort of technique using QTimer.singleShot()
can work, although I had vague doubts about it from the beginning. And indeed, it turns out on my machine that I get "test leakage" with this sort of thing, particularly when there is more than one such test method: strange errors apparently occurring outside the tests themselves:
TEARDOWN ERROR: Exceptions caught in Qt event loop:
... so I went back to the pytest-qt docs and found that there are various methods available beginning wait...
seemingly to cater to the problem of signals or other events taking a non-negligible time to have effect. So I made a few tries to test signal directoryEntered
:
def test_directoryEntered_triggers_on_dir_entered(request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = save_project_dialog_class.SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
qtbot.waitSignal(fd.directoryEntered, timeout=1000)
mock_entered.assert_called_once()
and then
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
qtbot.waitSignal(fd.directoryEntered, timeout=1000)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
mock_entered.assert_called_once()
and then
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
qtbot.wait(1000)
mock_entered.assert_called_once()
All fail: the method is called 0 times.
I also tried:
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called)
... this times out (default 5000 ms). I have double-checked and triple-checked that the code setting up connect
on this signal is executed.
Later
By putting a print
statement in the called slot (on_dir_entered
) I now see what the problem is: despite the with mock.patch...
line, the method is not being mocked!
At my low level of knowledge of mocking etc. I am tending to assume that this is because of the fact of using a signal with emit()
to trigger the event: I can't think of another explanation.
NB this signal is fired "naturally" by one or two events in a QFileDialog
(such as clicking the "go to parent directory" QToolButton
). Maybe you have to do it that way... So I tried this:
def test_directoryEntered_triggers_on_dir_entered(request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = save_project_dialog_class.SaveProjectDialog(project)
to_parent_button = fd.findChild(QtWidgets.QToolButton, 'toParentButton')
print(f'qtbot {qtbot} type(qtbot) {type(qtbot)}')
with mock.patch.object(SaveProjectDialog, 'on_dir_entered') as mock_entered:
qtbot.mouseClick(to_parent_button, QtCore.Qt.LeftButton)
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
Time out. 0 calls. Again I was able to ascertain that the real method is being called, and the patch is not working.
What am I doing wrong and is there a way to test this with something from pytest-qt?