Mocking for UI Controller Tests in PyQt4

What is Mocking?

Mocking is essentially taking a ‘heavy’ component — a UI element, database/file accessor — and swapping it out with a lighter component that only *pretends* to be the heavier component.  This is useful in cases where you don’t care about how the heavy component works, but need to test an object coupled tightly to it.  A unit test that does not require filesystem access or setting up a stack of GUI elements will run faster and possibly even give more relevant results.

UI Mocking

The main use for mocking is testing code coupled to the UI. This sometimes requires some design changes. For example, consider the following Qt4 window class:

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__(self)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.actionLoad.triggered.connect(self.fileLoad)
        self.ui.actionSave.triggered.connect(self.fileSave)
        self.ui.actionExit.triggered.connect(self.fileExit)
        # Other triggers...
    # Logic code...

This class cannot easily be tested — any QtGui object requires that the entire application base be created first. This incurs serious overhead for very little gain — we’re interested in testing the logic code, not the rendering.

The solution is to break our logic out into a separate object. Similarly to the UI itself, both the window and the controller need to hold references to the other so that signals can be routed properly.

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__(self)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.controller = MainWindowController(self)
        self.ui.actionLoad.triggered.connect(self.controller.fileLoad)
        self.ui.actionSave.triggered.connect(self.controller.fileSave)
        self.ui.actionExit.triggered.connect(self.controller.fileExit)
    #...

class MainWindowController(object):
    def __init__(self, window):
        self.window = window

    def fileLoad(self):
        #....

The window can route signals from the UI through the controller reference, while the controller can manipulate the UI through the window.ui reference. Furthermore, we can now test the MainWindowController class by passing in a mock of the MainWindow class, eliminating the overhead of setting up the GUI.

So what do these mocks look like? Here are some examples from my current project:

class UiMock(object):

    def __init__(self):
        pass

class WindowMock(object):

    def __init__(self):
        super(WindowMock, self).__init__()
        self.ui = UiMock()

class ButtonGroupMock(object):

    def __init__(self, buttons):
        super(ButtonGroupMock, self).__init__()
        self.buttons = buttons

    def checkedButton(self):
        for button in self.buttons:
            if button.isChecked():
                return button
        else:
            return 0

class RadioButtonMock(object):

    def __init__(self):
        super(RadioButtonMock, self).__init__()
        self.checked = False

    def isChecked(self):
        return self.checked

These, obviously, don’t emulate the objects they’re mocking very deeply. This is intentional — in keeping with the spirit of TDD, we only need to mock the interfaces we actually use in our code. The ButtonGroupMock doesn’t worry about exclusivity, for example, as I’ve not needed it in my tests yet, and UiMock is just an empty namespace. The dynamic nature of Python means that I need only create enough interface to fool the calling code, and no more. (Mocking in static languages, such as Java, is much more involved.)

As a result, our test setup looks something like this:

class TestMainWindowController(unittest.TestCase):
    def setUp(self):
        win_mock = WindowMock()
        
        win_mock.ui.radio_button = RadioButtonMock()
        win_mock.ui.check_box = CheckboxMock()
        # etc...

Hopefully this makes testing UI just a little simpler