├── .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 | [![pub package](https://img.shields.io/pub/v/image_test_utils.svg)](https://pub.dartlang.org/packages/image_test_utils) 4 | [![Build Status](https://travis-ci.org/roughike/image_test_utils.svg?branch=master)](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 | --------------------------------------------------------------------------------