Search code examples
pythonpyqt5python-unittestgui-testing

PyQt unit test that QDialog is created


I have a parent widget that in some cases calls a custom QDialog to get user input. How do I write a unit test to ensure the dialog is called, and that it will handle correct input correctly?

Here's a mini example:

from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget, QLabel, QApplication
from PyQt5.Qt import pyqtSignal, QPushButton, pyqtSlot, QLineEdit
import sys


class PopupDialog(QDialog):
    result = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        lbl = QLabel("That's not a full number! Try again?")
        layout.addWidget(lbl)
        self.field = QLineEdit(self)
        layout.addWidget(self.field)

        self.btn = QPushButton("Ok")
        self.btn.clicked.connect(self.on_clicked)
        layout.addWidget(self.btn)

    def on_clicked(self):
        value = self.field.text().strip()
        self.result.emit(value)
        self.close()


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.init_UI()

    def init_UI(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        lbl = QLabel("Please provide a full number")
        layout.addWidget(lbl)

        self.counter_fld = QLineEdit(self)
        self.counter_fld.setText("1")
        layout.addWidget(self.counter_fld)

        self.btn = QPushButton("start")
        layout.addWidget(self.btn)
        self.btn.clicked.connect(self.on_clicked)

        self.field = QLabel()
        layout.addWidget(self.field)
        self.show()

    @pyqtSlot()
    def on_clicked(self):
        txt = self.counter_fld.text()
        self.dialog = None
        try:
            result = int(txt) * 100
            self.field.setText(str(result))
        except ValueError:
            self.dialog = PopupDialog()
            self.dialog.result.connect(self.catch_dialog_output)
            self.dialog.exec_()

    @pyqtSlot(str)
    def catch_dialog_output(self, value):
        self.counter_fld.setText(value)
        self.on_clicked()


def main():
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

So in this case, I'd want to write a unit test that inserts different values into self.field and then tests that it works without PopupDialog for integers but that the PopupDialog is called when inserting a string.

(I know I could just test the functionality without the dialog, and that for this problem, the QDialog is not actually needed. I just tried to keep the example simple. Baseline is: I can get the unit test through the steps until the popup dialog is created, but how can I then test that it is indeed created, and then interact with it to test the result?)

#!/usr/bin/env Python3

import unittest
import temp2

class Test1(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        self.w = temp2.Example()

    def testHappy(self):
        for i in [0,1,5]:
            self.w.counter_fld.setText(str(i))
            self.w.btn.click()
            value = self.w.field.text()
            self.assertEqual(value, str(i * 100))

    def testSad(self):
        for i in ["A", "foo"]:
            self.w.counter_fld.setText(str(i))
            self.w.btn.click()
            # now what?


if __name__ == "__main__":
    unittest.main()   

(I'm using PyQt5 in Python3.6 on Windows.)


Solution

  • Well there are few ways to check if the QDialog is created,

    1) patch the PopupDialog and verify if it was called.

    from unittest.mock import patch
    
    @patch("temp2.PopupDialog")
    def testPopupDialog(self, mock_dialog):
            self.w.counter_fld.setText(str("A"))
            self.w.btn.click()
            mock_dialog.assert_called_once()
    

    2) To interact with the PopupDialog you may have to do a bit more.

    def testPopupDialogInteraction(self):
            self.w.counter_fld.setText(str("A"))
            self.w.btn.click()
            if hasattr(self.w.dialog, "field"):
                self.w.dialog.field.setText(str(1))
                self.w.dialog.btn.click()
                value = self.w.field.text()
                self.assertEqual(value, str(1 * 100))
            raise Exception("dialog not created")
    

    On a different note, there is a better way to verify the user input i.e QRegExpValidator. Check this SO answer

    In the present method, it will continue creating a Popup everytime a user inputs improper value and would create a poor user-experience(UX). Even websites use validators instead of Popups.