Skip to content

micromuseum.cc: testing flutter components

In the best tradition of TDD I’m going to write components along with component tests for them. Flutter provides a decent testing framework (the flutter_test library ) allowing to write unit, component and integration tests.

Here some details about different kind of tests:

  • Unit tests: don’t expose flutter, and are meant to test business logic. They use test/test.dart Rather than flutter_test.
  • Component (widget) tests: these test are based on flutter’s WidgetTester, a nifty class that allows to “pump” events to the widget you are testing as well as to look for children in the widget hierarchy
  • Integration tests: with these tests you start an "Instrumented" version of the App that allows to plug different sensors while running the app, measure performance and replicate the actual app working environment.

While there aren't currently unit tests, we can see an example of a component test:

Testing DotsIndicator component

Note

The following DotsIndicator component is adapted from one found on github: https://gist.github.com/collinjackson/4fddbfa2830ea3ac033e34622f278824

I later found out that there's also a component on Pub: https://pub.dartlang.org/packages/dots_indicator

This is the component code:

Component

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

/// An indicator showing the currently selected page of a PageController
class DotsIndicator extends AnimatedWidget {
  DotsIndicator({
    this.controller,
    this.itemCount,
    this.onPageSelected,
    this.color: Colors.black,
  }) : super(listenable: controller);

  /// The PageController that this DotsIndicator is representing.
  final PageController controller;

  /// The number of items managed by the PageController
  final int itemCount;

  /// Called when a dot is tapped
  final ValueChanged<int> onPageSelected;

  /// The color of the dots.
  ///
  /// Defaults to `Colors.white`.
  final Color color;

  // The base size of the dots
  static const double _kDotSize = 8.0;

  // The increase in the size of the selected dot
  static const double _kMaxZoom = 1.5;

  // The distance between the center of each dot
  static const double _kDotSpacing = 25.0;

  Widget _buildDot(int index) {
    double page = (controller.page != null) ? controller.page % itemCount : 0;
    double selectedness = Curves.easeOut.transform(
      max(
        0.0,
        1.0 - ((page ?? controller.initialPage) - index).abs(),
      ),
    );
    double zoom = 1.0 + (_kMaxZoom - 1.0) * selectedness;
    return new Container(
      width: _kDotSpacing,
      child: new Center(
        child: new Material(
          color: zoom > 1.1 ? color : color.withOpacity(0.1),
          type: MaterialType.circle,
          child: new Container(
            width: _kDotSize * zoom,
            height: _kDotSize * zoom,
            child: new InkWell(
              onTap: () => onPageSelected(index),
            ),
          ),
        ),
      ),
    );
  }

  Widget build(BuildContext context) {
    return new Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: new List<Widget>.generate(itemCount, _buildDot),
    );
  }
}

The widget builds a number of dots and applies a zoom effect to the selected dot. When a dot is clicked a callback is invoked.

And the following is the component test:

Component Test

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mm_mobile/components/dotsindicator.dart';
import 'package:mockito/mockito.dart';


class MockPageController extends Mock implements PageController {}

void main() {
  testWidgets("Correct number of dots is displayed",  (WidgetTester tester) async {

    final MockPageController ctrl = new MockPageController();

    final int itemCount = 10;

    when(ctrl.page).thenReturn(0);
    when(ctrl.initialPage).thenReturn(0);
    DotsIndicator dots = new DotsIndicator(controller: ctrl, itemCount: itemCount, onPageSelected: (int i) {});

    await tester.pumpWidget(MaterialApp(home:dots));

    expect(find.byType(Material), findsNWidgets(itemCount));

  });

    testWidgets("Clicking on a dots shows the correct page",  (WidgetTester tester) async {

    final MockPageController ctrl = new MockPageController();

    final int itemCount = 10;

    int selected = -1;

    when(ctrl.page).thenReturn(0);
    when(ctrl.initialPage).thenReturn(0);
    DotsIndicator dots = new DotsIndicator(controller: ctrl, itemCount: itemCount, onPageSelected: (int i) {
      selected = i;
    });

    await tester.pumpWidget(MaterialApp(home:dots));

    await(tester.tap(find.byType(Material).first));


     expect(selected, equals(0));q
    // expect(find.byType(Material), findsNWidgets(itemCount));
  await(tester.tap(find.byType(Material).last));
    expect(selected, equals(9));

  });
}

Comments

  • PageController won't work unless placed on the screen, so we replace it with a MockPageController provided by the mockito Dart Package.
  • We test just the DotsIndicator so we make sure it shows the correct number of dots and when clicked an event is fired with the correct id.
  • Something missing is a test to check the color is correct, and the zoom effect is applied to the dot.