QmlUnit
QmlUnit is an easy-to-use Unit Testing framework with asynchronous testing support for Qt Declarative UI framework that can be used for testing javascript libraries and QML applications.
It comes with a GUI runner and depends on a modified version of QUnit for tests execution.
Screenshots
http://github.com/fgrehm/qmlunit/tree/master/screenshots
Writing tests
Please refer to QUnit API docs to find out how to write tests in QmlUnit as well.
Altough 100% compatible with QUnit, a QmlUnit test case has a few differences from plain QUnit tests:
-
The tests go into a class
*Test.qml
file. -
import QmlUnit 0.1
is required -
The test is an instance of
QmlTestCase
orQUnitTestSuite
element (most of the time). -
On
QmlTestCase
tests are element methods named test_* or asyncTest_* (forQUnitTestSuite
check out "Compatibility with QUnit" below).When writing async tests with QmlTestCase you can write the number of expectations in the method name itself like
asyncTest_2_assync_testing_with_expect_on_definition
.
You can check out QmlUnit own tests to have an idea of how it works.
Basic structure
A really simple test might look like this (setup and teardown are commented out to indicate that they are completely optional):
import Qt 4.7
import QmlUnit 0.1
QmlTestCase {
//function setup() {
//}
//function teardown() {
//}
function test_fail() {
ok(false);
}
}
Assertions and asynchronous testing
QmlUnit assertions and methods for dealing with asynchronous testing are the same as QUnit's.
Assertions
-
ok( state [, message] )
A boolean assertion, equivalent to JUnit's assertTrue. Passes if the first argument is truthy.
-
equals( actual, expected [, message] )
A comparison assertion, equivalent to JUnit's assertEquals.
-
same( actual, expected [, message] )
A deep recursive comparison assertion, working on primitive types, arrays and objects.
Asynchronous testing
-
start()
Start running tests again after the testrunner was stopped. See stop().
-
stop( [timeout] )
Stop the testrunner to wait to async tests to run. Call start() to continue. You can specify a timeout to fail the test after the given time in milliseconds.
Interacting with QML elements
Although not fully tested, QmlUnit allows you to interact with QML elements pretty much like you would do in an QML app:
import Qt 4.7
import QmlUnit 0.1
QmlTestCase {
TextInput {
id: input
validator: IntValidator { }
}
function setup() {
// Clean up input for testing
input.text = '';
}
function test_text_input_with_invalid_text() {
input.text = 'text';
ok(!input.acceptableInput, 'invalid text');
}
function test_text_input_with_valid_text() {
input.text = '5';
ok(input.acceptableInput, 'valid text');
}
}
When testing signals / slots make sure you disconnect signals from elements when you are done testing or use our connect() method which will disconnect them all after a test is run.
Helpers
Two simple helpers are provided to make interaction with QML elements easier:
-
connect( signal, function )
Same as Function.connect() but it keeps all connected functions in an array for disconnecting after a test is run. Check out interaction.js to find out how it works.
-
click( mouseAreaElement )
Shortcut for sending a left button clicked signal to a MouseArea element to avoid writing:
mouseAreaElement.clicked({ button: Qt.LeftButton, buttons: 0, modifiers: Qt.NoModifier });
If you think we could make use of some other helper just let me know :-)
Example usage:
import Qt 4.7
import QmlUnit 0.1
QmlTestCase {
MouseArea {
id: mouseArea
}
function teardown() {
input.text = '';
}
function asyncTest_1_clicking_on_mouse_area() {
connect(mouseArea.clicked, function (){
ok(true, 'MouseArea clicked');
start();
});
click(mouseArea);
}
}
Compatibility with QUnit
QmlUnit is 100% compatible with QUnit but remember that we don't have a Document Object Model available so make sure your code does not depend on it.
QUnit tests should be an instance of QUnitTestSuite and should define a body() method which contains any QUnit test code:
import Qt 4.7
import QmlUnit 0.1
QUnitTestSuite {
function body() {
module("setup test", {
setup: function() {
ok(true);
}
});
test("module with setup", function() {
expect(2); // http://docs.jquery.com/QUnit/expect#amount
ok(true);
});
}
}
Running tests
As you tipically will have all your tests grouped in some folder, you can run them
all with ./path/to/qmlunit <tests/folder>
. This will make QmlUnit look for
all files ending in Test.qml
in the folder specified.
If you wanna focus on some specific test case you can supply the desired file to
QmlUnit executable like ./path/to/qmlunit tests/SomeTest.qml
.
When a test is completed, you can click on its name to check out assertions that were run.
Dependencies and compilation
The framework was compiled and tested against 27/05/2010 sources on Ubuntu 10.04 and depends on:
-
QUnit (distributed with the framework).
Please note that the version distributed has all HTML related code removed / commented out in order to make it work with QML and is based on my fork which allows for async setup and teardown methods.
-
Qt compiled with declarative ui enabled.
Henrik Hartz has made QmlUnit compatible with Mac so it might work there as well.
Bugs and Contributions
If you find a bug, please report it or fork the project, fix the problem and send me a pull request.
Thanks goes to John Resig and other QUnit contributors for their work on QUnit and to Henrik Hartz for making QmlUnit compatible with Mac.
TODO
- Create a XUnit-like logger
- Ability to rerun tests
- Toggle display of errors only
- Mobile version
- Provide some way of interacting with C++ code
- Change mouse cursor for tests names to indicate that it can be clicked
License
This work is licensed under the MIT license.