This is a quick exploration of testing within Flutter Forge.

Flutter Forge is specifically designed to make testing as easy possible. We are big fans of TDD and Outside-In Development.

There seems to some confusion lately that TDD which (does mean test first) somehow means you have to write a 100% perfect and complete test up front. That is incorrect, you are to evolve your test to a point of correctness and completion.

Another point of confusion is that people feel you have to test every little bit of code. This is incorrect as well. Yes you want your code covered and tested. But if you have tests toward the outer edges of your application then you end up getting coverage of more internal pieces of your system often without needing tests for them. In fact this promotes ease of refactoring.

This is why our stance is that you should do TDD from the Outside-In. There are lots of different types of tests, and you should understand them and when to use them all. However, in the context of Flutter Forge and thinking about Outside-In there are really two main ones, Widget Tests and Store Tests.

Widget Tests

Widget tests are the same Widget Tests you are likely already familiar with in Flutter. The only difference is that you are constructing a Flutter Forge widget. This generally looks something like the following.

void main() {
  testWidgets('Counter widget increments smoke test',
      (WidgetTester tester) async {
    // Create the widget store
    final store = Store(
        initialState: const CounterWidgetState(count: 0),
        reducer: counter.counterReducer,
        environment: counter.CounterWidgetEnvironment());

    // Build our app and trigger a frame.
    await tester.pumpWidget(MaterialApp(home: CounterWidget(store: store)));

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the 'increment' button and trigger a frame.
    await tester.tap(find.text('increment'));
    await tester.pump();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);

In the above the big advantage we have over other situations is that since we are testing a Flutter Forge widget we have a formalized interface to be able to trivially initialize the component with different state as well as with stubs for any dependency injection.

Store Tests

Store tests exist to facilitate testing the behavior of a component at the next layer down from a Widget Test. This is not something you normally see but is something that you do if you do TDD. The difference is that Flutter Forge formalizes the interface and provides the TestStore tool, making it consistent and easier to do.

The following is an example of a simple store test.

void main() {
  group('Counter Store', () {
    test('when sent CounterWidgetIncrementButtonTapped action it increments the count',
        () async {
      final store = TestStore(
          initialState: const CounterState(count: 0),
          reducer: counterReducer,
          environment: CounterWidgetEnvironment());

      // Send action into the store while collection a list of all the
      // actions and their associated resulting states. This can be more
      // than just the action that you send in as some actions have effects
      // that after being processed send an action back into the store.
      final results = await store.send(CounterWidgetIncrementButtonTapped());
      expect(results.length, 1);
      expect(results[0].action is CounterWidgetIncrementButtonTapped, true);
      expect(results[0].state, const CounterWidgetState(count: 1));

In the above example you can see that we construct a store using, TestStore and then send the ConterWidgetIncrementButtonTapped action to the store. It responds by processing the action and sending us back results. results is an Array of objects that contain a pair of action and state snapshots. This enables you to verify that the actions triggered by effects are being processed in the correct order and that the state snapshots are correct at each step.