flutterwidgetflutter-providerwidget-test-flutter

Widget not rebuilding on notifyListeners during test


I'm trying to build a Widget test for a screen that's using the Provider framework.

The app has 1 screen with 1 button, when I tap the button it will trigger a function to update the state. Once the state is updated a string with the key Key('LoadedString') will appear.
When manually testing in the simulator this works as expected.

When I'm trying to use a widget test to verify the expected behavior it seems like the UI is not updating.

This is the Widget:

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();
                },
              ),
            ),
          ],
        ),
      ),
    ));
  }
}

This is the Controller:

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);
  }
}

This is the Widget test:

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);
  });
}

Why is the last expect of my Widget test failing?


Solution

  • I found out what the issue was. I was not pumping the correct widget in the widget test.

    Updating the widget test to the following solved the issue:

    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);
      });
    }
    

    The change is on the line:

    home: HomePage(),