c++qtqt5qtestlib

Test method with Signals and Slots


I have the following two simple methods in a Qt Console Application:

void Webservice::getFile(PostElement el)
{
  parameter = ⪙
  QUrl url(PATH);
  QUrlQuery query;
  query.addQueryItem(el.getParam(), el.getValue());
  url.setQuery(query);
  request.setUrl(url);
  manager->get(request);
  connect(manager, SIGNAL(finished(QNetworkReply*)), this,
          SLOT(downloadCompleted(QNetworkReply*)));
}

void Webservice::downloadCompleted(QNetworkReply *reply)
{
  QByteArray b = reply->readAll();
  qDebug() << "Download completed!";
  QFile file("/tmp/" + parameter->getValue());
  file.open(QIODevice::WriteOnly);
  file.write(b);
  file.close();
  reply->deleteLater();
}

I created a QTest project to test getFile method.

#include <QtTest>
#include <QCoreApplication>

// add necessary includes here
#include "webservice.h"

class WebserviceTest : public QObject
{
  Q_OBJECT

public:
  WebserviceTest();
  ~WebserviceTest();

private:
  Webservice ws;

private slots:
  void initTestCase();
  void cleanupTestCase();
  void getFile();
};

WebserviceTest::WebserviceTest()
{

}

WebserviceTest::~WebserviceTest()
{

}

void WebserviceTest::initTestCase()
{

}

void WebserviceTest::cleanupTestCase()
{

}

void WebserviceTest::getFile()
{
  PostElement el{"file", "myPic.png"};
  ws.getFile(el); // it should wait for the slot to be executed
}

QTEST_MAIN(WebserviceTest)

#include "tst_webservicetest.moc"

As you can see, it has signals and slots. If I run that one on my original project, I see that downloadCompleted executes without issues (because it is using the Event Loop). But in the test project the slot is not called. I googled and found some examples using QSignalSpy, but at the moment I have not executed the slot successfully.

How to tell my test to "wait" for the slot to be completed?


Solution

  • That's exactly the purpose of QSignalSpy, it basically spins new QEventLoop that waits for signal to be executed. Your tests starts and asynchronous operation but it doesn't wait for it to finish so it will destruct objects while the operation is ongoing.

    Qt test also has some built-in timeouts that prevents tests to be stuck which are stopped when QSignalSpy is used. In your case you need to emit new signal from your class Webservice indicating that download is finished, e.g. (you can also pass arguments to the signal) if needed:

    class Webservice : public QObject {
         Q_OBJECT
         // Constructors etc.
         signals:
         void finished();
    }
    

    Then at the end of your void Webservice::downloadCompleted(QNetworkReply *reply):

    emit finished();
    

    and in your test something like this:

    PostElement el{"file", "myPic.png"};
    // Prepare spy
    QSignalSpy spy(&ws, SIGNAL(finished));
    ws.getFile(el);
    // Wait for result - by default this waits 5 seconds
    spy.wait();
    // Check if we had a timeout or actually signal was raised
    QCOMPARE(spy.count(), 1);