0

I want to test whether a Qt Class correctly emits a signal upon function call using QSignalSpy. I use MS Visual Studio and use the Microsoft::VisualStudio::CppUnitTestFramework. Executing unit tests generally works fine, but instantiating a QSignalSpy yields a quite generic Failed to set up the execution context to run the test error which prevents all unit tests from executing. Compilation works fine, though.

This is the (simplified) class I want to test:

// simcam.h
#include <QtCore>

class SimCam {
    Q_OBJECT

public:
    SimCam() {};
    ~SimCam();

public slots:
    void connect() {
        emit(s_connected());
    };

signals:
    void s_connected();
};

and the unit test:

#include "CppUnitTest.h"

#include "simcam.h"

#include <QSignalSpy>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace devices_cameras_simcam_UnitTests {
    TEST_CLASS(ConnectionTests) {
public:

    TEST_METHOD(connectTest) {
        auto simcam = SimCam();

        // Problematic line
        QSignalSpy spy(&simcam, &SimCam::s_connected);

        simcam.connect();
        spy.wait();

        Assert::AreEqual(1, (int)spy.count());
    }
    };
}

I am a bit lost, how to get it to work or debug it. I didn't find any hint how to use QSignalSpy in combination with Visual Studio.

  • Alter test runner `main` function so it will instantiate `QApplication` or `QCoreApplication` object or drop `spy.wait();`. Basically with invocation of `spy.wait();` you are [requesting run of event loop](https://doc.qt.io/qt-6/qsignalspy.html#wait) this means `QApplication` is needed. – Marek R Aug 11 '23 at 15:10
  • Offtopic: your test leaks memory. – Marek R Aug 11 '23 at 15:16
  • I will try that, but the problem already occurs simply by `QSignalSpy spy(simcam, &SimCam::s_connected);`. Calling `spy.wait();` is not necessary to trigger the issue. – Raimund Schlüßler Aug 11 '23 at 15:22
  • > Offtopic: your test leaks memory. Indeed. I tested so many variations of the code already, that I ended up with one leaking memory. Fixed it to not give a bad example. – Raimund Schlüßler Aug 11 '23 at 15:27
  • You are right, it has to do something with the event loop. The line `QTestEventLoop m_loop;` in `QSignalSpy` triggers this issue. Not calling this line, makes the test run. I yet have to figure out how to properly instantiate a `QApplication`, though. – Raimund Schlüßler Aug 14 '23 at 14:37
  • I couldn't figure out yet how to instantiate a `QApplication` in my test file. Having `QApplication` in the test requires it to be `moc`ed, but `moc` does not recognize the class with `TEST_CLASS(ConnectionTests)` and gives `No relevant classes found. No output generated.` leading to a linker error `unresolved externals`. – Raimund Schlüßler Aug 17 '23 at 08:23

1 Answers1

0

Basically with invocation of spy.wait(); (also other functionalities of QSignalSpy as you found out by testing) you are requesting run of event loop this means QApplication is needed.

In other test frameworks this means to provide own main function and instantiate there QApplication or QCoreApplication.

Problem is that CppUnitTestFramework uses dynamic libraries as tests suites which are loaded by some fixed tool, so you do now have access to main function.

The only choice is use initialization of module (whole test dll). I would try this (didn't test that, but should resolve issue):

#include "CppUnitTest.h"
#include <QCoreApplication>
#include <memory>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

static std::unique_ptr<QCoreApplication> myTestApplicationInstance;

TEST_MODULE_INITIALIZE(ModuleInitialize)
{
    static int argc = 1;
    static char testName[64] = "testSuiteName";
    static char *argv[2] = { testName, nullptr };

    myTestApplicationInstance = std::make_unique<QCoreApplication>(argc, argv);
}

TEST_MODULE_CLEANUP(ModuleCleanup)
{
    myTestApplicationInstance.reset();
}

Documentation of Microsoft.VisualStudio.TestTools.CppUnitTestFramework API

Test modules

TEST_MODULE_INITIALIZE(methodName)
{
    // module initialization code
}

Defines the method methodName that runs when a module is loaded. TEST_MODULE_INITIALIZE can only be defined once in a test module and must be declared at namespace scope.

TEST_MODULE_CLEANUP(methodName)

Defines the method methodName that runs when a module is unloaded. TEST_MODULE_CLEANUP can only be defined once in a test module and must be declared at namespace scope.

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • Thank you for the feedback. Unfortunately, this does not fix the problem, I still see `Failed to set up the execution context`. Is suspect the event loop is not acutally running, and one needs to call `myTestApplicationInstance->exec()`. But doing so in `TEST_MODULE_INITIALIZE` just hangs the tests as expected, because `exec()` does not return. – Raimund Schlüßler Aug 18 '23 at 11:47
  • Consider switch testing framework. Qt has some own solution `QTestLib`, but I would recommend use of `gtest` which is kind of industry standard. There are lots of plugins for VisualStudio to utilize gtest. – Marek R Aug 18 '23 at 11:53
  • Yes, I thought about this as well. Let's see whether I get this test to work with `gtest`. – Raimund Schlüßler Aug 18 '23 at 15:05
  • I switched to Google Test. It works well with Qt and Visual Studio. – Raimund Schlüßler Aug 22 '23 at 16:57