Test modal dialog with Qt Test - unit-testing

I am trying to write a unit test for a GUI application using the QTestLib. The problem is that one of the slots creates a modal dialog using exec() and I found no possibility to interact with the dialog.
The slots which creates the dialog is connected to a QAction. So the first problem is that the test blocks when I trigger the QAction in the test since this results in the call to exec(). Therefore, I tried creating a QThread that performs the interaction. However, this did not help.
Things I already tried (all performed from within the "interaction helper" thread):
Send key clicks using QTest::keyClicks()
Results in error message "QCoreApplication::sendEvent(): Cannot send events to objects owned by a different thread"
Post QKeyEvents using QCoreApplication::postEvent()
Doesn't work, i.e. nothing happens. I guess because the events end up in the event loop of the thread that owns the dialog, which will not be reached until the dialog is closed and exec() returns. See Edit below
Invoking Slots on the dialog using QMetaObject::invokeMethod()
Doesn't work, i.e. nothing happens. I guess for the same reason as postEvent() doesn't work. See Edit below
So the question is: Is there any way to interact programmatically with a modal dialog that was opened using the exec() method?
Edit: Actually, method 3 is working. The problem was a different one:
I passed the arguments to invokeMethod() to the "interaction helper" thread and for some reason, accessing the arguments did not work from that thread (I got no SEG errors but they were simply empty).
I guess that method 2 is also working and I simply had the same problem as with method 3 but I didn't test that.

The solution I use in command line applications which use Qt libraries meant for GUIs is the singleShot, as this answer alludes. In those cases it looks like this:
QCoreApplication app(argc, argv);
// ...
QTimer::singleShot(0, &app, SLOT(quit()));
return app.exec();
So in your case I imagine it would look something like this:
QDialog * p_modalDialog = getThePointer(); // you will have to replace this with
// a real way of getting the pointer
QTimer::singleShot(0, p_modalDialog, SLOT(accept()));
p_modalDialog->exec(); // called somewhere else in your case
// but it will be automatically accepted.

You can keep the interaction in the same thread by delaying its execution until the dialog event loop starts.
For example just before the exec() call, you use either QTimer::singleShot with 0 as interval or QMetaObject::invokeMethod with connection type Qt::QueuedConnection to invoke the slot that needs to be executed while the dialog is shown.

You can also post the events before calling exec().
As soon as the dialog has been constructed after the exec() call, the events will be executed.
For example to test an Esc key press (means reject/close the dialog):
// create a dialog
QDialog d = ...
//post an Escape key press and release event
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyPress , Qt::Key_Escape, Qt::NoModifier) );
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Escape, Qt::NoModifier) );
// execute and check result
int ret = d.exec();
QCOMPARE(ret, static_cast<int>(QDialog::Rejected));

related question's answer has some extra details about flushing the event queue during a test:
Qt event loop and unit testing?

Related

Qt DLL in MFC app - How to make QDialog *really* modal?

At the moment I am developing a Windows DLL with Qt 5.9.2 (MSVC 2015 compiler), which should be loaded by an existing, commercial MFC application. Upon request of this application a modal instance of QDialog should be displayed.
Since QApplication::exec() would block the entire application, I "simulate" the event loop using the following code:
void Core::createQApplicationInstance()
{
// Check, if there's already a 'QApplication' instance running (unlikely)
if (!QApplication::instance())
{
int argc = 1;
// Create a new 'QApplication' instance
m_app = new QApplication(argc, nullptr);
// Create a 'QTimer' instance to call 'processEvents' periodically:
// We can't run 'm_app->exec()' because it would block everything,
// so we'll use this 'hacky-whacky' method here
m_timer = new QTimer;
// Connect the timer's timeout to the app's 'processEvents' via a lambda
QObject::connect(
m_timer,
&QTimer::timeout,
[&]()
{
m_app->processEvents();
}
);
// Start the timer with the fixed 'message' interval
m_timer->start(kMsgInterval);
}
}
If my DLL should now display a modal dialog, it works (partially) with the following code:
{...}
case eUserIGeneral:
{
qDebug() << "<< eUserIGeneral";
QDialog w;
w.setModal(true);
w.exec();
// --> Code here is executed AFTER the dialog has been closed
}
break;
//-------------------------------------------------------------------
{...}
The code after w.exec() will actually be executed AFTER the dialog was closed (as intended). However, the main application still remains responsive and is not affected by the modality of my dialog, which Is not as I expected it to behave.
How can I make sure that inputs in the main application are locked when calling a modal DLL dialog?
Although I don't have a real answer to your question, there too much stuff wrong with your code to be properly explained in a comment. Therefore I am writing this as an answer.
QApplication::exec(): I strongly recommend revising the decision against it. If you want the window to be modal, why would it be wrong to "block the entire application" until it is closed? Note that you will not block the Qt part of the application, only the one that calls exec.
QTimer: A timer can only run inside an event loop. So the m_app->processEvents() either never executes, or you already have an event loop running. Either way, there is no use for the timer.
w.setModal(): If what this does is not correct for you, check out setWindowModality().
w.exec(): Ignores the value of setModal(). Read the documentation of setModal() and exec() to find out more.
w.exec(): Executes an event loop. If this somewhat does what you want, QApplication::exec() should work too. Just make sure to exit the main event loop when done.
w.exec(): Is not executed after the dialog was closed. It is executes while the dialog is shown. It blocks until the dialog is closed. So you will start executing it, show the dialog, close the dialog, and then return from it. Read the documentation of exec() to find out more.

Will QMessageBox block the running of the whole main thread in Qt?

I am new to Qt
My situation is: For some reason, I have to emit a heartbeat signal from the main thread, at the same time I would like to create a QMessageBox window using:
reply = QMessageBox::question(this, tr("Sure want to quit?"), tr("Sure quit?"), QMessageBox::Yes|QMessageBox::No);
I just want this message box to block user's input from other windows, but I do not want to block the heartbeat signal.
How should I do this? Or is this done by default in Qt?
QMessageBox::question internally executes the event loop. So everything continues running. You don't need to be worried about this.
However you can get strange effects using such functions. E.g. if your heartbeat could open a dialog that dialog would open too even if another dialog is open already.
Also imagine you have a TCP/IP stack running. Everything that this stack can do will continue to happen... whereever QMessageBox::question() is currently executed... like in the middle of some function.
This is why we have a policy in our company that forbids to use QMessageBox::question() (and similar) and to call exec() on dialogs in our applications. We are creating modal dialogs on the heap and use their signals instead.

Qt QProgressBar indeterminate

My application needs to do some operations which might take a second but might also take 10 minutes. For this purpose I need to show a QProgressDialog with indeterminate QProgressBar during the operation.
QProgressDialog dlg( this );
dlg.setBar( new QProgressBar() );
dlg->setMaximum( 0 );
dlg->setMinimum( 0 );
dlg.setModal( true );
dlg.show();
//operation ...
dlg.close();
During my operation the dialog shows up, is transparent, has no progressbar and after the operation it closes.
Does anyone know what I can do to show a modal dialog which prevents the user from interacting with the application and which shows the user an indeterminate progressbar?
I'd suggest you don't create your own QProgressBar. The QProgressDialog has its own bar inside, and propagates all the methods from dialog to bar. What's more, to make your window modal use exec not show, or setModal(true) first. To close it, connect some signal (of a finished work) to the cancel() slot (or user will have to click Cancel button).
QProgressDialog dialog;
dialog.setRange(0,0);
dialog.exec();
I think one thing that you might need is that you call QApplication::processEvents() while looping over your entries.
Quoting from QCoreApplication docs:
You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
and I think in this particular case the application will not update the appearance of your QProgressDialog while it is busy performing the long operation unless you call QApplication::processEvents().
If you have fixed range and you call setValue() as your loop progresses (quoting from the QProgressDialog docs):
If the progress dialog is modal (see QProgressDialog::QProgressDialog()), setValue() calls QApplication::processEvents()
(I am omitting here the warning which cautions that this can cause issues with re-entrancy).`
Note that when I tried your code it created a dialog like what you would expect if you only remove the line
dlg.setBar( new QProgressBar() );
As was said in another answer, QProgressDialog has its own QProgressBar so unless you have special requirements, this should do what you need.
The documentation of the QProgressDialogs method setBar states, that "... the progress dialog takes ownership of the progress bar ...", however I found a forum post about the exact same problem you have.
In that post the last answer of the user ShaChris23 states, that this problem can be solved by passing the pointer to your QProgressDialog in the constructor of the QProgressBar, to set it as parent:
dlg.setBar( new QProgressBar(&dlg) );

QTest asynchronous signal interception

I am investigating QTest for GUI testing. It appears that there is no mechanism in QTest to asynchronously test a signal callback. Am I misunderstanding how to use QTest, or misunderstanding the intended functionality provided by QTest?
For example, I am attempting to test a signal which launches a modal QMessageBox popup in response to clicking a QPushButton. I want to test events and state between the clicking of the button and clicking 'OK' on the QMessageBox. I have tried using QSignalSpy.wait(), QTestEventList.simulate(), QTest::mouseClick(), QTest::mouseEvent(), and QTRY_VERIFY(), all of which, when called from the testing code, do not return until after clicking 'OK' on the QMessageBox popup. Therefore, to test anything before all event handlers return, I would have to do something like install an event filter on the object under test, and write my own asynchronous testing code.
This is all the same code we would have to write if we weren't using a test library and we can do all of this without using QTest. For example, we can manually get a handle to the object under test, connect signals for testing, invoke an event to trigger the event handler or invoke the signal manually, and also write the installed test handlers that interact with the test environment before returning execution to the point at which the event was launched. What does QTest gain us here?
(Also posted on Qt forums)
Working with synchronous events using qtestlib is a bit tricky. If you look into the sources of qtestlib, you can find that event simulation are pretty straightforward. So, qtestlib doesn't provide any methods to handle synchronous events. Anyway, it's possible to handle Qt modal windows which are spawned by your app.
Main note to this question is that GUI objects can't be accessed from others threads except GUI thread. And moreover GUI could be created only in thread where QApplication was created. So some tricks like spawning a new thread to press OK button in QMessageBox will be unsuccessful with error like this object can not be accessed from other thread somewhere in QWidget sources.
To avoid this case async event could be triggered with Qt slots mechanism. First of all you should define a Helper object with some slot, for example
class Helper {
Helper() {}
public slots:
doSmth();
}
Further you should create an instance of this object in the testcase
void BlahblahTest::testCase1() {
Helper h;
...
And before you invoke some synchronous event with, for example, QTest::mouseClick, set a delayed action with
QTimer::singleShot(delay, &h, SLOT(doSmth));
Depends on your needs the implementation of doSmth could be like that
void Helper::doSmth() {
QList<QWidget *> tlw = qApp()->topLevelWidgets();
foreach (QWidget *w, tlw) {
if (...) { // w is a messagebox
Qt::keyClick(w, Qt::Key_Enter);
}
}
}

How to return from exec method programmatically in static singleton class

I am developing Qt application (Qt version 4.7.3) on SBC6000x board.
I have a MessageBox class derived from QDialog. I have made this class singleton.
Whenever a messagebox is to be show I am using .exec method to show it.
There are few places where I need to show messageboxes one after another.
So, to show new messagebox, I have to close previous one and show new one.
e.g. When Messagebox is open and at same time I receive an error from background I have to close the messagebox which is currently shown and show the one with error.
To closes previous dialog I have exposed CloseDlg method from messagebox class and trying to close it.
Inside this CloseDlg I am emitting finished signal.
void CMsgBox::CloseDlg()
{
if (NULL != CMsgBox::m_msgBox)
{
if(CMsgBox::m_msgBox->isVisible())
{
emit CMsgBox::m_msgBox->finished(0);
//QApplication::processEvents();
}
}
}
and calling it as
CMsgBox::CloseDlg();
My show method is :-
int CMsgBox::showMsgBox(Icon icon, const QString &textMsg, const QString &okBtnText)
{
if (CMsgBox::m_msgBox == NULL)
{
CMsgBox::m_msgBox = new CMsgBox();
}
CMsgBox::m_msgBox->setText(textMsg);
CMsgBox::m_msgBox->setIcon(icon);
CMsgBox::m_msgBox->setOkBtnText(okBtnText);
CMsgBox::m_msgBox->exec();
return CMsgBox::m_msgBox->m_btnPressed; //return, unblock the call
}
Again when I call showMsgBox,it is showing me following warning.
QDialog::exec: Recursive call detected
Problem is, it doesn’t return from previous exec call (unless we return, as commented above //).
I tried same with close(), accept(), reject() methods instead of finished() event but nothing worked.
What is the way to return from previous exe call and achieve above scenario? Any help is welcome.
What you have here looks like a race condition. A modal QDialog runs its own event loop, so your application behaves like a multithreaded application and you need to take care of concurrency and race conditions.
When you receive a second in your main event loop, you call CMsgBox::CloseDlg() and CMsgBox::showMsgBox() in quick succession. However, CloseDlg() tells the dialog's event loop to return, but CloseDlg() actually returns before the dialog's event loop is done cleaning up, and showMsgBox() attempts to call exec() on a dialog which hasn't finished exiting yet.
What you need to do is, when you call CMsgBox::CloseDlg(), connect to the finished(int) signal, and only when you receive the finished(int) can you safely exec() the dialog again.
NOTE: When connecting to the finished(int) signal, make sure to use a Qt::QueuedConnection instead of a Qt::DirectConnection which is the default.
So, you need modeless dialog box. As explained in their documentation :
Modeless dialogs are displayed using show(), which returns control to the caller immediately.
Therefore, instead of showing the box with exec(), show it with show().
Alternative to show() method suggested in another answer is, use QDialog::open(). It will return, but will still give you modal dialog, so the rest of the GUI will be disabled until you close it.