如何在单元测试期间访问 StatefulWidget 的状态内容?

How can I access the contents of a StatefulWidget's State during a unit test?

我有一个 StatefulWidget(称之为 MyWidget),它的状态 (MyWidgetState) 有一个字段 myData,它在 initState() 期间初始化如下:

void initState() {
    super.initState();
    myData = new myData(config.someField.getId());
}

当用户按下按钮时,myData 被添加到全局列表或从全局列表中删除。

我正在尝试编写单元测试来测试此行为,但我不知道如何访问 MyWidgetState。我尝试在 setup() 中包含这个:

widget = MyWidget();
widgetState = widget.createState(); 
widgetState.init();

但每次尝试 initState() 时都会崩溃,抱怨 "someField was called on null"。没关系。我可能试图以这种方式进行作弊,我应该使用 WidgetBuilder 做一些事情或使用 MyWidget 启动应用程序,然后在正确实例化后在树中找到 MyWidget。

如果我做了所有这些,一旦我做了,我如何访问那个 MyWidget 的 MyWidgetState 来获取 myData 的副本并将其与全局列表进行比较?

使用 tester.pumpWidgets 创建小部件,然后使用 tester.state(find.foo) 查找 State(其中 find.foo 是查找小部件的查找器)。有关更多选项,请参阅 WidgetTester 文档。

您可以创建一个状态,然后访问它的内容。 关注 Ian Hickson 回答。下面是实现的例子:

final MyWidgetState myWidgetState = tester.state(find.byType(MyWidget));

然后就可以访问状态内容了:

myWidgetState.myData;

您可以在 Flutter 中找到更多示例 repo

如果您使用任何代码片段在 Flutter 中创建有状态的小部件,您可能已经注意到 Flutter 创建的状态 class 是私有的 class,它由开始。这很好,因为这样的 classes 不打算在库外使用 - 除了测试。

在 widget/integration 测试中,您可能希望访问状态 class 的变量。在这种情况下,将状态 class 标记为私有将意味着您无法直接访问这些变量。这可能不是所希望的。为了两全其美,我们可以使用 @visibleForTesting 装饰器。

示例如下。

import 'dart:math';
import 'package:flutter/material.dart';

class CounterWidget extends StatefulWidget {
  const CounterWidget({Key key}) : super(key: key);

  @override
  CounterWidgetState createState() => CounterWidgetState();
}

@visibleForTesting
class CounterWidgetState extends State<CounterWidget> {
  int counter;
  @override
  void initState() {
    super.initState();
    var rndGen = Random(79);
    counter = rndGen.nextInt(96);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        child: ElevatedButton(
            key: Key('incrementButton'),
            onPressed: () {
              setState(() {
                counter++;
              });
            },
            child: Text(
              'Increment($counter)',
            )));
  }
}

@visibleForTesting 的文档说

Used to annotate a declaration that was made public, so that it is more visible than otherwise necessary, to make code testable.

Tools, such as the analyzer, can provide feedback if

the annotation is associated with a declaration not in the lib folder of a package, or a private declaration, or a declaration in an unnamed static extension, or the declaration is referenced outside of its defining library or a library which is in the test folder of the defining package.

这里的关键要点是,如果您在库或测试之外使用 public 状态 class,Dart 分析器会警告您,这在 Flutter 设计中很少是必需的。

使用visibleForTesting的想法来自here

如果您想对小部件状态的其中一种方法编写单元测试,可以按以下方式完成:

// Assuming your stateful widget is like this:
class MyWidget extends StatefulWidget {
  const MyWidget({this.someParam, Key? key}) : super(key: key);
  final String someParam;

  @override
  MyWidgetState createState() => MyWidgetState();
}

@visibleForTesting
class MyWidgetState extends State<MyWidget> {
  int methodToBeTested() {
    // dummy implementation that uses widget.XXX
    if (widget.someParam.isEmpty) return 0;
    return 1;
  }

  @override
  Widget build(BuildContext context) {
    // return ...
  }
}

// In your test file
void main() {
  test('if widget.someParam is empty, do something', () {
    const widget = MyWidget(someParam: '');
    final element = widget.createElement(); // this will set state.widget
    final state = element.state as MyWidgetState;
    expect(state.methodToBeTested(), 0);
  });
}