Testing
A widget test runs your UI without opening a terminal, so dart test runs the
same way in CI. Tests drive the UI through a tester instead of calling
runApp, which needs a real terminal and throws without one, as in most CI
jobs. Each example below pairs a running widget with its source and a test you
can try.
Write your first test
Section titled “Write your first test”button('Add one') finds the button by its role (the kind of control) and
label (its name). Button supplies both automatically.
The Logical test invokes its action with press(). The Keyboard test
sends Enter through the focus system. Try the counter, then compare the two:
pumpWidget mounts the widget and completes its first frame. testWidgets
provides a fresh tester for each test and disposes it afterward.
expect(actual, matcher) checks the result. Here, exists(text(...)) returns a
boolean; isTrue is Dart’s standard matcher for true. A logical press checks
the button’s behavior; use keyboard or pointer tests to check input routing.
Set up and run the tests
Projects created with fleury create already include fleury_test and
test. To add them to another app during the pre-release Git installation,
use:
dev_dependencies: fleury_test: git: url: https://github.com/danReynolds/fleury.git path: packages/fleury_test test: ^1.26.3
dependency_overrides: fleury: git: url: https://github.com/danReynolds/fleury.git path: packages/fleuryThe fleury dependency override keeps the app and the tester on the same
checkout. Run dart test from your application package. In a Fleury checkout,
run every example from this guide with:
cd website/examplesdart test test/testing_guide_test.dartKeep the imports and main from the first test. The remaining tests show
individual cases to add inside it.
Find a component, then edit its controls
Section titled “Find a component, then edit its controls”Both forms have a Name field and an Email updates checkbox. Select the Work widget by its type and key, then change only its controls. Use Run test to watch the actions and assertions, or type into Work and use Tab to explore:
fill replaces the field’s text. check leaves the option checked, including
when it was already on. Your own widgets, like Preferences, use these same
queries and actions.
Choose when a save finishes
Section titled “Choose when a save finishes”Try Save to see its pending state. The live demo completes after a short
delay; the test uses a Completer from dart:async to finish the request itself:
After request.complete(), await tester.settle() lets the future callback run
and processes the resulting frames. This makes the pending and completed states
easy to check separately. Use settle() whenever a future or stream has to
deliver; the synchronous pump methods never give it a chance to run.
Check a moment in an animation
Section titled “Check a moment in an animation”Press Animate to move between 0% and 100%. The test advances Fleury’s animation clock to the halfway point, then finishes the animation:
pump(duration) advances animation time immediately. pumpAndSettle() runs
frames until the animation is quiet. Both are synchronous: they move the
animation clock without waiting on wall time, but they don’t let pending
futures complete. The progress bar also shows how to select other control
kinds with target(role: ..., label: ...).
Test a command and its shortcut
Section titled “Test a command and its shortcut”This draft editor registers Save as a command
with a Ctrl+S shortcut. Edit the draft, then save with the button or
Ctrl+S:
AppCommand get saveCommand => AppCommand( id: const CommandId('editor.save'), title: 'Save draft', shortcuts: [KeySequence.ctrl.s], enabled: (_) => dirty && !saving, run: (_) => save(),);The editor registers the command in a CommandScope around its content, and
its Save button is a CommandButton for the same ID. A test can run the
command by that ID, without finding the button:
testWidgets('saves the draft by its command ID', (tester) async { String? saved; tester.pumpWidget( FleuryApp( title: 'Draft editor', home: DraftEditor(save: (text) async => saved = text), ), ); await tester.field('Draft').fill('Ready for review.');
final result = await tester.invokeCommand(const CommandId('editor.save')); expect(result.completed, isTrue); expect(saved, 'Ready for review.');
final again = await tester.invokeCommand(const CommandId('editor.save')); expect(again.status, CommandInvocationStatus.disabled);});invokeCommand waits for the command to finish and returns its result.
completed is false when the command was disabled, missing, or threw; status
says which. Here the second save is disabled, because there is nothing left to
save.
To check that the shortcut reaches the command from the focused field, press it the way a user would:
testWidgets('saves the draft with Ctrl+S', (tester) async { String? saved; tester.pumpWidget( FleuryApp( title: 'Draft editor', home: DraftEditor(save: (text) async => saved = text), ), ); final editor = tester.target(type: DraftEditor); expect(editor.field('Draft'), isFocused); tester.type(' Ready for review.'); tester.press(KeySequence.ctrl.s); await tester.settle();
expect(saved, 'Ship the testing guide. Ready for review.'); expect(tester.exists(text('All changes saved')), isTrue); expect(editor.button('Save'), isDisabled);});A shortcut starts the command without waiting for it, so the test calls
settle() before checking the save.
Go further
Section titled “Go further”The complete test file behind this guide also covers failed saves and a dialog that restores focus when it closes. For custom controls, see how widgets publish semantics.
The testing API reference covers additional actions, query rules, failure diagnostics, and golden tests.