How to mock a QML component

2019-06-17 08:02发布

问题:

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?

回答1:

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!



回答2:

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.