3

For test purposes I'd like to create and display a widget. For now I only need the widget to render correctly but in the future I may want to extend this so I simulate various events to see how the widget behaves.

From various sources it would appear that the following should work:

QApplication app;

QPushButton button("Hello");
button.show();

// Might also be necessary:
QApplication::processEvents();

But for me the widget does not render correctly. A window is created to display the widget, however it is entirely black.

I can get the widget to render correctly by adding the following lines:

std::this_thread::sleep_for(std::chrono::milliseconds(10));
QApplication::processEvents();

With 10 milliseconds being about the smallest time necessary to get the widget to render correctly.

Does anyone know how to get this to work without the time delay, or know why the delay is necessary?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
user975326
  • 657
  • 1
  • 7
  • 22

3 Answers3

4

To test Qt GUI application you need at least QApplication instance and event loop being processed. The fastest way is just use QTEST_MAIN macro, this answer explains in a nice way what it does exactly. However, to have more flexibility (e.g. to use GTest, GMock) you can also simply create QAplication instance in your tests main (no need to call exec).

Then, to have the events processed, you should invoke QTest::qWait. This will process your events for the specified amount of time. It is a good practice to use qWaitFor which accepts a predicate - this way you avoid race conditions in your tests.

In the particular scenario, when you expect some signal to be emitted, you can also use similar functionality of QSignalSpy::wait.

Small example when we want to wait until some parameters are passed from one item to another:

QSignalSpy spy(&widget1, &Widget1::settingsChanged);
widget2->applySettings();
ASSERT_TRUE(spy.wait(5000));
// do some further tests based on the content of passed settings
pptaszni
  • 5,591
  • 5
  • 27
  • 43
1

Why don't you want to have the application run exec ? The process of displaying a widget is not "static". You don't "draw" the widget on the screen, but rather you have an application that listen for various events and receives draw events from the windowing manager. The application can only draw the widget when the windowing manager asks it to.

The reason your second code works is that you wait sufficiently long for the windowing manager to have sent the "draw" request in your conditions. This does not guarantee it will always work.

If you want to guarantee the display of the widget, you need to start a loop and wait until you have received at least one draw event, but even that isn't foolproof.

Vincent Fourmond
  • 3,038
  • 1
  • 22
  • 24
  • The reason is because exec blocks. Which is problematic in a test scenario where you ideally want to do something like: create, show, simulate a condition, check a condition and then quit. If you call exec then how do you manually create events such as those in QTest library (https://doc.qt.io/qt-5/qtest.html)? – user975326 Nov 20 '20 at 09:57
  • Then one can use a timer slot connected to QApplication::quit(). Then the application is killed after a specified time. @user975326 – Vincent Fourmond Nov 20 '20 at 10:21
0

As expertly described by Vincent Fourmond, widgets are not a one-off deal. The GUI is non-blocking and for this, it needs to run in an event loop.

The exec() method starts this event loop which you mimicked by polling. While it is possible to combine Qt's event loop with other event loops, I would recommend you a simpler solution:

Proceed your program within the event loop by calling a method when it starts. Find an excellent answer here on how to do this: https://stackoverflow.com/a/8877968/21974

As you mentioned unit testing, there is also a signal you can use for doing checks at the end of the lifecycle (before widgets are destroyed): QApplication::aboutToQuit. This will happen when the last window is closed (programmatically or by the user).

ypnos
  • 50,202
  • 14
  • 95
  • 141
  • How does one move the GUI to a different thread? I'm not actually sure this is possible for Qt applications. I see that the 2nd option would work, it's just a bit unfortunate like it requires a bit of boiler plate and doesn't quite fit with the linear fashion of how I'd like the unit test to read. But if there's no way to get my way to work this is a good alternative. Thank you. – user975326 Nov 20 '20 at 10:13
  • 1
    just to answer your question about threading. Qt has a "thread-affinity" of QObjects, and depending on the thread an object is associated with it, it will process signals in that thread. Qt has the important requisite that any GUI-related objects (e.g., QWidgets) operate in a single thread. I _believed_ that this is not necessarily the main thread, but current documentation says "GUI classes, notably QWidget [...] can only be used from the main thread. [...] QCoreApplication::exec() must also be called from that thread." So I retract that option. – ypnos Nov 20 '20 at 12:34