I have one solution for this problem which not requires any modification on production code. Main issue in this flow is that QMessageBox
is usually being called with it's own message loop (by exec()
method). That means direct solution like this will not work:
p_button->show();
QTest::qWaitForWindowActive(p_btn);
QTest::mouseClick(p_button, Qt::LeftButton);//connected to msgbox.exec()
//next row will be not executed since we are still in event loop of msgbox
QTest::keyEvent(QTest::Click, qApp->activeWindow(), Qt::Key_Return);
So you need to expect that QMessageBox before it appears. One way to do that is to create eventFilter which will look for activated QMessageBox
. In that way you can also validate QMessageBox
properties if that required.
Imagine you have such function:
QAbstractButton* CreateWidget(QWidget* ip_parent)
{
auto p_btn = new QPushButton(ip_parent);
QObject::connect(p_btn, &QAbstractButton::pressed, []() {
QMessageBox msgBox;
msgBox.setText("Are you sure?");
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.exec();
});
return p_btn;
}
It will create button which will execute QMessageBox
on press. To test it you can use helper like this one:
class MessageWatcher : public QObject {
public:
using tDialogChecker = std::function<void(QMessageBox*)>;
MessageWatcher(tDialogChecker i_checker, QObject* ip_parent = nullptr)
: QObject(ip_parent)
, m_checker(i_checker)
{
qApp->installEventFilter(this);
}
bool eventFilter(QObject* ip_obj, QEvent* ip_event) override
{
if (auto p_dlg = qobject_cast<QMessageBox*>(ip_obj)) {
if (ip_event->type() == QEvent::WindowActivate) {
m_checker(p_dlg);
return true;
}
}
return false;
}
private:
tDialogChecker m_checker;
};
It will call lambda when it will receive event of type QEvent::WindowActivate
for QMessageBox
. You can perform any checks related to QMessageBox
itself and you can execute closure of that QMessageBox
there. Consider this simple test:
class WidgetLibTest : public QObject {
Q_OBJECT
private slots:
void WidgetLibCheck();
};
void WidgetLibTest::WidgetLibCheck()
{
MessageWatcher watcher([](auto ip_msg_box)
{
auto closer = qScopeGuard([ip_msg_box] { QTest::keyEvent(QTest::Click, ip_msg_box, Qt::Key_Return); });
QCOMPARE(ip_msg_box->text(), "Are you sure?");
});
auto p_btn = std::unique_ptr<QAbstractButton>(CreateWidget(nullptr));
p_btn->show();
QTest::qWaitForWindowActive(p_btn.get());
QTest::mouseClick(p_btn.get(), Qt::LeftButton);//will execute QMessageBox
}
qScopeGuard
is needed because when QCOMPARE
is failed it will call return which will skip rest of the code. So, to close QMessageBox every time, even when check is not correct, you need to use it.
In similar way you can also test QProgressDialog
or any dialog which is going to pop-up in your entangled implementation.
Also it is possible to test cascade of widgets\dialogs, only in that case you need some kind of array of functors. But I would suggest to avoid that situation and restructure implementation so it will be possible to test each component separately.