Querying Components in UI Unit Tests
The UIUnitTest
base class is able to get the instantiated view, but child components may not always be accessible directly. For example, components may be stored in fields with private visibility or they may even not be referenced at all in the view class.
To overcome this limitation, UIUnitTest
provides a component query functionality that lets you search the component tree for the components you need to interact with in test methods.
Component Queries
You can get a ComponentQuery
object by calling the $()
method, specifying the type of the component you are searching for.
Once the query is ready with all conditions configured, use a terminal operator to retrieve the components that it found. Examples of terminal operators are first()
, last()
, atIndex()
, all()
, and id()
.
// Get the first TextField in the UI
TextField nameField = $(TextField.class).first();
Scoping Queries
You can also restrict search scope to the children of the current view by using the $view()
method, or even to another component by using $(MyComponent.class, rootComponent)
.
// Get the first TextField in the current view
TextField nameField = $view(TextField.class).first();
// Get the first TextField nested in a container
TextField nameField = $(TextField.class, view.formLayout).first();
The query object has many filtering utilities that can be used to refine the search. For example, you can filter by component id
, by a property value, or using custom predicates on potential candidates.
// Get the TextField with the given label
TextField nameField = $view(TextField.class)
.withPropertyValue(TextField::getLabel, "First name")
.single();
// Get all TextFields in the view that satisfies the conditions
Predicate<TextField> fieldHasNotValue = field -> field.getOptionalValue().isEmpty();
Predicate<TextField> fieldIsInvalid = TextField::isInvalid;
List<TextField> textField = $view(TextField.class)
.withCondition(fieldHasNotValue.or(fieldIsInvalid))
.all();
You may sometimes need to do a query for components nested inside the UI, in a hierarchy composed of many different types of components. To simplify such situations, the query object offers methods to chain a new query starting with a found component, so that a complex query can be created in a fluent way. The thenOn()
method and its variants, for example thenOnFirst()
, provide you with a new query object for the given component type, setting the search scope to the component selected from the current query.
// Search for all 'VerticalLayout's in the view
TextField textField = $view(VerticalLayout.class)
// take the second one and start searching for 'TextField's
.thenOn(2, TextField.class)
// filter for disabled 'TextField's
.withCondition(tf -> !tf.isEnabled())
// and get the last one
.last();
Custom Testers
Custom testers are available for components that give a testing API for the component or one extending it. Testers are annotated using the @Tests
annotation, which specifies which components the tester is for.
Getting a generic tester using test(Component.class, component)
checks the available testers to determine whether one exists that Tests
the component or its supertype.
By default, tester implementations are scanned from the com.vaadin.flow.component
package, so adding a custom tester to the package that extends ComponentTester
makes it immediately available.
To have the custom testers in another package, the test needs to be annotated with @ComponentTesterPackages
containing the packages to scan for testers.
@ComponentTesterPackages("com.example.application.views.personform")
class PersonFormViewTest extends UIUnitTest {
}
Custom tester classes can use other testers internally, as demonstrated in PhoneNumberFieldTester
.
// Tests defines the components this tester should be used for automatically
@Tests(PersonFormView.PhoneNumberField.class)
public class PhoneNumberFieldTester extends ComponentTester<PersonFormView.PhoneNumberField> {
// Other testers can be used inside the custom tester
final ComboBoxTester<ComboBox<String>, String> combo_;
final TextFieldTester<TextField, String> number_;
public PhoneNumberFieldWrap(PersonFormView.PhoneNumberField component) {
super(component);
combo_ = new ComboBoxTester<>(
getComponent().countryCode);
number_ = new TextFieldTester<>(getComponent().number);
}
public List<String> getCountryCodes() {
return combo_.getSuggestionItems();
}
public void setCountryCode(String code) {
ensureComponentIsUsable();
if(!getCountryCodes().contains(code)) {
throw new IllegalArgumentException("Given code isn't available for selection");
}
combo_.selectItem(code);
}
public void setNumber(String number) {
ensureComponentIsUsable();
number_.setValue(number);
}
public String getValue() {
return getComponent().generateModelValue();
}
}
static class PhoneNumberField extends CustomField<String> {
ComboBox<String> countryCode = new ComboBox<>();
TextField number = new TextField();
// ...
}
DDC7D136-1A56-44FC-B256-C15DB7645EDC