Related Posts:
Qt5/QTest: How to mock the clock speed (QTimer, etc.)?
Why they don't answer my question:
The only hint given in the first is, "Mock it using composition." There is no answer or code example. I also don't want to test if QTimer::start was called, but what I want is control over when the timer fires its signal so that I can test deterministically, and without waiting needlessly for any amount of time.
The second is talks about manipulating CPU time, which is not what I want to do at all. Nor is it what the OP of that post should be doing. It does talk about directly calling the slot the timer is hooked up to, but that isn't possible without changing production code and making the slot accessible to the outside.
So, the question once again is, how can we mock QTimer? ...Or at least, have the ability to test a class that uses it deterministically?
Here is a minimal example demonstrating what I need and why I need it. The entire thing is currently checked into https://github.com/ChristopherPisz/MockQTimer But I will leave the listing of the minimal example here as well:
cow.hpp
#ifndef MOCKQTIMER_COW_HPP
#define MOCKQTIMER_COW_HPP
#include <QObject>
#include <QTimer>
class Cow : public QObject
{
Q_OBJECT
public:
Cow();
~Cow() override = default;
signals:
void signalSaidMoo();
private slots:
void onMooDue();
private:
QTimer m_timerMoo;
};
#endif // MOCKQTIMER_COW_HPP
cow.cpp
#include "cow.hpp"
#include <iostream>
unsigned const defaultMooIntervalMilliseconds = 1000;
Cow::Cow()
:
QObject()
{
m_timerMoo.setInterval(defaultMooIntervalMilliseconds);
QObject::connect(&m_timerMoo, &QTimer::timeout, this, &Cow::onMooDue);
m_timerMoo.start();
}
void Cow::onMooDue()
{
std::cout << "The cow says Moo" << std::endl;
emit signalSaidMoo();
}
animal_tests.hpp
#ifndef MOCKQTIMER_ANIMAL_TESTS_HPP
#define MOCKQTIMER_ANIMAL_TESTS_HPP
#include "cow.hpp"
#include <QCoreApplication>
#include <QtTest>
class animal_tests : public QObject
{
Q_OBJECT
public:
animal_tests();
~animal_tests() override = default;
public slots:
/*
* @brief Called when the Cow's Moo signal is fired
*/
void onCowMooed();
private slots:
void init();
void cleanup();
void test_moo();
private:
Cow m_testCow;
unsigned m_countMoo;
};
#endif // MOCKQTIMER_ANIMAL_TESTS_HPP
animal_tests.cpp
#include "animal_tests.hpp"
animal_tests::animal_tests()
:
m_countMoo(0)
{}
void animal_tests::onCowMooed()
{
++m_countMoo;
}
void animal_tests::init()
{
m_countMoo = 0;
connect(&m_testCow, &Cow::signalSaidMoo, this, &animal_tests::onCowMooed);
}
void animal_tests::cleanup()
{
}
void animal_tests::test_moo()
{
// We want to test that the cow moos once a second
//
// This is terrible to actually wait a second using timers and non deterministic results! We had to add an extra 500 ms to make it pass
// and that is completely dependant on cpu speed and accuracy of timer. We also don't want to wait eons for the unit tests to complete.
// We need dependency injection and a mock timer, so we can control when the timer fires!
QVERIFY(QTest::qWaitFor([this] () { return m_countMoo >= 3; }, 3500));
}
QTEST_MAIN(animal_tests)
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(mockqtimer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
find_package(Qt5 5.14 REQUIRED COMPONENTS Core Test)
# Library
set(animals_MOC_HEADERS
cow.hpp
)
add_library(animals
${animals_MOC_HEADERS}
cow.cpp
)
target_link_libraries(animals PRIVATE
Qt5::Core
)
# Tests
set(animal_tests_MOC_HEADERS
${animals_MOC_HEADERS}
animal_tests.hpp
)
add_executable(animal_tests
${animal_tests_MOC_HEADERS}
animal_tests.cpp
)
add_test(NAME animal_tests COMMAND animal_tests)
target_link_libraries(animal_tests PRIVATE
Qt5::Test
Qt5::Core
animals
)