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.