Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use QTimers in GoogleTest

I encounter cases during unit testing where I want the timeout of some QTimer to fire some slot in some QObject. It is not immediately obvious how to do this and some common pitfalls to this testing.

like image 835
shavera Avatar asked Nov 20 '15 15:11

shavera


1 Answers

This pattern is the one I've found works. I suspect it may be somewhat dependent on threading models, so I provide it with a minor note of YMMV.

Suppose you have some

class Foo : public QObject{
  ...
  public:
  QTimer* _timer;

  public slots:
  virtual void onTimeout();
  ...
}

for simplicity, let's pretend this is some private implementation class, which is why the timer is exposed, and the slot is virtual so we can mock it.

class MockFoo : public Foo{
public:
  MOCK_METHOD0(onTimeout, void());
}

First¸ generally when using QTimers and other threading model stuff from Qt, we must modify the 'main' function of google test:

int main(int argc, char **argv) {
    QCoreApplication app(argc, argv);

    ::testing::InitGoogleTest(&argc, argv);
    int ret = RUN_ALL_TESTS();

    QTimer exitTimer;
    QObject::connect(&exitTimer, &QTimer::timeout, &app, QCoreApplication::quit);
    exitTimer.start();
    app.exec();
    return ret;
}

Next, in the test suite:

TEST_F(Foo_Tests, onTimeout){
  MockFoo* foo{new MockFoo};
  //using Qt 5 convention, but do what you gotta do for signal spy in your setup
  QSignalSpy timeoutSpy(foo->_timer, &QTimer::timeout);
  QSignalSpy deleteSpy(foo, &QObject::destroyed);

  foo->_timer->setInterval(0);
  foo->_timer->setSingleShot(true);

  EXPECT_CALL(*foo, onTimeout());

  foo->_timer->start();

  EXPECT_TRUE(timeoutSpy.wait(100));
  foo->deleteLater();
  deleteSpy.wait(100);
}

Some notes about this that are very important:

  1. Even if you have a MockFoo already elsewhere in the testsuite, you need one that is created and destroyed within this one test. I suspect this has to do with QTimer and Qt's threading model/event loop.
  2. Suppose you do a similar test elsewhere, it could be a different class, different suite, different namespace, everything. If your EXPECT_CALL is not saturated here (or some other google test test), the next test using this pattern will fail, but it will complain about this test's expectations.
    • eg: Bar_test failed: MockFoo::onTimeout expected to be called once actual not called unsatisfied and active
  3. It is important to wait for the object to be destroyed before exiting. This allows the Qt event loop to process the pending actions on this object, namely the timeout signal that fires the slot.
  4. Even if the real program uses a non SingleShot timer, setting it that way here simplifies it so that the slot isn't called multiple times, upsetting the test.
like image 130
shavera Avatar answered Sep 25 '22 23:09

shavera