テストtips - TetsuFe/state_notifier_sample6 GitHub Wiki

StateNotifier.stateはprotectedなのですが、debugStateというプロパティもあり、テストをするときにはこれを利用します。

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_state_management/story_state_notifier.dart';
import 'package:flutter_state_management/story.dart';

void main() {
  test('StoryStateNotifier test', () {
    final story = Story(
        id: 1,
        title: 'テスト',
        summary: 'テストです',
        thumbnailImagePath: 'assets/story/1/hokuma.jpg',
        isRead: false);
    final storyStateNotifier = StoryStateNotifier(story);
    storyStateNotifier.markAsRead();
    expect(
      storyStateNotifier.debugState,
      story.copyWith(isRead: true),
    );
  });
}

参考サイト

Widgetの取得

CheckBoxの値を取得するなどの際に、Widgetが欲しい時がある

参考: https://github.com/flutter/flutter/blob/master/packages/flutter/test/material/checkbox_test.dart

expect(tester.widget<Checkbox>(find.byType(Checkbox)).value, false);

Navigator

oush, pop

遷移したかどうかのテスト

https://stackoverflow.com/questions/50704647/how-to-test-navigation-via-navigator-in-flutter

画像

Image.networkはHttpClientがモックであるため、400や404になるらしい

参考: https://qiita.com/kasa_le/items/9ab139f11bce1ce24235

pubspec.yaml

dev_dependencies:
  image_test_utils: ^1.0.0

widget_test.dart

import 'package:image_test_utils/image_test_utils.dart';

void main() {
  testWidgets('image.network test',
      (WidgetTester tester) async {
    provideMockedNetworkImages(() async {
      // 普通にテストできる

ProviderとMockitoを使ったテスト

import 'package:flutter_state_management/user_question_chat/models/user_question_chat_api.dart';
import 'package:flutter_state_management/user_question_chat/widgets/user_question_chat.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';

class MockUserQuestionChatApi extends Mock implements UserQuestionChatApi {}

void main() {
  testWidgets('chat list displaying widget test', (tester) async {
    tester.pumpWidget(
      # ここでテストしたいコードに出てくる型を指定
      Provider<UserQuestionChatApi>(
        child: MessageListView(),
        # 実際に注入するのは Mock
        create: (context) => MockUserQuestionChatApi(), 
      ),
    );
  });
}

textDirection関係のwarning

https://stackoverflow.com/questions/53676526/textdirection-null-assert-is-not-null-listtile

Firebaseのモックテスト

https://github.com/flutter/flutter/issues/15245#issuecomment-537675925

/// A fake implementation of AuthServices
class FakeAuthService implements AuthService {
  Stream<User> get currentUser => Stream.fromIterable([User.fake()]);
}

完成系

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_state_management/user_question_chat/models/message.dart';
import 'package:flutter_state_management/user_question_chat/models/user_question_chat_api.dart';
import 'package:flutter_state_management/user_question_chat/widgets/user_question_chat.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';

class MockUserQuestionChatApi extends Mock implements UserQuestionChatApi {
  MockUserQuestionChatApi({this.allMessageSnapshotData});

  final List<Message> allMessageSnapshotData;

  @override
  Stream<List<Message>> allMessageSnapshot() =>
      Stream.value(allMessageSnapshotData);
}

void main() {
  testWidgets('chat list displaying widget test', (tester) async {
    final message = Message(
      username: 'testuser',
      body: 'こんにちは',
      createdDate: DateTime(2020, 1, 1),
    );
    await tester.pumpWidget(
      MaterialApp(
          home: Provider<UserQuestionChatApi>(
        child: Scaffold(body: Column(children: [MessageListView()])),
        create: (context) =>
            MockUserQuestionChatApi(allMessageSnapshotData: [message]),
      )),
    );
    await tester.pump(Duration.zero);
    expect(find.byType(MessageListView), findsOneWidget);
    expect(find.byType(ListView), findsOneWidget);
    expect(find.byType(MessageBubble), findsOneWidget);
    expect(find.text(message.username), findsOneWidget);
    expect(find.text(message.body), findsOneWidget);
    expect(find.text(message.createdDate.toIso8601String()), findsOneWidget);
  });
}

Streamを待つ

https://github.com/flutter/flutter/blob/4d7525f05c05a6df0b29396bc9eb78c3bf1e9f89/packages/flutter/test/widgets/async_test.dart#L332

Future eventFiring(WidgetTester tester) async { await tester.pump(Duration.zero); }

testWidgets('StreamBuilder', (WidgetTester tester) async { await tester.pumpWidget(StreamBuilder( stream: Stream.fromIterable(['hello', 'world']), builder: snapshotText, )); await eventFiring(tester); });

// tester.pump(Duration.zero);
// tester.pumpAndSettle();

の場合、

00:02 +0: /Users/tetsu/flworks/flutter_novel/test/chat_test.dart: chat list displaying widget test
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure object was thrown running a test: Expected: exactly one matching node in the widget tree Actual: _WidgetTypeFinder:<zero widgets with type "ListView" (ignoring offstage widgets)> Which: means none were found but one was expected

tester.pump(Duration.zero);

Assertion failed: file:///private/var/folders/_z/ktrjffx13y5fygdgd6hs1h6w0000gn/T/scratch_spaceURK5zR/packages/flutter_test/src/test_async_utils.dart:312:14 lineMatch != null is not true

tester.pumpAndSettle();の場合

Assertion failed: file:///private/var/folders/_z/ktrjffx13y5fygdgd6hs1h6w0000gn/T/scratch_spaceURK5zR/packages/flutter_test/src/test_async_utils.dart:312:14 lineMatch != null is not true

tester.pumpAndSettle(Duration.zero);の場合

Assertion failed: file:///private/var/folders/_z/ktrjffx13y5fygdgd6hs1h6w0000gn/T/scratch_spaceURK5zR/packages/flutter_test/src/widget_tester.dart:543:12 duration > Duration.zero is not true

await tester.pump(Duration.zero);

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure object was thrown running a test: Expected: exactly one matching node in the widget tree Actual: _WidgetTypeFinder:<zero widgets with type "MessageBubble" (ignoring offstage widgets)> Which: means none were found but one was expected

問題

StreamBuilderのListViewの中身が表示されない

class MessageListView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: Provider.of<UserQuestionChatApi>(context, listen: false)
          .allMessageSnapshot(),
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return const Text('エラーが発生しています');
        }
        if (!snapshot.hasData) {
          return const Center(child: CircularProgressIndicator());
        }
        final messages = snapshot.data as List<Message>;
        print('********************');
        print(messages[0]);
        return ListView.builder(
            reverse: true,
            itemCount: messages.length,
            shrinkWrap: true,
            primary: false,
            itemBuilder: (context, int index) {
              return MessageBubble(message: messages[index]);
            });
      },
    );
  }
}

00:02 +0: /Users/tetsu/flworks/flutter_novel/test/chat_test.dart: chat list displaying widget test


Message(username: testuser, body: こんにちは, createdDate: 2020-01-01 00:00:00.000)

どうやら、とれてはいるものの表示される前に評価されている?あるいは、表示されていない

Directionallyを追加

ListViewの要素が見えていないだけで、ドラッグすればいいのではないか説 https://stackoverflow.com/questions/54651878/how-to-find-off-screen-listview-child-in-widget-tests

https://github.com/flutter/flutter/blob/master/packages/flutter/test/widgets/list_view_test.dart#L94

    await tester.pumpWidget(
      Provider<UserQuestionChatApi>(
        child: Directionality(
            textDirection: TextDirection.ltr, child: MessageListView()),
        create: (context) =>
            MockUserQuestionChatApi(allMessageSnapshotData: [message]),
      ),
    );

════════════════════════════════════════════════════════════════════════════════════════════════════ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure object was thrown running a test: Expected: exactly one matching node in the widget tree Actual: _TextFinder:<zero widgets with text "testuser" (ignoring offstage widgets)> Which: means none were found but one was expected

00:01 +0: loading /Users/tetsu/flworks/flutter_novel/test/chat_test.dart                                                                                                                               ERROR - 2020-06-25 12:10:24.491160
GET /packages/ui/assets/Roboto-Regular.ttf
Error thrown by handler.
NoSuchMethodError: The method 'toFilePath' was called on null.
Receiver: null
Tried calling: toFilePath()
dart:core                                                        Object.noSuchMethod
package:flutter_tools/src/test/flutter_web_platform.dart 225:48  FlutterWebPlatform._packageFilesHandler
===== asynchronous gap ===========================
package:shelf/shelf_io.dart 63:34                                serveRequests.<fn>.<fn>
===== asynchronous gap ===========================
package:shelf/src/io_server.dart 53:5                            IOServer.mount
package:flutter_tools/src/test/flutter_web_platform.dart 74:13   new FlutterWebPlatform._
package:flutter_tools/src/test/flutter_web_platform.dart 90:31   FlutterWebPlatform.start
dart:async                                                       _completeOnAsyncReturn
package:http_multi_server/http_multi_server.dart                 HttpMultiServer._loopback
===== asynchronous gap ===========================
dart:async                                                       _asyncThenWrapperHelper
package:flutter_tools/src/test/runner.dart 142:37                _FlutterTestRunnerImpl.runTests.<fn>
dart:async                                                       new Future.sync
package:async/src/async_memoizer.dart 43:45                      AsyncMemoizer.runOnce
package:test_core/src/runner/loader.dart 230:28                  Loader.loadFile.<fn>
package:test_core/src/runner/load_suite.dart 98:31               new LoadSuite.<fn>.<fn>
package:test_core/src/runner/load_suite.dart 108:8               new LoadSuite.<fn>
package:test_api/src/backend/invoker.dart 400:30                 Invoker._onRun.<fn>.<fn>.<fn>.<fn>
===== asynchronous gap ===========================
dart:async                                                       new Future
package:test_api/src/backend/invoker.dart 399:21                 Invoker._onRun.<fn>.<fn>.<fn>
dart:async                                                       runZoned
package:test_api/src/backend/invoker.dart 387:9                  Invoker._onRun.<fn>.<fn>
dart:async                                                       runZoned
package:test_api/src/backend/invoker.dart 148:7                  Invoker.guard
package:test_api/src/backend/invoker.dart 436:15                 Invoker._guardIfGuarded
package:test_api/src/backend/invoker.dart 386:7                  Invoker._onRun.<fn>
package:stack_trace                                              Chain.capture
package:test_api/src/backend/invoker.dart 385:11                 Invoker._onRun
package:test_api/src/backend/live_test_controller.dart 152:11    LiveTestController.run

00:03 +1: loading /Users/tetsu/flworks/flutter_novel/test/story_test.dart                                                                                                                              ERROR - 2020-06-25 12:10:26.675708
GET /packages/ui/assets/Roboto-Regular.ttf
Error thrown by handler.
NoSuchMethodError: The method 'toFilePath' was called on null.
Receiver: null
Tried calling: toFilePath()
dart:core                                                        Object.noSuchMethod
package:flutter_tools/src/test/flutter_web_platform.dart 225:48  FlutterWebPlatform._packageFilesHandler
===== asynchronous gap ===========================
package:shelf/shelf_io.dart 63:34                                serveRequests.<fn>.<fn>
===== asynchronous gap ===========================
package:shelf/src/io_server.dart 53:5                            IOServer.mount
package:flutter_tools/src/test/flutter_web_platform.dart 74:13   new FlutterWebPlatform._
package:flutter_tools/src/test/flutter_web_platform.dart 90:31   FlutterWebPlatform.start
dart:async                                                       _completeOnAsyncReturn
package:http_multi_server/http_multi_server.dart                 HttpMultiServer._loopback
===== asynchronous gap ===========================
dart:async                                                       _asyncThenWrapperHelper
package:flutter_tools/src/test/runner.dart 142:37                _FlutterTestRunnerImpl.runTests.<fn>
dart:async                                                       new Future.sync
package:async/src/async_memoizer.dart 43:45                      AsyncMemoizer.runOnce
package:test_core/src/runner/loader.dart 230:28                  Loader.loadFile.<fn>
package:test_core/src/runner/load_suite.dart 98:31               new LoadSuite.<fn>.<fn>
package:test_core/src/runner/load_suite.dart 108:8               new LoadSuite.<fn>
package:test_api/src/backend/invoker.dart 400:30                 Invoker._onRun.<fn>.<fn>.<fn>.<fn>
===== asynchronous gap ===========================
dart:async                                                       new Future
package:test_api/src/backend/invoker.dart 399:21                 Invoker._onRun.<fn>.<fn>.<fn>
dart:async                                                       runZoned
package:test_api/src/backend/invoker.dart 387:9                  Invoker._onRun.<fn>.<fn>
dart:async                                                       runZoned
package:test_api/src/backend/invoker.dart 148:7                  Invoker.guard
package:test_api/src/backend/invoker.dart 436:15                 Invoker._guardIfGuarded
package:test_api/src/backend/invoker.dart 386:7                  Invoker._onRun.<fn>
package:stack_trace                                              Chain.capture
package:test_api/src/backend/invoker.dart 385:11                 Invoker._onRun
package:test_api/src/backend/live_test_controller.dart 152:11    LiveTestController.run

00:04 +2: All tests passed!    
⚠️ **GitHub.com Fallback** ⚠️