├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── example
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── app
│ │ └── src
│ │ │ └── main
│ │ │ └── java
│ │ │ └── io
│ │ │ └── flutter
│ │ │ └── plugins
│ │ │ └── GeneratedPluginRegistrant.java
│ └── local.properties
├── example.iml
├── ios
│ ├── Flutter
│ │ └── Generated.xcconfig
│ └── Runner
│ │ ├── GeneratedPluginRegistrant.h
│ │ └── GeneratedPluginRegistrant.m
├── pubspec.yaml
└── test
│ └── widget_test.dart
├── lib
└── image_test_utils.dart
└── pubspec.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Files and directories created by pub
2 | .dart_tool/
3 | .packages
4 | # Remove the following pattern if you wish to check in your lock file
5 | pubspec.lock
6 |
7 | # Conventional directory for build outputs
8 | build/
9 |
10 | # Directory created by dartdoc
11 | doc/api/
12 |
13 | .idea/
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - linux
3 | sudo: false
4 | addons:
5 | apt:
6 | sources:
7 | - ubuntu-toolchain-r-test
8 | packages:
9 | - libstdc++6
10 | - fonts-droid
11 |
12 | before_script:
13 | - git clone https://github.com/flutter/flutter.git -b master --depth 1
14 | - ./flutter/bin/flutter doctor
15 |
16 | script:
17 | - cd example && ../flutter/bin/flutter test
18 |
19 | cache:
20 | directories:
21 | - $HOME/.pub-cache
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0.0
2 |
3 | - Initial version
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2018 Iiro Krankka
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # image_test_utils
2 |
3 | [](https://pub.dartlang.org/packages/image_test_utils)
4 | [](https://travis-ci.org/roughike/image_test_utils)
5 |
6 | Without providing mocked responses, any widget test that pumps up `Image.network` widgets will crash.
7 |
8 | [There's a blog post that goes more into detail on this.](https://iirokrankka.com/2018/09/16/image-network-widget-tests/)
9 |
10 | Copy-pasting [the code for mocking the image responses](https://github.com/flutter/flutter/blob/master/dev/manual_tests/test/mock_image_http.dart) to every new project gets a little boring. This helper library makes it easier to provide those mocked image responses.
11 |
12 | ## Usage
13 |
14 | First, depend on the library:
15 |
16 | **pubspec.yaml**
17 |
18 | ```yaml
19 | dev_dependencies:
20 | image_test_utils: ^1.0.0
21 | ```
22 |
23 | Note that this library should be included in your `dev_dependencies` block; **not in your regular `dependencies`**.
24 |
25 | In your widget tests, import the library and wrap your widget test in a `provideMockedNetworkImages` method.
26 |
27 | ```dart
28 | import 'package:flutter/material.dart';
29 | import 'package:flutter_test/flutter_test.dart';
30 | import 'package:image_test_utils/image_test_utils.dart';
31 |
32 | void main() {
33 | testWidgets('should not crash', (WidgetTester tester) async {
34 | provideMockedNetworkImages(() async {
35 | /// Now we can pump NetworkImages without crashing our tests. Yay!
36 | await tester.pumpWidget(
37 | MaterialApp(
38 | home: Image.network('https://example.com/image.png'),
39 | ),
40 | );
41 |
42 | /// Other test code goes here.
43 | });
44 | });
45 | }
46 | ```
47 |
48 | All HTTP GET requests inside the closure of `provideMockedNetworkImages` will receive a mocked image response, and your tests will not crash with 404's anymore.
49 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | # exclude:
3 | # - path/to/excluded/files/**
4 |
5 | # Lint rules and documentation, see http://dart-lang.github.io/linter/lints
6 | linter:
7 | rules:
8 | - cancel_subscriptions
9 | - hash_and_equals
10 | - iterable_contains_unrelated_type
11 | - list_remove_unrelated_type
12 | - test_types_in_equals
13 | - unrelated_type_equality_checks
14 | - valid_regexps
15 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 |
9 | .flutter-plugins
10 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 9299c02cf708497d6f72edda8efae0bb8340660e
8 | channel: beta
9 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | A new Flutter application.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java:
--------------------------------------------------------------------------------
1 | package io.flutter.plugins;
2 |
3 | import io.flutter.plugin.common.PluginRegistry;
4 |
5 | /**
6 | * Generated file. Do not edit.
7 | */
8 | public final class GeneratedPluginRegistrant {
9 | public static void registerWith(PluginRegistry registry) {
10 | if (alreadyRegisteredWith(registry)) {
11 | return;
12 | }
13 | }
14 |
15 | private static boolean alreadyRegisteredWith(PluginRegistry registry) {
16 | final String key = GeneratedPluginRegistrant.class.getCanonicalName();
17 | if (registry.hasPlugin(key)) {
18 | return true;
19 | }
20 | registry.registrarFor(key);
21 | return false;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/android/local.properties:
--------------------------------------------------------------------------------
1 | sdk.dir=/Users/ironman/Library/Android/sdk
2 | flutter.sdk=/Users/ironman/flutter
3 | flutter.versionName=1.0.0
4 | flutter.versionCode=1
--------------------------------------------------------------------------------
/example/example.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Generated.xcconfig:
--------------------------------------------------------------------------------
1 | // This is a generated file; do not edit or check into version control.
2 | FLUTTER_ROOT=/Users/ironman/flutter
3 | FLUTTER_APPLICATION_PATH=/Users/ironman/test_http_overrides/example
4 | FLUTTER_TARGET=lib/main.dart
5 | FLUTTER_BUILD_MODE=debug
6 | FLUTTER_BUILD_DIR=build
7 | SYMROOT=${SOURCE_ROOT}/../build/ios
8 | FLUTTER_FRAMEWORK_DIR=/Users/ironman/flutter/bin/cache/artifacts/engine/ios
9 | FLUTTER_BUILD_NAME=1.0.0
10 | FLUTTER_BUILD_NUMBER=1
11 | PREVIEW_DART_2=true
12 |
--------------------------------------------------------------------------------
/example/ios/Runner/GeneratedPluginRegistrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | #ifndef GeneratedPluginRegistrant_h
6 | #define GeneratedPluginRegistrant_h
7 |
8 | #import
9 |
10 | @interface GeneratedPluginRegistrant : NSObject
11 | + (void)registerWithRegistry:(NSObject*)registry;
12 | @end
13 |
14 | #endif /* GeneratedPluginRegistrant_h */
15 |
--------------------------------------------------------------------------------
/example/ios/Runner/GeneratedPluginRegistrant.m:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | #import "GeneratedPluginRegistrant.h"
6 |
7 | @implementation GeneratedPluginRegistrant
8 |
9 | + (void)registerWithRegistry:(NSObject*)registry {
10 | }
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: A new Flutter application.
3 | version: 1.0.0+1
4 |
5 | environment:
6 | sdk: ">=2.0.0-dev.68.0 <3.0.0"
7 |
8 | dependencies:
9 | flutter:
10 | sdk: flutter
11 |
12 | dev_dependencies:
13 | flutter_test:
14 | sdk: flutter
15 | image_test_utils:
16 | path: ../
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:image_test_utils/image_test_utils.dart';
4 |
5 | void main() {
6 | testWidgets('should not crash', (WidgetTester tester) async {
7 | provideMockedNetworkImages(() async {
8 | await tester.pumpWidget(
9 | MaterialApp(
10 | home: Image.network('https://example.com/image.png'),
11 | ),
12 | );
13 | });
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/lib/image_test_utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 |
4 | import 'package:mockito/mockito.dart';
5 |
6 | /// Runs [body] in a fresh [Zone] that provides mocked responses for [Image.network] widgets.
7 | ///
8 | /// Behind the scenes, this creates a mocked HTTP client that responds with mocked
9 | /// image data to all HTTP GET requests.
10 | ///
11 | /// This is a workaround needed for widget tests that use network images. Without
12 | /// the mocked HTTP client, any widget tests that pump a widget tree containing
13 | /// [Image.network] widgets will crash.
14 | ///
15 | /// By default, the mocked HTTP client will respond with [_transparentImage]. If
16 | /// provided, it will use [imageBytes] instead.
17 | ///
18 | /// Example usage in a test case:
19 | ///
20 | /// ```
21 | /// void main() {
22 | /// testWidgets('should not crash', (WidgetTester tester) async {
23 | /// provideMockedNetworkImages(() async {
24 | /// await tester.pumpWidget(
25 | /// MaterialApp(
26 | /// home: Image.network('https://example.com/image.png'),
27 | /// ),
28 | /// );
29 | /// });
30 | /// });
31 | /// }
32 | /// ```
33 | ///
34 | /// Note that you'll want to add this package to the dev_dependencies instead of
35 | /// the regular dependencies block on your pubspec.yaml.
36 | ///
37 | /// For more context about [Image.network] widgets failing in widget tests, see
38 | /// these issues:
39 | ///
40 | /// * https://github.com/flutter/flutter/issues/13433
41 | /// * https://github.com/flutter/flutter_markdown/pull/17
42 | ///
43 | /// The underlying code is taken from the Flutter repo:
44 | /// https://github.com/flutter/flutter/blob/master/dev/manual_tests/test/mock_image_http.dart
45 | R provideMockedNetworkImages(R body(), {List imageBytes = _transparentImage}) {
46 | return HttpOverrides.runZoned(
47 | body,
48 | createHttpClient: (_) => _createMockImageHttpClient(_, imageBytes),
49 | );
50 | }
51 |
52 | class MockHttpClient extends Mock implements HttpClient {}
53 | class MockHttpClientRequest extends Mock implements HttpClientRequest {}
54 | class MockHttpClientResponse extends Mock implements HttpClientResponse {}
55 | class MockHttpHeaders extends Mock implements HttpHeaders {}
56 |
57 | // Returns a mock HTTP client that responds with an image to all requests.
58 | MockHttpClient _createMockImageHttpClient(SecurityContext _, List imageBytes) {
59 | final MockHttpClient client = MockHttpClient();
60 | final MockHttpClientRequest request = MockHttpClientRequest();
61 | final MockHttpClientResponse response = MockHttpClientResponse();
62 | final MockHttpHeaders headers = MockHttpHeaders();
63 |
64 | when(client.getUrl(any)).thenAnswer((_) => Future.value(request));
65 | when(request.headers).thenReturn(headers);
66 | when(request.close()).thenAnswer((_) => Future.value(response));
67 | when(response.contentLength).thenReturn(_transparentImage.length);
68 | when(response.statusCode).thenReturn(HttpStatus.ok);
69 | when(response.listen(any)).thenAnswer((Invocation invocation) {
70 | final void Function(List) onData = invocation.positionalArguments[0];
71 | final void Function() onDone = invocation.namedArguments[#onDone];
72 | final void Function(Object, [StackTrace]) onError = invocation.namedArguments[#onError];
73 | final bool cancelOnError = invocation.namedArguments[#cancelOnError];
74 |
75 | return Stream>.fromIterable(>[imageBytes])
76 | .listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError);
77 | });
78 |
79 | return client;
80 | }
81 |
82 | const List _transparentImage = const [
83 | 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49,
84 | 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,
85 | 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44,
86 | 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D,
87 | 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
88 | ];
89 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: image_test_utils
2 | description: Makes it easier to provide mocked responses for Image.network widgets in Flutter widget tests.
3 | version: 1.0.0
4 | homepage: https://github.com/roughike/image_test_utils
5 | author: Iiro Krankka
6 |
7 | environment:
8 | sdk: '>=2.0.0 <3.0.0'
9 |
10 | dependencies:
11 | mockito: ^3.0.0
12 |
--------------------------------------------------------------------------------