This page shows how to set up a UI test suite and run a complete scenario. For the general test setup
(aui_enable_tests, running Tests), see Writing tests. An overview of the module is at uitests.
A UI test creates real windows and views, but no native window is opened. testing::UITest switches to
AStubWindowManager and a software renderer, so the test runs on a machine without a display. The views are laid out,
styled and rendered as usual, the screenshot of any moment is available, and input is delivered to the window the same
way the platform would do it.
Include <AUI/UITest.h>; it brings By, all actions, all assertions and GTest.
The application under test here is a button "Say hello" that reveals a "Hello!" label. Note GMock: the window
exposes MOCK_METHODs that the signal handlers call, so the test can tell whether the click was delivered.
Tip
Place the window in the fixture class as a nested class, as above, when it is only needed for tests. To test a
window of your application, include its header and _new<MyWindow>()->show() it.
TEST_F(UIClick,HelloAppearsAfterClick){EXPECT_CALL(*mTestWindow,onButtonClicked).Times(1);// press the buttonBy::text("Say hello").perform(click());// check label is appearedBy::text("Hello!").check(visible(),"label is not appeared");}
By::text("Say hello") finds the button, click() clicks the center of it, and By::text("Hello!") finds the label
and visible() checks that it is shown. See Finding views, Simulating user input and Checking views.
Views can be found by their ASS name (<< "#username" in the view tree). The following window has a text field and a
button; the test types a name, presses the button and checks the label.
TEST_F(UIType,HelloAppearsAfterClick){// type "Steve" to the text fieldBy::name("#username").perform(type("Steve"));// press the buttonBy::text("Say hello").perform(click());// check label is appeared and contains text "Hello, Steve!"By::name("#hello").check(visible(),"label is not appeared").check(text("Hello, Steve!"),"invalid text");}
A UI is not updated by itself in a test; nothing runs an event loop for you. Layout, style changes and queued messages
(for example, lambdas posted with AUI_UI_THREAD) are processed when you call
uitest::frame(). Every perform and check of UIMatcher calls it before and after the action, so most tests
never need it. Call it explicitly when you changed something directly and want to look at the result:
uitest::frame() also repacks every window (see AStubWindowManager::drawFrame), so a window is shrunk to the size
of its contents unless the contents are expanding. Use AUI_REPEAT(10) { uitest::frame(); } to let a layout settle
if it needs several passes.
Note
Timers and other asynchronous work do not advance on their own either. If your code waits for an AThread::sleep
or a background task, call AThread::processMessages() after waiting, and prefer to make the code under test
injectable (for instance, pass the delay as a parameter) so the test does not have to sleep.
TEST_F(UIReactiveTest,Label){// Test that a reactive label updates its text when the bound property changes.// The test creates a simple state object with a reactive string property// and binds it to a label using the AUI_REACT macro. It then verifies// the initial rendered text and the updated text after changing the// property.usingnamespaceass;structState{AProperty<AString>name;};autostate=_new<State>();mWindow->setContents(Vertical{Label{AUI_REACT("{}!"_format(state->name))}});// Initially, the property is empty, so the label should display "!".EXPECT_EQ(*_cast<ALabel>(By::type<ALabel>().one())->text(),"!");// Update the property; the label should automatically reflect the new value.state->name="Hello";EXPECT_EQ(*_cast<ALabel>(By::type<ALabel>().one())->text(),"Hello!");}
Release the windows and views you hold before calling UITest::TearDown(), as the fixtures above do
(mTestWindow = nullptr;). UITest::TearDown() closes all windows and destroys the window manager.