UI单元测试中的组件查询
UIUnitTest 基类能够获取已实例化的视图,但子组件并不总是可以直接访问。例如,组件可能被保存在私有可见性的字段中,甚至可能根本没有在视图类中引用。
为了克服这种限制,UIUnitTest 提供了组件查询功能,让您能够在组件树中搜索并找到测试方法需要交互的组件。
组件查询(Component Queries)
您可以通过调用 $() 方法来获取一个 ComponentQuery 对象,并指定要搜索的组件类型。
当此查询对象已准备好,配置好所有条件后,使用终端操作符获得找到的组件。例如终端操作符包括 first()、last()、atIndex()、all() 和 id()。
Source code
Java
// 获取UI中第一个TextField
TextField nameField = $(TextField.class).first();限定查询范围(Scoping Queries)
通过使用 $view() 方法,您还可以将搜索范围限制在当前视图的子组件,或者使用 $(MyComponent.class, rootComponent) 将搜索范围限制在另一个组件中。
Source code
Java
// 获取当前视图中的第一个TextField
TextField nameField = $view(TextField.class).first();
// 获取一个容器中包含的第一个TextField
TextField nameField = $(TextField.class, view.formLayout).first();查询对象提供许多过滤工具,可以细化搜 索。比如,您可以依据组件的 id、属性值或使用自定义的谓词(predicate)条件对潜在候选进行过滤。
Source code
Java
// 获取带有指定标签的TextField
TextField nameField = $view(TextField.class)
.withPropertyValue(TextField::getLabel, "First name")
.single();
// 获取视图中符合条件的所有TextField
Predicate<TextField> fieldHasNotValue = field -> field.getOptionalValue().isEmpty();
Predicate<TextField> fieldIsInvalid = TextField::isInvalid;
List<TextField> textField = $view(TextField.class)
.withCondition(fieldHasNotValue.or(fieldIsInvalid))
.all();有时您可能需要查询嵌套在UI中,并由不同类型组件组成的多个层级中的组件。为了简化这种情况,查询对象提供了方法,可以在找到的组件基础上链接新的查询,这样就可以以流式的方式创建复杂的查询。例如,thenOn() 方法及其变体,如 thenOnFirst(),会为您提供一个新的查询对象,并将搜索范围设置为当前查询所选中的组件。
Source code
链式查询示例
// 查找视图中的所有 'VerticalLayout' 组件
TextField textField = $view(VerticalLayout.class)
// 选择第二个并开始搜寻其中的 'TextField'
.thenOn(2, TextField.class)
// 筛选禁用的 'TextField'
.withCondition(tf -> !tf.isEnabled())
// 最后获取满足条件的最后一个
.last();自定义测试程序(Custom Testers)
针对特定组件,可以使用自定义测试程序(custom testers),以提供针对该组件或其扩展组件的测试API。使用 @Tests 注解标记测试程序,并指定此测试程序适用的组件类型。
使用 test(Component.class, component) 获取一个通用测试程序,将会检查所有可用测试程序,确定是否有能 Tests 此组件或其超类型的测试器。
默认情况下,框架会扫描 com.vaadin.flow.component 包下的测试器实现,因此,如果您将自定义测试程序添加到这些包内并继承了 ComponentTester,则它立即可用。
如果您的自定义测试程序位于其他包中,则必须使用 @ComponentTesterPackages 在测试类中指定需要扫描的包位置。
Source code
定义自定义测试程序包
@ComponentTesterPackages("com.example.application.views.personform")
class PersonFormViewTest extends UIUnitTest {
}自定义测试程序类可以内部调用其他测试程序,以下示例中演示了 PhoneNumberFieldTester 类的使用:
Source code
用于 CustomField 的示例自定义测试程序
CustomField 的示例自定义测试程序// Tests注解指定此测试程序适用的组件类型
@Tests(PersonFormView.PhoneNumberField.class)
public class PhoneNumberFieldTester extends ComponentTester<PersonFormView.PhoneNumberField> {
// 可在自定义测试程序内部使用其他测试程序
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();
}
}Source code
PhoneNumberField.java
PhoneNumberField.javastatic class PhoneNumberField extends CustomField<String> {
ComboBox<String> countryCode = new ComboBox<>();
TextField number = new TextField();
// ...
}DDC7D136-1A56-44FC-B256-C15DB7645EDC