This sample demonstrates how to use DrMock to mock an interface
IFoo that inherits from QObject and uses the Q_OBJECT macro.
samples/qt
│ CMakeLists.txt
│ Makefile
│
└───src
│ │ CMakeLists.txt
│ │ IFoo.h
│
└───tests
│ CMakeLists.txt
│ FooTest.cpp
This project requires an installation of DrMock in build/install/
or a path container in the CMAKE_PREFIX_PATH variable. If your
installation of DrMock is located elsewhere, you must change the
value of CMAKE_PREFIX_PATH.
Furthermore, unless you wish to call cmake directly, it is required that
you define the environment variable DRMOCK_QT_PATH to contain the path
to an installation of Qt5.
Let's take a look at the changes make to CMakeLists.txt.
# samples/qt/CMakeLists.txt
# ...
find_package(DrMock)
drmock_enable_qt()
find_package(Qt5 COMPONENTS Core REQUIRED)
# ...To enable Qt for DrMock, the drmock_enable_qt() macro must be called
after finding the DrMock package. This macro enables CMAKE_AUTOMOC
and a CMake policy that allows mocking generated source code. Of course,
one must also find the Qt5 package.
When calling DrMockModule, the required Qt5 modules must now
bespecified using the keyword QTMODULES, as follows. DrMock will
automatically link TARGET to the required libraries.
DrMockModule(
TARGET DrMockSampleQtMocked
QTMODULES
Qt5::Widget
# ...
HEADERS
IFoo.h
)Furthermore, in order to use DrMockModule with Qt, the $DRMOCK_QT_PATH
environment variable must be set (see Building DrMock for
details).
The drmock_test call requires no changes for the use of Qt.
A class that is derived from QObject3 and holds the Q_OBJECT macro in
the private section of its body may be mocked if it satisfies the
following rules:
-
The only declarations in the interface shall be public methods, public slots, signals and type alias (template) declarations.
-
All methods shall be declared pure virtual, with the exception of the signals.
-
The interface shall not contain any conversion functions.
-
If an operator is defined in the interface, the interface shall not have a method called
operator[SYMBOL], where[SYMBOL]is determined by the operator's symbol according to the table below. -
None of the interface's method shall be a volatile qualified method or a method with volatile qualified parameters.
3: Note that condition is satisfied if, for instance, the class is
derived from QWidget, which in turn is derived from QObject.
The interface IFoo inhertis from QWidget, has a pure virtual slot
and a signal.
class IFoo : public QWidget
{
Q_OBJECT
public:
virtual ~IFoo() = default;
public slots:
virtual void theSlot(const std::string&) = 0;
signals:
void theSignal(const std::string&);
};To demonstrate the mock object, two instances of FooMock are made
and theSignal of foo connected to the slot of bar.
QObject::connect(
foo.get(), &IFoo::theSignal,
bar.get(), &IFoo::theSlot
);Before that, bar is instructed to expect a call of theSlot with
the argument "foo". After foo emits theSignal, this may be
verified by bar:
emit foo->theSignal("foo");
DRMOCK_ASSERT(bar->mock.verify());The emit foo->theSignal("foo"); looks a bit off. That's how we had to
do it prior to DrMock<0.3.0. In DrMock>=0.3.0 the emits
configuration call is added to Behavior and StateBehavior. It can be
used to instruct a mock object to emit a signal:
// emit foo->theSignal("foo"); // Old, boring and explicit.
foo->mock.theSlot().push()
.emits<const std::string&>(&IFoo::theSignal, "foo")
.expects("bar")
.times(1); // Optional.
foo->theSlot("bar"); // `emit foo->theSignal("foo")` happens here!Note. As in the case above, you have to be explicit when passing
const T& instead of relying on template deduction. Only calling
emits(&IFoo::theSignal, "foo") will result in a
deduced conflicting types for parameter 'SigArgs' error.
Do make. If everything checks out, this should return
Start 1: FooTest
1/1 Test #1: FooTest .......................... Passed 0.02 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.02 sec
Some QObjects, such as QEventLoop require a QApplication to run in
the main thread of the program to function correctly. To run a
QApplication in the main thread during a DRTEST_TEST, define the
DRTEST_USE_QT macro before including DrMock/Test.h.
Let's take a look at the previous example, but this time, connect the
two FooMock instances via Qt::QueuedConnection:
DRTEST_TEST(useQt)
{
auto foo = std::make_shared<FooMock>();
auto bar = std::make_shared<FooMock>();
QObject::connect(
foo.get(), &IFoo::theSignal,
bar.get(), &IFoo::theSlot,
Qt::QueuedConnection // Connect via event loop.
);
bar->mock.theSlot().push().times(1);
emit foo->theSignal();
// ...
}To process the emitted signal, we must use an event loop, something like this:
DRTEST_TEST(useQt)
{
// ...
QCoreApplication::processEvents();
DRTEST_ASSERT(bar->mock.verify());
}If you run this application (don't forget to include <QCoreApplication>),
you will receive the following error:
Start 1: FooTest
1/1 Test #1: FooTest ..........................***Failed 0.02 sec
TEST signalsAndSlots
*FAIL signalsAndSlots (45): bar->mock.verify()
****************
1 FAILED
0% tests passed, 1 tests failed out of 1
Total Test time (real) = 0.02 sec
The following tests FAILED:
1 - FooTest (Failed)
Errors while running CTest
make: *** [default] Error 8
Due to the missing QCoreApplication in the main thread,
processEvents() fails to process theSignal, so theSlot never gets
called the expected number of times and bar->mock.verify() rightfully
returns false.
Now add #define DRTEST_USE_QT before #include "DrMock/Test.h" and
run the test again. The test should now succeed.