How to mock a QML component
Asked Answered
A

3

9

Actually I'm trying to run some test on a QML component which embeds C++ objects. Unfortunately, I'm getting some errors when I execute my tests. The C++ objects aren't recognized by the QML file. That makes also sense as the C++ objects are set in the main.cpp file.

My question is: How can I mock an context property for performing QML tests? Or other said, how can I do unit-test with mixing Qt/QML code?

Airminded answered 14/4, 2014 at 12:56 Comment(3)
How are you running your tests? Are you writing unit tests in QML and trying to use qmltestrunner to run the tests? Are you trying to run QML inside C++ and run the tests from googlemock? Or are you doing something else?Polytypic
Did you ever get this working? I have the same problem.Germanic
I figured out something that worked and added it as an answer below. Unfortunately, my original cry for help there was down voted; so I'm hoping someone will take another look and up vote the edited one back to life.Germanic
G
1

I got QML tests working for me without compiling in any C++ code.

In my case, I have a C++ object controller with a property called left_motor, which is another object, and that has a property speed.

Note that speed is readable, but not writable. Any updates will happen through slots. In QML that looks like this: controller.left_motor.onGuiSpeedChanged(speed)

I was able to mock this in QML using Item components, properties, and some javascript.

Item {                      // mock of controller
    id: controller
    property alias left_motor: left_motor
    Item {
        id: left_motor
        property int speed: 0
        function onGuiSpeedChanged(arg) {
            speed = arg
        }
    }
}
property alias controller: controller

Now calls to controller.left_motor.onGuiSpeedChanged(speed) resolve like before, but connect into the mock function. I can even read back the speed property to know that the call happened.

Here is my test function (the code I'm testing is part of page1):

function test_set_speed() {
    console.log("controller.left_motor.speed: " + controller.left_motor.speed)
    var got = page1.set_left_speed(250)
    compare(got, 250, "set_left_speed() return incorrect")
    console.log("controller.left_motor.speed: " + controller.left_motor.speed)
}

Note that it's important to use slots instead of writable properties. The call to a slot looks just like a function call and can be mocked as such. I could not figure out a way to mock out a property write.

I had started out trying writable properties because that was the first thing in the documentation on binding C++ and QML. It connects QML and C++ as expected, but can't be mocked out for testing.

Germanic answered 12/4, 2017 at 23:58 Comment(2)
This space is for answers, any query you can do spaces similar to this one or create a new question.Eclecticism
My original request was misplaced, but I did find an answer and edited it to that. I'm not sure the best action here: either post a new answer (and leave the dead one), or edit the bad one (and have it have a negative score). I did the latter, but welcome advise.Germanic
K
0

As I understand you right, you got same problem as I. Some time ago I wrote this mock: https://bitbucket.org/troyane/qml-cpp-template (you can use that code free for your purposes).

Take a look at main.cpp, there you can see two ways of doing things:

// 1 case:
// Register type and create object at QML side
qmlRegisterType<CppClass>("CppClassModule", 1, 0, "CppClass");
QQmlApplicationEngine engine(QUrl("qrc:///qml/main.qml"));
qDebug() << "Qt version: " << qVersion();
// 2 case:
// Create object here and "move it up" to QML side
// engine.rootContext()->setContextProperty("cppClass", new CppClass);

Good luck!

Keening answered 14/4, 2014 at 16:43 Comment(0)
P
0

The best solution would be to move your C++ types to a QML plugin so that the QML tests can import them. This plugin needs to be available to your tests at runtime, which means ensuring that they can be found in the import path. If you run into any issues with the plugin not being found, you can set the QML_IMPORT_TRACE environment variable to 1 to give you some useful debugging output.

However, even QML-only test cases involve some C++ to set them up.

To execute C++ code before any of the QML tests are run, the QUICK_TEST_MAIN_WITH_SETUP macro can be used:

// src_qmltest_qquicktest.cpp
#include <QtQuickTest>
#include <QQmlEngine>
#include <QQmlContext>

class Setup : public QObject
{
    Q_OBJECT

public:
    Setup() {}

public slots:
    void qmlEngineAvailable(QQmlEngine *engine)
    {
        engine->rootContext()->setContextProperty("myContextProperty", QVariant(true));
    }
};

QUICK_TEST_MAIN_WITH_SETUP(mytest, Setup)

#include "src_qmltest_qquicktest.moc"

The most flexible and powerful option is to write tests in C++ and load the QML from there.

Physostomous answered 14/3, 2022 at 4:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.