小部件在测试期间不在 notifyListeners 上重建

Widget not rebuilding on notifyListeners during test

我正在尝试为使用 Provider 框架的屏幕构建 Widget 测试。

该应用程序有 1 个屏幕和 1 个按钮,当我点击按钮时,它会触发更新状态的功能。更新状态后,将出现一个带有键 Key('LoadedString') 的字符串。
在模拟器中手动测试时,这按预期工作。

当我尝试使用小部件测试来验证预期行为时,UI 似乎没有更新。

这是小部件:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider_page_state_controller.dart';

class ProviderPageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(providers: [
      ChangeNotifierProvider<ProviderPageStateController>(
          create: (context) => ProviderPageStateController())
    ], child: HomePage());
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var screenHeight = MediaQuery.of(context).size.height;
    ScreenState state =
        Provider.of<ProviderPageStateController>(context).pageState;

    // Loading state
    if (state == ScreenState.Loading) {
      return Scaffold(
          body: Container(
        color: Colors.purple,
        child: Center(child: Text('Loading...')),
      ));
    }
    // SuccessfullyLoaded state
    if (state == ScreenState.SuccessfullyLoaded) {
      return Scaffold(
          body: Container(
              color: Colors.green,
              child: Center(
                  child: Column(
                children: [
                  Padding(
                    padding:
                    EdgeInsets.fromLTRB(0, (screenHeight / 2) - 100,     0, 0),
                  ),
                  Text('Loaded!', key: Key('LoadedString')),
                  Container(
                    margin: EdgeInsets.all(25),
                    color: Colors.blueAccent,
                    child: TextButton(
                      child: Text(
                        'Reset',
                    style: TextStyle(color: Colors.white, fontSize:     20.0),
                      ),
                      onPressed: () {
                        Provider.of<ProviderPageStateController>(context,
                                listen: false)
                            .updateState(ScreenState.InitialState);
                      },
                    ),
                  ),
                ],
              ))));
    }
    // InitialState (=default state)
    return Scaffold(
        body: Container(
      color: Colors.white,
      child: Center(
        child: Column(
          children: [
            Padding(
          padding: EdgeInsets.fromLTRB(0, (screenHeight / 2) - 100,     0, 0),
            ),
            Container(
              key: Key('ButtonA'),
              margin: EdgeInsets.all(25),
              color: Colors.blueAccent,
              child: TextButton(
                child: Text(
                  'Button A',
                  style: TextStyle(color: Colors.white, fontSize: 20.0),
                ),
                onPressed: () {
                  Provider.of<ProviderPageStateController>(context,
                          listen: false)
                      .functionA();
                },
              ),
            ),
          ],
        ),
      ),
    ));
  }
}

这是控制器:

import 'package:flutter/material.dart';

enum ScreenState { InitialState, Loading, SuccessfullyLoaded }

class ProviderPageStateController extends ChangeNotifier {
  var pageState = ScreenState.InitialState;

  void updateState(ScreenState screenState) {
    pageState = screenState;
    notifyListeners();
  }

  Future<void> functionA() async {
    updateState(ScreenState.Loading);
    Future.delayed(Duration(milliseconds: 1000), () {
      updateState(ScreenState.SuccessfullyLoaded);
    });
  }

  Future<void> reset() async {
    updateState(ScreenState.InitialState);
  }
}

这是小部件测试:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import '../provider_page/provider_page_state_controller.dart';
import '../provider_page/provider_page_widget.dart';

void main() {
  testWidgets('ProviderPageWidget SuccessfullyLoaded test',
      (WidgetTester tester) async {
    final providerPageStateController = ProviderPageStateController();

    await tester.pumpWidget(
      ListenableProvider<ProviderPageStateController>.value(
        value: providerPageStateController,
        child: MaterialApp(
          home: ProviderPageWidget(),
        ),
      ),
    );

    await providerPageStateController.functionA();
    expect(providerPageStateController.pageState, ScreenState.Loading);
    await tester.pumpAndSettle(Duration(seconds: 2));
    expect(
    providerPageStateController.pageState,     ScreenState.SuccessfullyLoaded);

    // Why does this assert fail?
    expect(find.byKey(Key('LoadedString')), findsOneWidget);
  });
}

为什么我的 Widget 测试的最后一个 expect 失败了?

使用 Provider.value 构造函数,因为您使用的是变量而不是实例化对象。为了确保所有依赖项都得到更新,请在测试中使用 lazy: false 标志,即使值为 not accessed。产生的变化是:

void main() {
  final providerPageState = ProviderPageState();

...
      Provider<ProviderPageState>.value(
        value: providerPageState,
        child: MaterialApp(
          home: ProviderPageWidget(),
        ),
        lazy: false,
      ),
    );

我还建议切换到 ChangeNotifierProvider, when using provider to store states. Make sure your ProviderPageState is extending ChangeNotifier and implementing the needed methods, as reference you can use the implementation of ValueNotifier

我发现了问题所在。我没有在小部件测试中抽取正确的小部件。

将小部件测试更新为以下解决了问题:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import '../provider_page/provider_page_state_controller.dart';
import '../provider_page/provider_page_widget.dart';

void main() {
  testWidgets('ProviderPageWidget SuccessfullyLoaded test',
      (WidgetTester tester) async {
    final providerPageStateController = ProviderPageStateController();

    await tester.pumpWidget(
      ListenableProvider<ProviderPageStateController>.value(
        value: providerPageStateController,
        child: MaterialApp(
          home: HomePage(),
        ),
      ),
    );

    await providerPageStateController.functionA();
    expect(providerPageStateController.pageState, ScreenState.Loading);
    await tester.pumpAndSettle(Duration(seconds: 2));
    expect(providerPageStateController.pageState, ScreenState.SuccessfullyLoaded);

    // Why does this assert fail?
    expect(find.byKey(Key('LoadedString')), findsOneWidget);
  });
}

变化已上线:

home: HomePage(),