← 開發日常

Widget Test的14種find方法

上禮拜介紹了一個Widget Test所包含的流程,過程中用到Finder、Tester、和Matcher的部分方法...等,但是實際上這些元件都還有許多不同的操作,今天先介紹Finder的各種方法。

find的方法們

  1. text:找到一個顯示特定字串的Text Widget
    dart
    find.text("Hello World");
  2. textContaining:找到一個顯示文字中包含特定字串的Text Widget
    • 當文字可能不是固定字串時,例如:在一個棋局中顯示黑棋或白棋獲勝時,因為有可能會是"Black Wins"或"White Wins",此時就會需要使用到textContaining做部分比較
    dart
    find.textContaining("Wins");
    • 除了直接使用字串以外,也可使用正規表達式(RegExp)來找尋
    dart
    find.textContaining(RegExp(r"Wins$"));
  3. widgetWithText:找到一個包含特定字串Text Widget的Widget
    • 假設有一個文字按鈕使用FlatButton,在FlatButton的child再放上Text,此時我們就可以用這個方法來找到FlatButton
    dart
    find.widgetWithText(FlatButton, "Hello World")
    • 以上面的例子來說,假設畫面中有兩個Widget包含Text("Hello World"),一個是FlatButton,一個是Container,此時就會找到FlatButton,因為它符合第一個參數中的條件。
    dart
    Column(
    	children: <Widget>[
    		FlatButton(
    			onPressed: _incrementCounter,
    			child: Text("Hello World"),
    		),      
    		Container(
    		  child: Text("Hello World"),
    		),
    	],
    )
  4. byKey:找到一個符合Key值的Widget
    dart
    find.byKey(ValueKey("First Hello World"))
    • 以上面的例子來說,假設有兩個一樣的Text,就能正確找到Key值符合的Widget
    dart
    Column(
    	children: <Widget>[
    		Text(
    			"Increase",
    		  key: ValueKey("First Hello World"),
    		),
    		Text(
    			"Increase",
    		),
    	],
    )

    ⇒ 如果是用UniqueKey這類的Key,因為無法在測試中產生一模一樣的Key,此時可以考慮使用其他find方法解決或者由外部注入Key的方式解決。

  5. byIcon:找到一個Icon符合的Icon Widget
    dart
    find.byIcon(Icons.add);
  6. widgetWithIcon:與widgetWithText類似,同樣是找到一個包含特定Icon Widget的Widget
    dart
    find.widgetWithIcon(FlatButton, Icons.add);
  7. byType:找到一個類別符合的Widget
    dart
    find.byType(FlatButton);
  8. byWidget:找到與傳入的Widget傳入Widget同一實例的Widget
    dart
    find.byWidget(Text("Hello World"))
    • 假設我們想測試MyContainer是否有正常渲染child時,就可以使用byWidget找到預期的Widget並檢查
    dart
    testWidgets('test my container', (WidgetTester tester) async {
    	var text = Text("Hello World");
    
      await tester.pumpWidget(
        createTestingWidget(MyContainer(child: text)),
      );
    
      await tester.pump();
    
      expect(find.byWidget(text), findsOneWidget);
    });
    dart
    class MyContainer extends StatelessWidget {
      final Widget child;
    
      MyContainer({this.child});
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            Text("Header"),
            child,
            Text("Footer"),
          ],
        );
      }
    }
  9. byWidgetPredicate:找到一個符合predicate回傳true的Widget
    • 參數是一個predicate方法,在方法中可以比較很多東西,例如類別或Widget中的資料,透過比較Widget中的屬性,並回傳boolean值表示這個widget是否是目標Widget,是一個十分泛用的方法
    dart
    find.byWidgetPredicate(
              (widget) => widget is Text && widget.data == "Hello World")
  10. byTooltip:找到符合message的Tooltip Widget
    dart
    find.byTooltip("Hello World");

    ⇒ 這個方法實際上是透過byWidgetPredicate實作的

  11. byElementType:與byType相似,不同的這個方法會找的是符合類別的Element
    dart
    find.byElementType(InheritedElement);
  12. byElementPredicate:與byWidgetPredicate相似,不同的是他的predicate參數傳入的是Element
    • 這個方法可以用來比較Element中的屬性,例如自己做了一個MyCheckbox的StatefulWidget,就能用這個方法找出已選取的MyCheckbox
    dart
    find.byElementPredicate((element) {
    	if(element is StatefulElement) {
    	  var state = element.state;
        return state is MyCheckboxState && state.isChecked;
      }
      return false;
    });
    dart
    class MyCheckbox extends StatefulWidget {
    
      @override
      MyCheckboxState createState() => MyCheckboxState();
    }
    
    class MyCheckboxState extends State<MyCheckbox> {
      bool isChecked;
    
      @override
      Widget build(BuildContext context) {
        ...
      }
    }
  13. ancestor:在某個Widget的祖先中,找尋符合條件的Widget
    • 從符合of參數的Widget開始往祖先找,直到找到符合matching參數的Widget,這兩個參數都可以使用前幾個find方法來決定條件
    dart
    find.ancestor(of: find.byType(MyContainer), matching: find.byWidget(text));

    ⇒ widgetWithText, widgetWithIcon也是用這個方法實作

  14. descendant:在某個Widget的子孫中,找尋符合條件的Widget。
    • 參數基本上與ancestor相似,只是搜尋的方向不同
    dart
    find.descendant(of: find.byWidget(text), matching: find.byType(MyContainer));

小結

Finder中有各式各樣的方法,在不同情境下使用不同的方法來找到想要的Widget,然後才能測試中正確的操作或驗證這些Widget,讓測試保護我們的產品代碼。