Q:

Qt Testing QMessageBoxes (or a general QDialog) with exec() loop

I want to test a custom QMessageBox that I created. In particular, my API will execute automatically the exec() loop of the QMessageBox, and I want to check the result when the user clicks a button or closes the widget.
The problem is that I am blocked in the exec() loop.
The only solution I found is creating a QTimer that will do what I want just before the loop is executed. References to this solution are this and this.
The tests are working correctly, but really there isn't a more native way in Qt to test the exec() loop of the QDialogs? I mean, there are ways to activate/simulate signals in QTest, there is nothing that can simulate the exec() loop?

A:

Why would you want to "simulate" the event loop? You want to run the real event loop! You just want to inject your code into it, and that's what timers are for (or events in general).
Qt Test has all sorts of convenience wrappers around the event loop, e.g. keyClick delivers the event and drains the event queue, etc., but you don't need to simulate anything. If you don't find something that you need, you can make your own.
The API you're using is of course broken: it pretends that an asynchronous action is synchronous. It should invert the control into a continuation passing style, i.e.:
// horrible
int Api::askForValue();
// better
Q_SIGNAL void askForValueResult(int);
void Api::askForValue(); // emits the signal
template <typename F>
void Api::askForValue(F &&fun) { // convenience wrapper
  auto *conn = new QMetaObject::Connection;
  *conn = connect(this, &Class::askForValueResult, 
    [fun = std::forward<F>(fun), 
     c = std::unique_ptr<QMetaObject::Connection>(conn)]
    (int val){
      fun(val);
      QObject::disconnect(*c);
    });
  askForValue();
}

The synchronous APIs like make a lot of application code have to face reentrancy, and this is not a hypothetical problem. Questions about it are not uncommon. Few people realize how bad of a problem it is.
But assuming that you are forced to stick with the horrible API: what you really want is to react to the message window being shown. You can do this by adding a global, application-wide event filter on the QEvent::WindowActivate. Simply install your event filter object on the application instance. This could be wrapped in some helper code:
/// Invokes setup, waits for a new window of a type T to be activated,
/// then invokes fun on it. Returns true when fun was invoked before the timeout elapsed.
template <typename T, typename F1, typename F2>
bool waitForWindowAnd(F1 &&setup, F2 &&fun, int timeout = -1) {
  QEventLoop loop;
  QWidgetList const exclude = QApplication::topLevelWidgets();
  struct Filter : QObject {
    QEventLoop &loop;
    F2 &fun;
    bool eventFilter(QObject *obj, QEvent *ev) override {
      if (ev.type() == QEvent::WindowActivate)
        if (auto *w = qobject_cast<T*>(obj))
          if (w->isWindow() && !exclude.contains(w)) {
            fun(w);
            loop.exit(0);
          }
      return false;
    }
    Filter(QEventLoop &loop, F2 &fun) : loop(loop), fun(fun) {}
  } filter{loop, fun};
  qApp->installEventFilter(&filter);

  QTimer::singleShot(0, std::forward<F1>(setup));
  if (timeout > -1)
    QTimer::singleShot(timeout, &loop, [&]{ loop.exit(1); });
  return loop.exec() == 0;
}

Further wrappers could be made that factor out common requirements:
/// Invokes setup, waits for new widget of type T to be shown,
/// then clicks the standard OK button on it. Returns true if the button
/// was clicked before the timeout.
template <typename T, typename F>
bool waitForStandardOK(F &&setup, int timeout = -1) {
  return waitForWindowAnd<T>(setup,
    [](QWidget *w){
        if (auto *box = w->findChild<QDialogButtonBox*>())
          if (auto *ok = box->standardButton(QDialogButtonBox::Ok))
            ok->click();
    }, timeout);
}

Then, supposed you wanted to test the blocking API QDialogInput::getInt:
waitForStandardOK<QDialog>([]{ QInputDialog::getInt(nullptr, "Hello", "Gimme int!"); }, 500);

You could also make a wrapper that builds the bound call without needing to use a lambda:
/// Binds and invokes the setup call, waits for new widget of type T to be shown,
/// then clicks the standard OK button on it. Returns true if the button
/// was clicked before the timeout.
template<typename T, class ...Args>
void waitForStandardOKCall(int timeout, Args&&... setup) {
  auto bound = std::bind(&invoke<Args&...>, std::forward<Args>(args)...);
  return waitForStandardOK<T>(bound, timeout);
}

And thus:
waitForStandardOKCall<QDialog>(500, &QInputDialog::getInt, nullptr, "Hello", "Gimme int!");

For more about std::bind and perfect forwarding, see this question.

