I want to test and cover both paths of a widget that uses kIsWeb. the class looks like this :

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    if (kIsWeb) {
      return Text("I'm web");
    } else {
      return Text("I'm not web");
    }
  }
}

After searching a while I found a way to test this here : Testing kIsWeb constant in flutter

This is the Test class that I wrote :

 class MockAppService extends Mock implements AppService {}

         void main() {
          final mockAppService = MockAppService();
          when(mockAppService.getkIsWeb())
              .thenAnswer((realInvocation) => true);

          testWidgets("add item", (WidgetTester tester) async {
            await tester.pumpWidget(MaterialApp(
              title: 'Flutter Demo',
              home: Scaffold(
                appBar: AppBar(
                  title: Text("title"),
                ),
                body: Center(
                  child: Text("message"),
                ),
              ),
            ));
            final PaymentPageState myWidgetState = tester.state(find.byType(PaymentPage));
            expect(find.byType(PaymentPage), findsOneWidget);
          });
        }

but I get the error

type 'Null' is not a subtype of type 'bool'

My AppService class and the abstract class looks like this,

abstract class IAppService {
  bool getkIsWeb();
}

class AppService implements IAppService {
  bool getkIsWeb() {
    return kIsWeb;
  }
}

I'm new to Flutter and does not know a lot of things. please help.


Solution 1: Tim Brückner

The solution you found about how to test kIsWeb should work, but you have to understand the whole picture.

First of all, the main idea behind this approach is, to move the responsibility to decide, whether the application runs on the web or not, out of the widget and not use kIsWeb directly in the widget's build method.

Therefore an interface named IAppService is introduced and the class AppService implements this interface. The interface is not really needed and I will work with the AppService class directly in my example.

Your widget should use an instance of AppService and call getKIsWeb on it, instead of using the kIsWeb constant directly. There are many ways to provide the instance to the widget. Using a constructor parameter is one of the easiest, but you may want to take a look at dependency injection or state management solutions in the future.

class MyWidget extends StatelessWidget {
  const MyWidget({Key? key, required this.appService}) : super(key: key);

  final AppService appService;

  @override
  Widget build(BuildContext context) {
    if (appService.getKIsWeb()) {
      return const Text('I\'m web');
    } else {
      return const Text('I\'m not web');
    }
  }
}

class AppService {
  bool getKIsWeb() {
    return kIsWeb;
  }
}

And last not least. When you test your widget, you give it a mocked implementation of the AppService class, which returns either true or false when getKIsWeb gets called, depending on what you want to test.

class MockAppService extends Mock implements AppService {}

void main() {
  testWidgets('Test web widget', (WidgetTester tester) async {
    final mockAppService = MockAppService();
    when(() => mockAppService.getKIsWeb()).thenReturn(true);

    await tester.pumpWidget(
      MaterialApp(
        title: 'Flutter Demo',
        home: Scaffold(
          body: MyWidget(
            appService: mockAppService,
          ),
        ),
      ),
    );

    final webTextFinder = find.text('I\'m web');
    expect(webTextFinder, findsOneWidget);
  });

  testWidgets('Test not web widget', (WidgetTester tester) async {
    final mockAppService = MockAppService();
    when(() => mockAppService.getKIsWeb()).thenReturn(false);

    await tester.pumpWidget(
      MaterialApp(
        title: 'Flutter Demo',
        home: Scaffold(
          body: MyWidget(
            appService: mockAppService,
          ),
        ),
      ),
    );

    final webTextFinder = find.text('I\'m not web');
    expect(webTextFinder, findsOneWidget);
  });
}

Please note that I am using Mocktail here instead of Mockito. But you should get the idea.

when(() => mockAppService.getKIsWeb()).thenReturn(false); is Mocktails way to define the mocked behavior. https://pub.dev/packages/mocktail