I am currently in the process of writing a testing suite using pytest for a PyQt class I have setup to house a desktop application. I am running into the error below when trying to run the tests. The error renders the tests unable to run at all (i.e. I don't get a success or fail from pytest, the tests simply won't run)
"TypeError: Cannot read properties of null (reading 'testsuites')"
The class I am testing (it renders successfully, so no issue there):
import logging
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon, QAction
from PyQt6.QtWidgets import QMainWindow, QStackedWidget, QToolButton, QToolBar, QWidget, QApplication
import sys
class MainWindow(QMainWindow):
"""
Main window class - this will house the main window
for the desktop application.
"""
def __init__(self) -> None:
# setup basic page components
super().__init__()
self.setWindowTitle("Demo App")
self.setWindowState(Qt.WindowState.WindowMaximized)
# call page setup here
self.setup_application()
@property
def logger(self) -> logging.Logger:
return logging.getLogger(f"{__name__}.{self.__class__.__name__}")
def setup_application(self) -> None:
self.logger.debug("Starting MainWindow setup")
# setup stack of widgets (different views)
self.setup_stack()
# setup toolbar for view navigation
self.setup_toolbar()
def setup_stack(self) -> None:
# all views to be added into application
self.first = QWidget()
self.second = QWidget()
self.third = QWidget()
self.fourth = QWidget()
# add widgets to stack
self.stack = QStackedWidget(self)
self.stack.addWidget(self.first)
self.stack.addWidget(self.second)
self.stack.addWidget(self.third)
self.stack.addWidget(self.fourth)
# set indexes of views in stack
self.FIRST_INDEX = 0
self.SECOND_INDEX = 1
self.THIRD_INDEX = 2
self.FOURTH_INDEX = 3
# finally, set central widget for application
self.setCentralWidget(self.stack)
def setup_toolbar(self) -> None:
# setup actions for views' toolbuttons
self.first_action = QAction("Page 1", self)
self.second_action = QAction("Page 2", self)
self.third_action = QAction("Page 3", self)
self.fourth_action = QAction("Page 4", self)
self.first_action.triggered.connect(
lambda: self.change_window(self.FIRST_INDEX)
)
self.second_action.triggered.connect(lambda: self.change_window(self.SECOND_INDEX))
self.third_action.triggered.connect(
lambda: self.change_window(self.THIRD_INDEX)
)
self.fourth_action.triggered.connect(
lambda: self.change_window(self.FOURTH_INDEX)
)
# setup buttons for toolbar
self.first_button = QToolButton()
self.first_button.setDefaultAction(self.first_action)
self.second_button = QToolButton()
self.second_button.setDefaultAction(self.second_action)
self.third_button = QToolButton()
self.third_button.setDefaultAction(self.third_action)
self.fourth_button = QToolButton()
self.fourth_button.setDefaultAction(self.fourth_action)
# finally, setup toolbar for top of screen
self.toolbar = QToolBar()
self.toolbar.addWidget(self.first_button)
self.toolbar.addWidget(self.second_button)
self.toolbar.addWidget(self.third_button)
self.toolbar.addWidget(self.fourth_button)
self.toolbar.setMovable(False)
self.addToolBar(self.toolbar)
def change_window(self, index: int) -> None:
# TODO: add bold text/button highlighting to make it clear from
# the toolbar which view you are on
# set application view to desired view, just doing this for now
self.stack.setCurrentIndex(index)
Code used to invoke the application normally (non-testing environment); housed in a separate file from the MainWindow class:
def main() -> None:
# TODO: Figure way to get package name dynamically
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
# invoke application
if __name__ == "__main__":
main()
My test code as it currently stands (housed in a file called test_main_window.py):
# add imports for any files to be tested in windows
# package imports
import pytest
from pytest_mock import MockerFixture
from pytestqt.plugin import QtBot
import sys
# file imports
from windows.main_window import MainWindow
# tests should start with `test_` and be followed by function name
# example: test_my_fancy_func()
@pytest.fixture
def mw() -> MainWindow:
return MainWindow()
class TestMainWindow:
def test_dummy(self, mw: MainWindow) -> None:
pass
Using the pytest integration/plugin for VS Code gives me the error mentioned above, and I have not been able to figure out why. Pytest seems to be getting upset with my fixture for some reason but I'm not sure why as the MainWindow class performs just as expected in a normal setting. I have tried defining a QApplication for the test file, as well as using the pytest-qt plugin's QtBot. Neither have been able to resolve my issue, although there is a good chance I might have misused either of those in my attempted fixes. Any help would be much appreciated!
CodePudding user response:
I believe the solution to this is related to the fact that you're trying to instantiate the MainWindow widget without first constructing a QApplication.
pytest-qt lets you avoid having to construct a QApplication for each unit test with qtbot
.
In your TestMainWindow
class, you should simply add qtbot
as an argument to your test function.
class TestMainWindow:
def test_dummy(self, qtbot, mw: MainWindow) -> None:
pass
Adding the qtbot
fixed the issue for me locally.