5

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.)

Seungho Lee
  • 1,068
  • 4
  • 16
  • 42
CodingCat
  • 4,999
  • 10
  • 37
  • 59

1 Answers1

5

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.

Ja8zyjits
  • 1,433
  • 16
  • 31
  • Thanks for the pointer to mock and @patch, this was what I was looking for! (And yes, I know the popup in this case makes no sense. My actual popup asks for user choices and is far more complex, both the popup and the circumstances where it is called. I just wanted to create a very quick MVE for this.) – CodingCat Mar 18 '19 at 14:54