Fix silent test failure in image cache tests (#56492)

diff --git a/packages/flutter/lib/src/painting/image_cache.dart b/packages/flutter/lib/src/painting/image_cache.dart
index 24d894f..dbdf7b0 100644
--- a/packages/flutter/lib/src/painting/image_cache.dart
+++ b/packages/flutter/lib/src/painting/image_cache.dart
@@ -286,10 +286,9 @@
     }
   }
 
-  void _trackLiveImage(Object key, _LiveImage image, { bool debugPutOk = true }) {
+  void _trackLiveImage(Object key, _LiveImage image) {
     // Avoid adding unnecessary callbacks to the completer.
     _liveImages.putIfAbsent(key, () {
-      assert(debugPutOk);
       // Even if no callers to ImageProvider.resolve have listened to the stream,
       // the cache is listening to the stream and will remove itself once the
       // image completes to move it from pending to keepAlive.
@@ -400,10 +399,6 @@
           imageSize,
           () => _liveImages.remove(key),
         ),
-        // This should result in a put if `loader()` above executed
-        // synchronously, in which case syncCall is true and we arrived here
-        // before we got a chance to track the image otherwise.
-        debugPutOk: syncCall,
       );
 
       final _PendingImage pendingImage = untrackedPendingImage ?? _pendingImages.remove(key);
diff --git a/packages/flutter/lib/src/painting/image_provider.dart b/packages/flutter/lib/src/painting/image_provider.dart
index e75a582..c6ffb7f 100644
--- a/packages/flutter/lib/src/painting/image_provider.dart
+++ b/packages/flutter/lib/src/painting/image_provider.dart
@@ -347,10 +347,10 @@
           stack: stack,
           context: ErrorDescription('while resolving an image'),
           silent: true, // could be a network error or whatnot
-          informationCollector: collector
-          );
-        },
-      );
+          informationCollector: collector,
+        );
+      },
+    );
     return stream;
   }
 
diff --git a/packages/flutter/test/painting/image_cache_clearing_test.dart b/packages/flutter/test/painting/image_cache_clearing_test.dart
new file mode 100644
index 0000000..beb913b
--- /dev/null
+++ b/packages/flutter/test/painting/image_cache_clearing_test.dart
@@ -0,0 +1,31 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+
+void main() {
+  TestRenderingFlutterBinding();
+
+  test('Clearing images while they\'re pending does not crash', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage memoryImage = MemoryImage(bytes);
+    final ImageStream stream = memoryImage.resolve(ImageConfiguration.empty);
+    final Completer<void> completer = Completer<void>();
+    FlutterError.onError = (FlutterErrorDetails error) { completer.completeError(error.exception, error.stack); };
+    stream.addListener(ImageStreamListener(
+      (ImageInfo image, bool synchronousCall) {
+        completer.complete();
+      }
+    ));
+    imageCache.clearLiveImages();
+    await completer.future;
+  });
+}
diff --git a/packages/flutter/test/painting/image_cache_resize_test.dart b/packages/flutter/test/painting/image_cache_resize_test.dart
index eab9291..867ff4c 100644
--- a/packages/flutter/test/painting/image_cache_resize_test.dart
+++ b/packages/flutter/test/painting/image_cache_resize_test.dart
@@ -9,71 +9,70 @@
 import 'mocks_for_image_cache.dart';
 
 void main() {
-  TestRenderingFlutterBinding(); // initializes the imageCache
-  group(ImageCache, () {
-    tearDown(() {
-      imageCache.clear();
-      imageCache.maximumSize = 1000;
-      imageCache.maximumSizeBytes = 10485760;
-    });
+  TestRenderingFlutterBinding();
 
-    test('Image cache resizing based on count', () async {
-      imageCache.maximumSize = 2;
+  tearDown(() {
+    imageCache.clear();
+    imageCache.maximumSize = 1000;
+    imageCache.maximumSizeBytes = 10485760;
+  });
 
-      final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(a.value, equals(1));
-      expect(b.value, equals(2));
-      expect(c.value, equals(3));
-      expect(d.value, equals(4));
+  test('Image cache resizing based on count', () async {
+    imageCache.maximumSize = 2;
 
-      imageCache.maximumSize = 0;
+    final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(a.value, equals(1));
+    expect(b.value, equals(2));
+    expect(c.value, equals(3));
+    expect(d.value, equals(4));
 
-      final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(e.value, equals(5));
+    imageCache.maximumSize = 0;
 
-      final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(f.value, equals(6));
+    final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(e.value, equals(5));
 
-      imageCache.maximumSize = 3;
+    final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(f.value, equals(6));
 
-      final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(g.value, equals(7));
+    imageCache.maximumSize = 3;
 
-      final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(h.value, equals(7));
-    });
+    final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(g.value, equals(7));
 
-    test('Image cache resizing based on size', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8); // 256 B.
-      imageCache.maximumSizeBytes = 256 * 2;
+    final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(h.value, equals(7));
+  });
 
-      final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(a.value, equals(1));
-      expect(b.value, equals(2));
-      expect(c.value, equals(3));
-      expect(d.value, equals(4));
+  test('Image cache resizing based on size', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8); // 256 B.
+    imageCache.maximumSizeBytes = 256 * 2;
 
-      imageCache.maximumSizeBytes = 0;
+    final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(a.value, equals(1));
+    expect(b.value, equals(2));
+    expect(c.value, equals(3));
+    expect(d.value, equals(4));
 
-      final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(e.value, equals(5));
+    imageCache.maximumSizeBytes = 0;
 
-      final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(f.value, equals(6));
+    final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(e.value, equals(5));
 
-      imageCache.maximumSizeBytes = 256 * 3;
+    final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(f.value, equals(6));
 
-      final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(g.value, equals(7));
+    imageCache.maximumSizeBytes = 256 * 3;
 
-      final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(h.value, equals(7));
-    });
+    final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(g.value, equals(7));
+
+    final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8, image: testImage).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(h.value, equals(7));
   });
 }
diff --git a/packages/flutter/test/painting/image_cache_test.dart b/packages/flutter/test/painting/image_cache_test.dart
index ad72ade..1ddba9b 100644
--- a/packages/flutter/test/painting/image_cache_test.dart
+++ b/packages/flutter/test/painting/image_cache_test.dart
@@ -9,475 +9,471 @@
 import 'mocks_for_image_cache.dart';
 
 void main() {
-  group('ImageCache', () {
-    setUpAll(() {
-      TestRenderingFlutterBinding(); // initializes the imageCache
+  TestRenderingFlutterBinding();
+
+  tearDown(() {
+    imageCache.clear();
+    imageCache.clearLiveImages();
+    imageCache.maximumSize = 1000;
+    imageCache.maximumSizeBytes = 10485760;
+  });
+
+  test('maintains cache size', () async {
+    imageCache.maximumSize = 3;
+
+    final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(a.value, equals(1));
+    final TestImageInfo b = await extractOneFrame(const TestImageProvider(1, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(b.value, equals(1));
+    final TestImageInfo c = await extractOneFrame(const TestImageProvider(1, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(c.value, equals(1));
+    final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(d.value, equals(1));
+    final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(e.value, equals(1));
+    final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(f.value, equals(1));
+
+    expect(f, equals(a));
+
+    // cache still only has one entry in it: 1(1)
+
+    final TestImageInfo g = await extractOneFrame(const TestImageProvider(2, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(g.value, equals(7));
+
+    // cache has two entries in it: 1(1), 2(7)
+
+    final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(h.value, equals(1));
+
+    // cache still has two entries in it: 2(7), 1(1)
+
+    final TestImageInfo i = await extractOneFrame(const TestImageProvider(3, 9).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(i.value, equals(9));
+
+    // cache has three entries in it: 2(7), 1(1), 3(9)
+
+    final TestImageInfo j = await extractOneFrame(const TestImageProvider(1, 10).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(j.value, equals(1));
+
+    // cache still has three entries in it: 2(7), 3(9), 1(1)
+
+    final TestImageInfo k = await extractOneFrame(const TestImageProvider(4, 11).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(k.value, equals(11));
+
+    // cache has three entries: 3(9), 1(1), 4(11)
+
+    final TestImageInfo l = await extractOneFrame(const TestImageProvider(1, 12).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(l.value, equals(1));
+
+    // cache has three entries: 3(9), 4(11), 1(1)
+
+    final TestImageInfo m = await extractOneFrame(const TestImageProvider(2, 13).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(m.value, equals(13));
+
+    // cache has three entries: 4(11), 1(1), 2(13)
+
+    final TestImageInfo n = await extractOneFrame(const TestImageProvider(3, 14).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(n.value, equals(14));
+
+    // cache has three entries: 1(1), 2(13), 3(14)
+
+    final TestImageInfo o = await extractOneFrame(const TestImageProvider(4, 15).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(o.value, equals(15));
+
+    // cache has three entries: 2(13), 3(14), 4(15)
+
+    final TestImageInfo p = await extractOneFrame(const TestImageProvider(1, 16).resolve(ImageConfiguration.empty)) as TestImageInfo;
+    expect(p.value, equals(16));
+
+    // cache has three entries: 3(14), 4(15), 1(16)
+  });
+
+  test('clear removes all images and resets cache size', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
+
+    expect(imageCache.currentSize, 0);
+    expect(imageCache.currentSizeBytes, 0);
+
+    await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
+    await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+
+    expect(imageCache.currentSize, 2);
+    expect(imageCache.currentSizeBytes, 256 * 2);
+
+    imageCache.clear();
+
+    expect(imageCache.currentSize, 0);
+    expect(imageCache.currentSizeBytes, 0);
+  });
+
+  test('evicts individual images', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
+    await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
+    await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+
+    expect(imageCache.currentSize, 2);
+    expect(imageCache.currentSizeBytes, 256 * 2);
+    expect(imageCache.evict(1), true);
+    expect(imageCache.currentSize, 1);
+    expect(imageCache.currentSizeBytes, 256);
+  });
+
+  test('Do not cache large images', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
+
+    imageCache.maximumSizeBytes = 1;
+    await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
+    expect(imageCache.currentSize, 0);
+    expect(imageCache.currentSizeBytes, 0);
+    expect(imageCache.maximumSizeBytes, 1);
+  });
+
+  test('Returns null if an error is caught resolving an image', () {
+    final ErrorImageProvider errorImage = ErrorImageProvider();
+    expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null)), throwsA(isA<Error>()));
+    bool caughtError = false;
+    final ImageStreamCompleter result = imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null), onError: (dynamic error, StackTrace stackTrace) {
+      caughtError = true;
     });
+    expect(result, null);
+    expect(caughtError, true);
+  });
 
-    tearDown(() {
-      imageCache.clear();
-      imageCache.clearLiveImages();
-      imageCache.maximumSize = 1000;
-      imageCache.maximumSizeBytes = 10485760;
-    });
+  test('already pending image is returned when it is put into the cache again', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-    test('maintains cache size', () async {
-      imageCache.maximumSize = 3;
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
+    final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
 
-      final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(a.value, equals(1));
-      final TestImageInfo b = await extractOneFrame(const TestImageProvider(1, 2).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(b.value, equals(1));
-      final TestImageInfo c = await extractOneFrame(const TestImageProvider(1, 3).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(c.value, equals(1));
-      final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(d.value, equals(1));
-      final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(e.value, equals(1));
-      final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(f.value, equals(1));
+    final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+      return completer1;
+    }) as TestImageStreamCompleter;
+    final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
+      return completer2;
+    }) as TestImageStreamCompleter;
 
-      expect(f, equals(a));
+    expect(resultingCompleter1, completer1);
+    expect(resultingCompleter2, completer1);
+  });
 
-      // cache still only has one entry in it: 1(1)
+  test('pending image is removed when cache is cleared', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageInfo g = await extractOneFrame(const TestImageProvider(2, 7).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(g.value, equals(7));
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
+    final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
 
-      // cache has two entries in it: 1(1), 2(7)
+    final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+      return completer1;
+    }) as TestImageStreamCompleter;
 
-      final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(h.value, equals(1));
+    expect(imageCache.statusForKey(testImage).pending, true);
+    expect(imageCache.statusForKey(testImage).live, true);
+    imageCache.clear();
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    imageCache.clearLiveImages();
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, false);
 
-      // cache still has two entries in it: 2(7), 1(1)
+    final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
+      return completer2;
+    }) as TestImageStreamCompleter;
 
-      final TestImageInfo i = await extractOneFrame(const TestImageProvider(3, 9).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(i.value, equals(9));
+    expect(resultingCompleter1, completer1);
+    expect(resultingCompleter2, completer2);
+  });
 
-      // cache has three entries in it: 2(7), 1(1), 3(9)
+  test('pending image is removed when image is evicted', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageInfo j = await extractOneFrame(const TestImageProvider(1, 10).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(j.value, equals(1));
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
+    final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
 
-      // cache still has three entries in it: 2(7), 3(9), 1(1)
+    final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+      return completer1;
+    }) as TestImageStreamCompleter;
 
-      final TestImageInfo k = await extractOneFrame(const TestImageProvider(4, 11).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(k.value, equals(11));
+    imageCache.evict(testImage);
 
-      // cache has three entries: 3(9), 1(1), 4(11)
+    final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
+      return completer2;
+    }) as TestImageStreamCompleter;
 
-      final TestImageInfo l = await extractOneFrame(const TestImageProvider(1, 12).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(l.value, equals(1));
+    expect(resultingCompleter1, completer1);
+    expect(resultingCompleter2, completer2);
+  });
 
-      // cache has three entries: 3(9), 4(11), 1(1)
+  test("failed image can successfully be removed from the cache's pending images", () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageInfo m = await extractOneFrame(const TestImageProvider(2, 13).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(m.value, equals(13));
+    const FailingTestImageProvider(1, 1, image: testImage)
+        .resolve(ImageConfiguration.empty)
+        .addListener(ImageStreamListener(
+          (ImageInfo image, bool synchronousCall) { },
+          onError: (dynamic exception, StackTrace stackTrace) {
+            final bool evicationResult = imageCache.evict(1);
+            expect(evicationResult, isTrue);
+          },
+        ));
+  });
 
-      // cache has three entries: 4(11), 1(1), 2(13)
+  test('containsKey - pending', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageInfo n = await extractOneFrame(const TestImageProvider(3, 14).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(n.value, equals(14));
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
 
-      // cache has three entries: 1(1), 2(13), 3(14)
+    final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+      return completer1;
+    }) as TestImageStreamCompleter;
 
-      final TestImageInfo o = await extractOneFrame(const TestImageProvider(4, 15).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(o.value, equals(15));
+    expect(resultingCompleter1, completer1);
+    expect(imageCache.containsKey(testImage), true);
+  });
 
-      // cache has three entries: 2(13), 3(14), 4(15)
+  test('containsKey - completed', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageInfo p = await extractOneFrame(const TestImageProvider(1, 16).resolve(ImageConfiguration.empty)) as TestImageInfo;
-      expect(p.value, equals(16));
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
 
-      // cache has three entries: 3(14), 4(15), 1(16)
-    });
+    final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+      return completer1;
+    }) as TestImageStreamCompleter;
 
-    test('clear removes all images and resets cache size', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+    // Mark as complete
+    completer1.testSetImage(testImage);
 
-      expect(imageCache.currentSize, 0);
-      expect(imageCache.currentSizeBytes, 0);
+    expect(resultingCompleter1, completer1);
+    expect(imageCache.containsKey(testImage), true);
+  });
 
-      await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
-      await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+  test('putIfAbsent updates LRU properties of a live image', () async {
+    imageCache.maximumSize = 1;
+    const TestImage testImage = TestImage(width: 8, height: 8);
+    const TestImage testImage2 = TestImage(width: 10, height: 10);
 
-      expect(imageCache.currentSize, 2);
-      expect(imageCache.currentSizeBytes, 256 * 2);
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()..testSetImage(testImage);
+    final TestImageStreamCompleter completer2 = TestImageStreamCompleter()..testSetImage(testImage2);
 
-      imageCache.clear();
+    completer1.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
 
-      expect(imageCache.currentSize, 0);
-      expect(imageCache.currentSizeBytes, 0);
-    });
+    final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
+      return completer1;
+    }) as TestImageStreamCompleter;
 
-    test('evicts individual images', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
-      await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
-      await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage2).untracked, true);
+    final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage2, () {
+      return completer2;
+    }) as TestImageStreamCompleter;
 
-      expect(imageCache.currentSize, 2);
-      expect(imageCache.currentSizeBytes, 256 * 2);
-      expect(imageCache.evict(1), true);
-      expect(imageCache.currentSize, 1);
-      expect(imageCache.currentSizeBytes, 256);
-    });
 
-    test('Do not cache large images', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).keepAlive, false); // evicted
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage2).pending, false);
+    expect(imageCache.statusForKey(testImage2).keepAlive, true); // took the LRU spot.
+    expect(imageCache.statusForKey(testImage2).live, false); // no listeners
 
-      imageCache.maximumSizeBytes = 1;
-      await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
-      expect(imageCache.currentSize, 0);
-      expect(imageCache.currentSizeBytes, 0);
-      expect(imageCache.maximumSizeBytes, 1);
-    });
+    expect(resultingCompleter1, completer1);
+    expect(resultingCompleter2, completer2);
+  });
 
-    test('Returns null if an error is caught resolving an image', () {
-      final ErrorImageProvider errorImage = ErrorImageProvider();
-      expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null)), throwsA(isA<Error>()));
-      bool caughtError = false;
-      final ImageStreamCompleter result = imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null), onError: (dynamic error, StackTrace stackTrace) {
-        caughtError = true;
-      });
-      expect(result, null);
-      expect(caughtError, true);
-    });
+  test('Live image cache avoids leaks of unlistened streams', () async {
+    imageCache.maximumSize = 3;
 
-    test('already pending image is returned when it is put into the cache again', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+    const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
+    const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
+    const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
+    const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
+    const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
+    const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
 
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-      final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
+    // wait an event loop to let image resolution process.
+    await null;
 
-      final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
-        return completer1;
-      }) as TestImageStreamCompleter;
-      final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
-        return completer2;
-      }) as TestImageStreamCompleter;
+    expect(imageCache.currentSize, 3);
+    expect(imageCache.liveImageCount, 0);
+  });
 
-      expect(resultingCompleter1, completer1);
-      expect(resultingCompleter2, completer1);
-    });
+  test('Disabled image cache does not leak live images', () async {
+    imageCache.maximumSize = 0;
 
-    test('pending image is removed when cache is cleared', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+    const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
+    const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
+    const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
+    const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
+    const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
+    const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
 
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-      final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
+    // wait an event loop to let image resolution process.
+    await null;
 
-      final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
-        return completer1;
-      }) as TestImageStreamCompleter;
+    expect(imageCache.currentSize, 0);
+    expect(imageCache.liveImageCount, 0);
+  });
 
-      expect(imageCache.statusForKey(testImage).pending, true);
-      expect(imageCache.statusForKey(testImage).live, true);
-      imageCache.clear();
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      imageCache.clearLiveImages();
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, false);
+  test('Evicting a pending image clears the live image by default', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
-        return completer2;
-      }) as TestImageStreamCompleter;
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
 
-      expect(resultingCompleter1, completer1);
-      expect(resultingCompleter2, completer2);
-    });
+    imageCache.putIfAbsent(testImage, () => completer1);
+    expect(imageCache.statusForKey(testImage).pending, true);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
 
-    test('pending image is removed when image is evicted', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+    imageCache.evict(testImage);
+    expect(imageCache.statusForKey(testImage).untracked, true);
+  });
 
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-      final TestImageStreamCompleter completer2 = TestImageStreamCompleter();
+  test('Evicting a pending image does clear the live image when includeLive is false and only cache listening', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
-        return completer1;
-      }) as TestImageStreamCompleter;
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
 
-      imageCache.evict(testImage);
+    imageCache.putIfAbsent(testImage, () => completer1);
+    expect(imageCache.statusForKey(testImage).pending, true);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
 
-      final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage, () {
-        return completer2;
-      }) as TestImageStreamCompleter;
+    imageCache.evict(testImage, includeLive: false);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, false);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
+  });
 
-      expect(resultingCompleter1, completer1);
-      expect(resultingCompleter2, completer2);
-    });
+  test('Evicting a pending image does clear the live image when includeLive is false and some other listener', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-    test("failed image can successfully be removed from the cache's pending images", () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
 
-      const FailingTestImageProvider(1, 1, image: testImage)
-          .resolve(ImageConfiguration.empty)
-          .addListener(ImageStreamListener(
-            (ImageInfo image, bool synchronousCall) { },
-            onError: (dynamic exception, StackTrace stackTrace) {
-              final bool evicationResult = imageCache.evict(1);
-              expect(evicationResult, isTrue);
-            },
-          ));
-    });
+    imageCache.putIfAbsent(testImage, () => completer1);
+    expect(imageCache.statusForKey(testImage).pending, true);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
 
-    test('containsKey - pending', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
-
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
-      final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
-        return completer1;
-      }) as TestImageStreamCompleter;
-
-      expect(resultingCompleter1, completer1);
-      expect(imageCache.containsKey(testImage), true);
-    });
-
-    test('containsKey - completed', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
-
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
-      final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
-        return completer1;
-      }) as TestImageStreamCompleter;
-
-      // Mark as complete
-      completer1.testSetImage(testImage);
-
-      expect(resultingCompleter1, completer1);
-      expect(imageCache.containsKey(testImage), true);
-    });
-
-    test('putIfAbsent updates LRU properties of a live image', () async {
-      imageCache.maximumSize = 1;
-      const TestImage testImage = TestImage(width: 8, height: 8);
-      const TestImage testImage2 = TestImage(width: 10, height: 10);
-
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter()..testSetImage(testImage);
-      final TestImageStreamCompleter completer2 = TestImageStreamCompleter()..testSetImage(testImage2);
-
-      completer1.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
-
-      final TestImageStreamCompleter resultingCompleter1 = imageCache.putIfAbsent(testImage, () {
-        return completer1;
-      }) as TestImageStreamCompleter;
-
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage2).untracked, true);
-      final TestImageStreamCompleter resultingCompleter2 = imageCache.putIfAbsent(testImage2, () {
-        return completer2;
-      }) as TestImageStreamCompleter;
-
-
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).keepAlive, false); // evicted
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage2).pending, false);
-      expect(imageCache.statusForKey(testImage2).keepAlive, true); // took the LRU spot.
-      expect(imageCache.statusForKey(testImage2).live, false); // no listeners
-
-      expect(resultingCompleter1, completer1);
-      expect(resultingCompleter2, completer2);
-    });
-
-    test('Live image cache avoids leaks of unlistened streams', () async {
-      imageCache.maximumSize = 3;
-
-      const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
-      const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
-      const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
-      const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
-      const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
-      const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
-
-      // wait an event loop to let image resolution process.
-      await null;
-
-      expect(imageCache.currentSize, 3);
-      expect(imageCache.liveImageCount, 0);
-    });
-
-    test('Disabled image cache does not leak live images', () async {
-      imageCache.maximumSize = 0;
-
-      const TestImageProvider(1, 1).resolve(ImageConfiguration.empty);
-      const TestImageProvider(2, 2).resolve(ImageConfiguration.empty);
-      const TestImageProvider(3, 3).resolve(ImageConfiguration.empty);
-      const TestImageProvider(4, 4).resolve(ImageConfiguration.empty);
-      const TestImageProvider(5, 5).resolve(ImageConfiguration.empty);
-      const TestImageProvider(6, 6).resolve(ImageConfiguration.empty);
-
-      // wait an event loop to let image resolution process.
-      await null;
-
-      expect(imageCache.currentSize, 0);
-      expect(imageCache.liveImageCount, 0);
-    });
-
-    test('Evicting a pending image clears the live image by default', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
-
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
-      imageCache.putIfAbsent(testImage, () => completer1);
-      expect(imageCache.statusForKey(testImage).pending, true);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-
-      imageCache.evict(testImage);
-      expect(imageCache.statusForKey(testImage).untracked, true);
-    });
-
-    test('Evicting a pending image does clear the live image when includeLive is false and only cache listening', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
-
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
-      imageCache.putIfAbsent(testImage, () => completer1);
-      expect(imageCache.statusForKey(testImage).pending, true);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-
-      imageCache.evict(testImage, includeLive: false);
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, false);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-    });
-
-    test('Evicting a pending image does clear the live image when includeLive is false and some other listener', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
-
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
-
-      imageCache.putIfAbsent(testImage, () => completer1);
-      expect(imageCache.statusForKey(testImage).pending, true);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-
-      completer1.addListener(ImageStreamListener((_, __) {}));
-      imageCache.evict(testImage, includeLive: false);
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-    });
+    completer1.addListener(ImageStreamListener((_, __) {}));
+    imageCache.evict(testImage, includeLive: false);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
+  });
 
-    test('Evicting a completed image does clear the live image by default', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+  test('Evicting a completed image does clear the live image by default', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
-        ..testSetImage(testImage)
-        ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+      ..testSetImage(testImage)
+      ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
 
-      imageCache.putIfAbsent(testImage, () => completer1);
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
+    imageCache.putIfAbsent(testImage, () => completer1);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
 
-      imageCache.evict(testImage);
-      expect(imageCache.statusForKey(testImage).untracked, true);
-    });
+    imageCache.evict(testImage);
+    expect(imageCache.statusForKey(testImage).untracked, true);
+  });
 
-    test('Evicting a completed image does not clear the live image when includeLive is set to false', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+  test('Evicting a completed image does not clear the live image when includeLive is set to false', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
-        ..testSetImage(testImage)
-        ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+      ..testSetImage(testImage)
+      ..addListener(ImageStreamListener((ImageInfo info, bool syncCall) {}));
 
-      imageCache.putIfAbsent(testImage, () => completer1);
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
+    imageCache.putIfAbsent(testImage, () => completer1);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
 
-      imageCache.evict(testImage, includeLive: false);
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-    });
+    imageCache.evict(testImage, includeLive: false);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
+  });
 
-    test('Clearing liveImages removes callbacks', () async {
-      const TestImage testImage = TestImage(width: 8, height: 8);
+  test('Clearing liveImages removes callbacks', () async {
+    const TestImage testImage = TestImage(width: 8, height: 8);
 
-      final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
+    final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
 
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
-        ..testSetImage(testImage)
-        ..addListener(listener);
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+      ..testSetImage(testImage)
+      ..addListener(listener);
 
-      final TestImageStreamCompleter completer2 = TestImageStreamCompleter()
-        ..testSetImage(testImage)
-        ..addListener(listener);
+    final TestImageStreamCompleter completer2 = TestImageStreamCompleter()
+      ..testSetImage(testImage)
+      ..addListener(listener);
 
-      imageCache.putIfAbsent(testImage, () => completer1);
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
+    imageCache.putIfAbsent(testImage, () => completer1);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
 
-      imageCache.clear();
-      imageCache.clearLiveImages();
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, false);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
+    imageCache.clear();
+    imageCache.clearLiveImages();
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, false);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
 
-      imageCache.putIfAbsent(testImage, () => completer2);
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
+    imageCache.putIfAbsent(testImage, () => completer2);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
 
-      completer1.removeListener(listener);
+    completer1.removeListener(listener);
 
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
-    });
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
+  });
 
-    test('Live image gets size updated', () async {
-      // Add an image to the cache in pending state
-      // Complete it once it is in there as live
-      // Evict it but leave the live one.
-      // Add it again.
-      // If the live image did not track the size properly, the last line of
-      // this test will fail.
+  test('Live image gets size updated', () async {
+    // Add an image to the cache in pending state
+    // Complete it once it is in there as live
+    // Evict it but leave the live one.
+    // Add it again.
+    // If the live image did not track the size properly, the last line of
+    // this test will fail.
 
-      const TestImage testImage = TestImage(width: 8, height: 8);
-      const int testImageSize = 8 * 8 * 4;
+    const TestImage testImage = TestImage(width: 8, height: 8);
+    const int testImageSize = 8 * 8 * 4;
 
-      final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
+    final ImageStreamListener listener = ImageStreamListener((ImageInfo info, bool syncCall) {});
 
-      final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
-        ..addListener(listener);
+    final TestImageStreamCompleter completer1 = TestImageStreamCompleter()
+      ..addListener(listener);
 
 
-      imageCache.putIfAbsent(testImage, () => completer1);
-      expect(imageCache.statusForKey(testImage).pending, true);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-      expect(imageCache.currentSizeBytes, 0);
+    imageCache.putIfAbsent(testImage, () => completer1);
+    expect(imageCache.statusForKey(testImage).pending, true);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
+    expect(imageCache.currentSizeBytes, 0);
 
-      completer1.testSetImage(testImage);
+    completer1.testSetImage(testImage);
 
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
-      expect(imageCache.currentSizeBytes, testImageSize);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
+    expect(imageCache.currentSizeBytes, testImageSize);
 
-      imageCache.evict(testImage, includeLive: false);
+    imageCache.evict(testImage, includeLive: false);
 
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, false);
-      expect(imageCache.currentSizeBytes, 0);
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, false);
+    expect(imageCache.currentSizeBytes, 0);
 
-      imageCache.putIfAbsent(testImage, () => completer1);
+    imageCache.putIfAbsent(testImage, () => completer1);
 
-      expect(imageCache.statusForKey(testImage).pending, false);
-      expect(imageCache.statusForKey(testImage).live, true);
-      expect(imageCache.statusForKey(testImage).keepAlive, true);
-      expect(imageCache.currentSizeBytes, testImageSize);
-    });
+    expect(imageCache.statusForKey(testImage).pending, false);
+    expect(imageCache.statusForKey(testImage).live, true);
+    expect(imageCache.statusForKey(testImage).keepAlive, true);
+    expect(imageCache.currentSizeBytes, testImageSize);
   });
 }
diff --git a/packages/flutter/test/painting/image_provider_and_image_cache_test.dart b/packages/flutter/test/painting/image_provider_and_image_cache_test.dart
new file mode 100644
index 0000000..53724c6
--- /dev/null
+++ b/packages/flutter/test/painting/image_provider_and_image_cache_test.dart
@@ -0,0 +1,142 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+import 'mocks_for_image_cache.dart';
+
+void main() {
+  TestRenderingFlutterBinding();
+
+  final DecoderCallback _basicDecoder = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
+    return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+  };
+
+  FlutterExceptionHandler oldError;
+  setUp(() {
+    oldError = FlutterError.onError;
+  });
+
+  tearDown(() {
+    FlutterError.onError = oldError;
+    PaintingBinding.instance.imageCache.clear();
+    PaintingBinding.instance.imageCache.clearLiveImages();
+  });
+
+  tearDown(() {
+    imageCache.clear();
+  });
+
+  test('AssetImageProvider - evicts on failure to load', () async {
+    final Completer<FlutterError> error = Completer<FlutterError>();
+    FlutterError.onError = (FlutterErrorDetails details) {
+      error.complete(details.exception as FlutterError);
+    };
+
+    const ImageProvider provider = ExactAssetImage('does-not-exist');
+    final Object key = await provider.obtainKey(ImageConfiguration.empty);
+    expect(imageCache.statusForKey(provider).untracked, true);
+    expect(imageCache.pendingImageCount, 0);
+
+    provider.resolve(ImageConfiguration.empty);
+
+    expect(imageCache.statusForKey(key).pending, true);
+    expect(imageCache.pendingImageCount, 1);
+
+    await error.future;
+
+    expect(imageCache.statusForKey(provider).untracked, true);
+    expect(imageCache.pendingImageCount, 0);
+  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56314
+
+  test('AssetImageProvider - evicts on null load', () async {
+    final Completer<StateError> error = Completer<StateError>();
+    FlutterError.onError = (FlutterErrorDetails details) {
+      error.complete(details.exception as StateError);
+    };
+
+    final ImageProvider provider = ExactAssetImage('does-not-exist', bundle: _TestAssetBundle());
+    final Object key = await provider.obtainKey(ImageConfiguration.empty);
+    expect(imageCache.statusForKey(provider).untracked, true);
+    expect(imageCache.pendingImageCount, 0);
+
+    provider.resolve(ImageConfiguration.empty);
+
+    expect(imageCache.statusForKey(key).pending, true);
+    expect(imageCache.pendingImageCount, 1);
+
+    await error.future;
+
+    expect(imageCache.statusForKey(provider).untracked, true);
+    expect(imageCache.pendingImageCount, 0);
+  });
+
+  test('ImageProvider can evict images', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage imageProvider = MemoryImage(bytes);
+    final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+    final Completer<void> completer = Completer<void>();
+    stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) => completer.complete()));
+    await completer.future;
+
+    expect(imageCache.currentSize, 1);
+    expect(await MemoryImage(bytes).evict(), true);
+    expect(imageCache.currentSize, 0);
+  });
+
+  test('ImageProvider.evict respects the provided ImageCache', () async {
+    final ImageCache otherCache = ImageCache();
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage imageProvider = MemoryImage(bytes);
+    final ImageStreamCompleter cacheStream = otherCache.putIfAbsent(
+      imageProvider, () => imageProvider.load(imageProvider, _basicDecoder),
+    );
+    final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+    final Completer<void> completer = Completer<void>();
+    final Completer<void> cacheCompleter = Completer<void>();
+    stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+      completer.complete();
+    }));
+    cacheStream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+      cacheCompleter.complete();
+    }));
+    await Future.wait(<Future<void>>[completer.future, cacheCompleter.future]);
+
+    expect(otherCache.currentSize, 1);
+    expect(imageCache.currentSize, 1);
+    expect(await imageProvider.evict(cache: otherCache), true);
+    expect(otherCache.currentSize, 0);
+    expect(imageCache.currentSize, 1);
+  });
+
+  test('ImageProvider errors can always be caught', () async {
+    final ErrorImageProvider imageProvider = ErrorImageProvider();
+    final Completer<bool> caughtError = Completer<bool>();
+    FlutterError.onError = (FlutterErrorDetails details) {
+      caughtError.complete(false);
+    };
+    final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+    stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+      caughtError.complete(false);
+    }, onError: (dynamic error, StackTrace stackTrace) {
+      caughtError.complete(true);
+    }));
+    expect(await caughtError.future, true);
+  });
+}
+
+class _TestAssetBundle extends CachingAssetBundle {
+  @override
+  Future<ByteData> load(String key) async {
+    return null;
+  }
+}
diff --git a/packages/flutter/test/painting/image_provider_network_image_test.dart b/packages/flutter/test/painting/image_provider_network_image_test.dart
new file mode 100644
index 0000000..3fda11b
--- /dev/null
+++ b/packages/flutter/test/painting/image_provider_network_image_test.dart
@@ -0,0 +1,224 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:math' as math;
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+
+void main() {
+  TestRenderingFlutterBinding();
+
+  final DecoderCallback _basicDecoder = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
+    return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+  };
+
+  _MockHttpClient httpClient;
+
+  setUp(() {
+    httpClient = _MockHttpClient();
+    debugNetworkImageHttpClientProvider = () => httpClient;
+  });
+
+  tearDown(() {
+    debugNetworkImageHttpClientProvider = null;
+    PaintingBinding.instance.imageCache.clear();
+    PaintingBinding.instance.imageCache.clearLiveImages();
+  });
+
+  test('Expect thrown exception with statusCode - evicts from cache', () async {
+    final int errorStatusCode = HttpStatus.notFound;
+    const String requestUrl = 'foo-url';
+
+    final _MockHttpClientRequest request = _MockHttpClientRequest();
+    final _MockHttpClientResponse response = _MockHttpClientResponse();
+    when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
+    when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
+    when(response.statusCode).thenReturn(errorStatusCode);
+
+    final Completer<dynamic> caughtError = Completer<dynamic>();
+
+    final ImageProvider imageProvider = NetworkImage(nonconst(requestUrl));
+    expect(imageCache.pendingImageCount, 0);
+    expect(imageCache.statusForKey(imageProvider).untracked, true);
+
+    final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+
+    expect(imageCache.pendingImageCount, 1);
+    expect(imageCache.statusForKey(imageProvider).pending, true);
+
+    result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+    }, onError: (dynamic error, StackTrace stackTrace) {
+      caughtError.complete(error);
+    }));
+
+    final dynamic err = await caughtError.future;
+
+    expect(imageCache.pendingImageCount, 0);
+    expect(imageCache.statusForKey(imageProvider).untracked, true);
+
+    expect(
+      err,
+      isA<NetworkImageLoadException>()
+        .having((NetworkImageLoadException e) => e.statusCode, 'statusCode', errorStatusCode)
+        .having((NetworkImageLoadException e) => e.uri, 'uri', Uri.base.resolve(requestUrl)),
+    );
+  }, skip: isBrowser); // Browser implementation does not use HTTP client but an <img> tag.
+
+  test('Disallows null urls', () {
+    expect(() {
+      NetworkImage(nonconst(null));
+    }, throwsAssertionError);
+  });
+
+  test('Uses the HttpClient provided by debugNetworkImageHttpClientProvider if set', () async {
+    when(httpClient.getUrl(any)).thenThrow('client1');
+    final List<dynamic> capturedErrors = <dynamic>[];
+
+    Future<void> loadNetworkImage() async {
+      final NetworkImage networkImage = NetworkImage(nonconst('foo'));
+      final ImageStreamCompleter completer = networkImage.load(networkImage, _basicDecoder);
+      completer.addListener(ImageStreamListener(
+        (ImageInfo image, bool synchronousCall) { },
+        onError: (dynamic error, StackTrace stackTrace) {
+          capturedErrors.add(error);
+        },
+      ));
+      await Future<void>.value();
+    }
+
+    await loadNetworkImage();
+    expect(capturedErrors, <dynamic>['client1']);
+    final _MockHttpClient client2 = _MockHttpClient();
+    when(client2.getUrl(any)).thenThrow('client2');
+    debugNetworkImageHttpClientProvider = () => client2;
+    await loadNetworkImage();
+    expect(capturedErrors, <dynamic>['client1', 'client2']);
+  }, skip: isBrowser); // Browser implementation does not use HTTP client but an <img> tag.
+
+  test('Propagates http client errors during resolve()', () async {
+    when(httpClient.getUrl(any)).thenThrow(Error());
+    bool uncaught = false;
+
+    final FlutterExceptionHandler oldError = FlutterError.onError;
+    await runZoned(() async {
+      const ImageProvider imageProvider = NetworkImage('asdasdasdas');
+      final Completer<bool> caughtError = Completer<bool>();
+      FlutterError.onError = (FlutterErrorDetails details) {
+        throw Error();
+      };
+      final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+      result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+      }, onError: (dynamic error, StackTrace stackTrace) {
+        caughtError.complete(true);
+      }));
+      expect(await caughtError.future, true);
+    }, zoneSpecification: ZoneSpecification(
+      handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
+        uncaught = true;
+      },
+    ));
+    expect(uncaught, false);
+    FlutterError.onError = oldError;
+  });
+
+  test('Notifies listeners of chunk events', () async {
+    const int chunkSize = 8;
+    final List<Uint8List> chunks = <Uint8List>[
+      for (int offset = 0; offset < kTransparentImage.length; offset += chunkSize)
+        Uint8List.fromList(kTransparentImage.skip(offset).take(chunkSize).toList()),
+    ];
+    final Completer<void> imageAvailable = Completer<void>();
+    final _MockHttpClientRequest request = _MockHttpClientRequest();
+    final _MockHttpClientResponse response = _MockHttpClientResponse();
+    when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
+    when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
+    when(response.statusCode).thenReturn(HttpStatus.ok);
+    when(response.contentLength).thenReturn(kTransparentImage.length);
+    when(response.listen(
+      any,
+      onDone: anyNamed('onDone'),
+      onError: anyNamed('onError'),
+      cancelOnError: anyNamed('cancelOnError'),
+    )).thenAnswer((Invocation invocation) {
+      final void Function(List<int>) onData = invocation.positionalArguments[0] as void Function(List<int>);
+      final void Function(Object) onError = invocation.namedArguments[#onError] as void Function(Object);
+      final VoidCallback onDone = invocation.namedArguments[#onDone] as VoidCallback;
+      final bool cancelOnError = invocation.namedArguments[#cancelOnError] as bool;
+
+      return Stream<Uint8List>.fromIterable(chunks).listen(
+        onData,
+        onDone: onDone,
+        onError: onError,
+        cancelOnError: cancelOnError,
+      );
+    });
+
+    final ImageProvider imageProvider = NetworkImage(nonconst('foo'));
+    final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+    final List<ImageChunkEvent> events = <ImageChunkEvent>[];
+    result.addListener(ImageStreamListener(
+      (ImageInfo image, bool synchronousCall) {
+        imageAvailable.complete();
+      },
+      onChunk: (ImageChunkEvent event) {
+        events.add(event);
+      },
+      onError: (dynamic error, StackTrace stackTrace) {
+        imageAvailable.completeError(error, stackTrace);
+      },
+    ));
+    await imageAvailable.future;
+    expect(events.length, chunks.length);
+    for (int i = 0; i < events.length; i++) {
+      expect(events[i].cumulativeBytesLoaded, math.min((i + 1) * chunkSize, kTransparentImage.length));
+      expect(events[i].expectedTotalBytes, kTransparentImage.length);
+    }
+  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56317
+
+  test('NetworkImage is evicted from cache on SocketException', () async {
+    final _MockHttpClient mockHttpClient = _MockHttpClient();
+    when(mockHttpClient.getUrl(any)).thenAnswer((_) => throw const SocketException('test exception'));
+    debugNetworkImageHttpClientProvider = () => mockHttpClient;
+
+
+    final ImageProvider imageProvider = NetworkImage(nonconst('testing.url'));
+    expect(imageCache.pendingImageCount, 0);
+    expect(imageCache.statusForKey(imageProvider).untracked, true);
+
+    final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+
+    expect(imageCache.pendingImageCount, 1);
+    expect(imageCache.statusForKey(imageProvider).pending, true);
+    final Completer<dynamic> caughtError = Completer<dynamic>();
+    result.addListener(ImageStreamListener(
+      (ImageInfo info, bool syncCall) {},
+      onError: (dynamic error, StackTrace stackTrace) {
+        caughtError.complete(error);
+      },
+    ));
+
+    final dynamic err = await caughtError.future;
+
+    expect(err, isA<SocketException>());
+
+    expect(imageCache.pendingImageCount, 0);
+    expect(imageCache.statusForKey(imageProvider).untracked, true);
+    expect(imageCache.containsKey(result), isFalse);
+
+    debugNetworkImageHttpClientProvider = null;
+  }, skip: isBrowser); // Browser does not resolve images this way.
+}
+
+class _MockHttpClient extends Mock implements HttpClient {}
+class _MockHttpClientRequest extends Mock implements HttpClientRequest {}
+class _MockHttpClientResponse extends Mock implements HttpClientResponse {}
diff --git a/packages/flutter/test/painting/image_provider_resize_image_test.dart b/packages/flutter/test/painting/image_provider_resize_image_test.dart
new file mode 100644
index 0000000..6f8d268
--- /dev/null
+++ b/packages/flutter/test/painting/image_provider_resize_image_test.dart
@@ -0,0 +1,134 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../rendering/rendering_tester.dart';
+import 'image_data.dart';
+
+void main() {
+  TestRenderingFlutterBinding();
+
+  tearDown(() {
+    PaintingBinding.instance.imageCache.clear();
+    PaintingBinding.instance.imageCache.clearLiveImages();
+  });
+
+  test('ResizeImage resizes to the correct dimensions', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage imageProvider = MemoryImage(bytes);
+    final Size rawImageSize = await _resolveAndGetSize(imageProvider);
+    expect(rawImageSize, const Size(1, 1));
+
+    const Size resizeDims = Size(14, 7);
+    final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round());
+    const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
+    final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
+    expect(resizedImageSize, resizeDims);
+  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56312
+
+  test('ResizeImage does not resize when no size is passed', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage imageProvider = MemoryImage(bytes);
+    final Size rawImageSize = await _resolveAndGetSize(imageProvider);
+    expect(rawImageSize, const Size(1, 1));
+
+    // Cannot pass in two null arguments for cache dimensions, so will use the regular
+    // MemoryImage
+    final MemoryImage resizedImage = MemoryImage(bytes);
+    final Size resizedImageSize = await _resolveAndGetSize(resizedImage);
+    expect(resizedImageSize, const Size(1, 1));
+  });
+
+  test('ResizeImage stores values', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage memoryImage = MemoryImage(bytes);
+    memoryImage.resolve(ImageConfiguration.empty);
+    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: 20);
+    expect(resizeImage.width, 10);
+    expect(resizeImage.height, 20);
+    expect(resizeImage.imageProvider, memoryImage);
+    expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
+  });
+
+  test('ResizeImage takes one dim', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage memoryImage = MemoryImage(bytes);
+    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: null);
+    expect(resizeImage.width, 10);
+    expect(resizeImage.height, null);
+    expect(resizeImage.imageProvider, memoryImage);
+    expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
+  });
+
+  test('ResizeImage forms closure', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage memoryImage = MemoryImage(bytes);
+    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+
+    final DecoderCallback decode = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
+      expect(cacheWidth, 123);
+      expect(cacheHeight, 321);
+      return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+    };
+
+    resizeImage.load(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
+  });
+
+  test('ResizeImage handles sync obtainKey', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final MemoryImage memoryImage = MemoryImage(bytes);
+    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+
+    bool isAsync = false;
+    resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
+      expect(isAsync, false);
+    });
+    isAsync = true;
+    expect(isAsync, true);
+  });
+
+  test('ResizeImage handles async obtainKey', () async {
+    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
+    final _AsyncKeyMemoryImage memoryImage = _AsyncKeyMemoryImage(bytes);
+    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+
+    bool isAsync = false;
+    resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
+      expect(isAsync, true);
+    });
+    isAsync = true;
+    expect(isAsync, true);
+  });
+}
+
+Future<Size> _resolveAndGetSize(ImageProvider imageProvider,
+    {ImageConfiguration configuration = ImageConfiguration.empty}) async {
+  final ImageStream stream = imageProvider.resolve(configuration);
+  final Completer<Size> completer = Completer<Size>();
+  final ImageStreamListener listener =
+    ImageStreamListener((ImageInfo image, bool synchronousCall) {
+      final int height = image.image.height;
+      final int width = image.image.width;
+      completer.complete(Size(width.toDouble(), height.toDouble()));
+    }
+  );
+  stream.addListener(listener);
+  return await completer.future;
+}
+
+// This version of MemoryImage guarantees obtainKey returns a future that has not been
+// completed synchronously.
+class _AsyncKeyMemoryImage extends MemoryImage {
+  const _AsyncKeyMemoryImage(Uint8List bytes) : super(bytes);
+
+  @override
+  Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
+    return Future<MemoryImage>(() => this);
+  }
+}
diff --git a/packages/flutter/test/painting/image_provider_test.dart b/packages/flutter/test/painting/image_provider_test.dart
index 34b5b97..a342d6a 100644
--- a/packages/flutter/test/painting/image_provider_test.dart
+++ b/packages/flutter/test/painting/image_provider_test.dart
@@ -4,29 +4,17 @@
 
 import 'dart:async';
 import 'dart:io';
-import 'dart:math' as math;
-import 'dart:typed_data';
 
 import 'package:file/memory.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/painting.dart';
-import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
 
 import '../rendering/rendering_tester.dart';
-import 'image_data.dart';
 import 'mocks_for_image_cache.dart';
 
 void main() {
-
-  final DecoderCallback basicDecoder = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
-    return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
-  };
-
-  setUpAll(() {
-    TestRenderingFlutterBinding(); // initializes the imageCache
-  });
+  TestRenderingFlutterBinding();
 
   FlutterExceptionHandler oldError;
   setUp(() {
@@ -39,485 +27,98 @@
     PaintingBinding.instance.imageCache.clearLiveImages();
   });
 
-  group('ImageProvider', () {
-    group('Image cache', () {
-      tearDown(() {
-        imageCache.clear();
-      });
+  test('obtainKey errors will be caught', () async {
+    final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+    final Completer<bool> caughtError = Completer<bool>();
+    FlutterError.onError = (FlutterErrorDetails details) {
+      caughtError.complete(false);
+    };
+    final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
+    stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+      caughtError.complete(false);
+    }, onError: (dynamic error, StackTrace stackTrace) {
+      caughtError.complete(true);
+    }));
+    expect(await caughtError.future, true);
+  });
 
-      test('AssetImageProvider - evicts on failure to load', () async {
-        final Completer<FlutterError> error = Completer<FlutterError>();
-        FlutterError.onError = (FlutterErrorDetails details) {
-          error.complete(details.exception as FlutterError);
-        };
+  test('obtainKey errors will be caught - check location', () async {
+    final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+    final Completer<bool> caughtError = Completer<bool>();
+    FlutterError.onError = (FlutterErrorDetails details) {
+      caughtError.complete(true);
+    };
+    await imageProvider.obtainCacheStatus(configuration: ImageConfiguration.empty);
 
-        const ImageProvider provider = ExactAssetImage('does-not-exist');
-        final Object key = await provider.obtainKey(ImageConfiguration.empty);
-        expect(imageCache.statusForKey(provider).untracked, true);
-        expect(imageCache.pendingImageCount, 0);
+    expect(await caughtError.future, true);
+  });
 
-        provider.resolve(ImageConfiguration.empty);
-
-        expect(imageCache.statusForKey(key).pending, true);
-        expect(imageCache.pendingImageCount, 1);
-
-        await error.future;
-
-        expect(imageCache.statusForKey(provider).untracked, true);
-        expect(imageCache.pendingImageCount, 0);
-      }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56314
-
-      test('AssetImageProvider - evicts on null load', () async {
-        final Completer<StateError> error = Completer<StateError>();
-        FlutterError.onError = (FlutterErrorDetails details) {
-          error.complete(details.exception as StateError);
-        };
-
-        final ImageProvider provider = ExactAssetImage('does-not-exist', bundle: TestAssetBundle());
-        final Object key = await provider.obtainKey(ImageConfiguration.empty);
-        expect(imageCache.statusForKey(provider).untracked, true);
-        expect(imageCache.pendingImageCount, 0);
-
-        provider.resolve(ImageConfiguration.empty);
-
-        expect(imageCache.statusForKey(key).pending, true);
-        expect(imageCache.pendingImageCount, 1);
-
-        await error.future;
-
-        expect(imageCache.statusForKey(provider).untracked, true);
-        expect(imageCache.pendingImageCount, 0);
-      });
-
-      test('ImageProvider can evict images', () async {
-        final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-        final MemoryImage imageProvider = MemoryImage(bytes);
-        final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
-        final Completer<void> completer = Completer<void>();
-        stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) => completer.complete()));
-        await completer.future;
-
-        expect(imageCache.currentSize, 1);
-        expect(await MemoryImage(bytes).evict(), true);
-        expect(imageCache.currentSize, 0);
-      });
-
-      test('ImageProvider.evict respects the provided ImageCache', () async {
-        final ImageCache otherCache = ImageCache();
-        final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-        final MemoryImage imageProvider = MemoryImage(bytes);
-        final ImageStreamCompleter cacheStream = otherCache.putIfAbsent(
-          imageProvider, () => imageProvider.load(imageProvider, basicDecoder),
-        );
-        final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
-        final Completer<void> completer = Completer<void>();
-        final Completer<void> cacheCompleter = Completer<void>();
-        stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-          completer.complete();
-        }));
-        cacheStream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-          cacheCompleter.complete();
-        }));
-        await Future.wait(<Future<void>>[completer.future, cacheCompleter.future]);
-
-        expect(otherCache.currentSize, 1);
-        expect(imageCache.currentSize, 1);
-        expect(await imageProvider.evict(cache: otherCache), true);
-        expect(otherCache.currentSize, 0);
-        expect(imageCache.currentSize, 1);
-      });
-
-      test('ImageProvider errors can always be caught', () async {
-        final ErrorImageProvider imageProvider = ErrorImageProvider();
-        final Completer<bool> caughtError = Completer<bool>();
-        FlutterError.onError = (FlutterErrorDetails details) {
-          caughtError.complete(false);
-        };
-        final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
-        stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-          caughtError.complete(false);
-        }, onError: (dynamic error, StackTrace stackTrace) {
-          caughtError.complete(true);
-        }));
-        expect(await caughtError.future, true);
-      });
-    });
-
-    test('obtainKey errors will be caught', () async {
-      final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+  test('resolve sync errors will be caught', () async {
+    bool uncaught = false;
+    final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
+      handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
+        uncaught = true;
+      },
+    ));
+    await testZone.run(() async {
+      final ImageProvider imageProvider = LoadErrorImageProvider();
       final Completer<bool> caughtError = Completer<bool>();
       FlutterError.onError = (FlutterErrorDetails details) {
-        caughtError.complete(false);
+        throw Error();
       };
-      final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
-      stream.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-        caughtError.complete(false);
+      final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+      result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
       }, onError: (dynamic error, StackTrace stackTrace) {
         caughtError.complete(true);
       }));
       expect(await caughtError.future, true);
     });
+    expect(uncaught, false);
+  });
 
-    test('obtainKey errors will be caught - check location', () async {
-      final ImageProvider imageProvider = ObtainKeyErrorImageProvider();
+  test('resolve errors in the completer will be caught', () async {
+    bool uncaught = false;
+    final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
+      handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
+        uncaught = true;
+      },
+    ));
+    await testZone.run(() async {
+      final ImageProvider imageProvider = LoadErrorCompleterImageProvider();
       final Completer<bool> caughtError = Completer<bool>();
       FlutterError.onError = (FlutterErrorDetails details) {
-        caughtError.complete(true);
+        throw Error();
       };
-      await imageProvider.obtainCacheStatus(configuration: ImageConfiguration.empty);
-
+      final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+      result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+      }, onError: (dynamic error, StackTrace stackTrace) {
+        caughtError.complete(true);
+      }));
       expect(await caughtError.future, true);
     });
-
-    test('resolve sync errors will be caught', () async {
-      bool uncaught = false;
-      final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
-        handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
-          uncaught = true;
-        },
-      ));
-      await testZone.run(() async {
-        final ImageProvider imageProvider = LoadErrorImageProvider();
-        final Completer<bool> caughtError = Completer<bool>();
-        FlutterError.onError = (FlutterErrorDetails details) {
-          throw Error();
-        };
-        final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-        result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-        }, onError: (dynamic error, StackTrace stackTrace) {
-          caughtError.complete(true);
-        }));
-        expect(await caughtError.future, true);
-      });
-      expect(uncaught, false);
-    });
-
-    test('resolve errors in the completer will be caught', () async {
-      bool uncaught = false;
-      final Zone testZone = Zone.current.fork(specification: ZoneSpecification(
-        handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
-          uncaught = true;
-        },
-      ));
-      await testZone.run(() async {
-        final ImageProvider imageProvider = LoadErrorCompleterImageProvider();
-        final Completer<bool> caughtError = Completer<bool>();
-        FlutterError.onError = (FlutterErrorDetails details) {
-          throw Error();
-        };
-        final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-        result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-        }, onError: (dynamic error, StackTrace stackTrace) {
-          caughtError.complete(true);
-        }));
-        expect(await caughtError.future, true);
-      });
-      expect(uncaught, false);
-    });
-
-    test('File image with empty file throws expected error and evicts from cache', () async {
-      final Completer<StateError> error = Completer<StateError>();
-      FlutterError.onError = (FlutterErrorDetails details) {
-        error.complete(details.exception as StateError);
-      };
-      final MemoryFileSystem fs = MemoryFileSystem();
-      final File file = fs.file('/empty.png')..createSync(recursive: true);
-      final FileImage provider = FileImage(file);
-
-      expect(imageCache.statusForKey(provider).untracked, true);
-      expect(imageCache.pendingImageCount, 0);
-
-      provider.resolve(ImageConfiguration.empty);
-
-      expect(imageCache.statusForKey(provider).pending, true);
-      expect(imageCache.pendingImageCount, 1);
-
-      expect(await error.future, isStateError);
-      expect(imageCache.statusForKey(provider).untracked, true);
-      expect(imageCache.pendingImageCount, 0);
-    });
-
-    group('NetworkImage', () {
-      MockHttpClient httpClient;
-
-      setUp(() {
-        httpClient = MockHttpClient();
-        debugNetworkImageHttpClientProvider = () => httpClient;
-      });
-
-      tearDown(() {
-        debugNetworkImageHttpClientProvider = null;
-      });
-
-      test('Expect thrown exception with statusCode - evicts from cache', () async {
-        final int errorStatusCode = HttpStatus.notFound;
-        const String requestUrl = 'foo-url';
-
-        final MockHttpClientRequest request = MockHttpClientRequest();
-        final MockHttpClientResponse response = MockHttpClientResponse();
-        when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
-        when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
-        when(response.statusCode).thenReturn(errorStatusCode);
-
-        final Completer<dynamic> caughtError = Completer<dynamic>();
-
-        final ImageProvider imageProvider = NetworkImage(nonconst(requestUrl));
-        expect(imageCache.pendingImageCount, 0);
-        expect(imageCache.statusForKey(imageProvider).untracked, true);
-
-        final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-
-        expect(imageCache.pendingImageCount, 1);
-        expect(imageCache.statusForKey(imageProvider).pending, true);
-
-        result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-        }, onError: (dynamic error, StackTrace stackTrace) {
-          caughtError.complete(error);
-        }));
-
-        final dynamic err = await caughtError.future;
-
-        expect(imageCache.pendingImageCount, 0);
-        expect(imageCache.statusForKey(imageProvider).untracked, true);
-
-        expect(
-          err,
-          isA<NetworkImageLoadException>()
-            .having((NetworkImageLoadException e) => e.statusCode, 'statusCode', errorStatusCode)
-            .having((NetworkImageLoadException e) => e.uri, 'uri', Uri.base.resolve(requestUrl)),
-        );
-      }, skip: isBrowser);  // Browser implementation does not use HTTP client but an <img> tag.
-
-      test('Disallows null urls', () {
-        expect(() {
-          NetworkImage(nonconst(null));
-        }, throwsAssertionError);
-      });
-
-      test('Uses the HttpClient provided by debugNetworkImageHttpClientProvider if set', () async {
-        when(httpClient.getUrl(any)).thenThrow('client1');
-        final List<dynamic> capturedErrors = <dynamic>[];
-
-        Future<void> loadNetworkImage() async {
-          final NetworkImage networkImage = NetworkImage(nonconst('foo'));
-          final ImageStreamCompleter completer = networkImage.load(networkImage, basicDecoder);
-          completer.addListener(ImageStreamListener(
-            (ImageInfo image, bool synchronousCall) { },
-            onError: (dynamic error, StackTrace stackTrace) {
-              capturedErrors.add(error);
-            },
-          ));
-          await Future<void>.value();
-        }
-
-        await loadNetworkImage();
-        expect(capturedErrors, <dynamic>['client1']);
-        final MockHttpClient client2 = MockHttpClient();
-        when(client2.getUrl(any)).thenThrow('client2');
-        debugNetworkImageHttpClientProvider = () => client2;
-        await loadNetworkImage();
-        expect(capturedErrors, <dynamic>['client1', 'client2']);
-      }, skip: isBrowser); // Browser implementation does not use HTTP client but an <img> tag.
-
-      test('Propagates http client errors during resolve()', () async {
-        when(httpClient.getUrl(any)).thenThrow(Error());
-        bool uncaught = false;
-
-        await runZoned(() async {
-          const ImageProvider imageProvider = NetworkImage('asdasdasdas');
-          final Completer<bool> caughtError = Completer<bool>();
-          FlutterError.onError = (FlutterErrorDetails details) {
-            throw Error();
-          };
-          final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-          result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
-          }, onError: (dynamic error, StackTrace stackTrace) {
-            caughtError.complete(true);
-          }));
-          expect(await caughtError.future, true);
-        }, zoneSpecification: ZoneSpecification(
-          handleUncaughtError: (Zone zone, ZoneDelegate zoneDelegate, Zone parent, Object error, StackTrace stackTrace) {
-            uncaught = true;
-          },
-        ));
-        expect(uncaught, false);
-      });
-
-      test('Notifies listeners of chunk events', () async {
-        const int chunkSize = 8;
-        final List<Uint8List> chunks = <Uint8List>[
-          for (int offset = 0; offset < kTransparentImage.length; offset += chunkSize)
-            Uint8List.fromList(kTransparentImage.skip(offset).take(chunkSize).toList()),
-        ];
-        final Completer<void> imageAvailable = Completer<void>();
-        final MockHttpClientRequest request = MockHttpClientRequest();
-        final MockHttpClientResponse response = MockHttpClientResponse();
-        when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
-        when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
-        when(response.statusCode).thenReturn(HttpStatus.ok);
-        when(response.contentLength).thenReturn(kTransparentImage.length);
-        when(response.listen(
-          any,
-          onDone: anyNamed('onDone'),
-          onError: anyNamed('onError'),
-          cancelOnError: anyNamed('cancelOnError'),
-        )).thenAnswer((Invocation invocation) {
-          final void Function(List<int>) onData = invocation.positionalArguments[0] as void Function(List<int>);
-          final void Function(Object) onError = invocation.namedArguments[#onError] as void Function(Object);
-          final VoidCallback onDone = invocation.namedArguments[#onDone] as VoidCallback;
-          final bool cancelOnError = invocation.namedArguments[#cancelOnError] as bool;
-
-          return Stream<Uint8List>.fromIterable(chunks).listen(
-            onData,
-            onDone: onDone,
-            onError: onError,
-            cancelOnError: cancelOnError,
-          );
-        });
-
-        final ImageProvider imageProvider = NetworkImage(nonconst('foo'));
-        final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-        final List<ImageChunkEvent> events = <ImageChunkEvent>[];
-        result.addListener(ImageStreamListener(
-          (ImageInfo image, bool synchronousCall) {
-            imageAvailable.complete();
-          },
-          onChunk: (ImageChunkEvent event) {
-            events.add(event);
-          },
-          onError: (dynamic error, StackTrace stackTrace) {
-            imageAvailable.completeError(error, stackTrace);
-          },
-        ));
-        await imageAvailable.future;
-        expect(events.length, chunks.length);
-        for (int i = 0; i < events.length; i++) {
-          expect(events[i].cumulativeBytesLoaded, math.min((i + 1) * chunkSize, kTransparentImage.length));
-          expect(events[i].expectedTotalBytes, kTransparentImage.length);
-        }
-      }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56317
-
-      test('NetworkImage is evicted from cache on SocketException', () async {
-        final MockHttpClient mockHttpClient = MockHttpClient();
-        when(mockHttpClient.getUrl(any)).thenAnswer((_) => throw const SocketException('test exception'));
-        debugNetworkImageHttpClientProvider = () => mockHttpClient;
-
-
-        final ImageProvider imageProvider = NetworkImage(nonconst('testing.url'));
-        expect(imageCache.pendingImageCount, 0);
-        expect(imageCache.statusForKey(imageProvider).untracked, true);
-
-        final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
-
-        expect(imageCache.pendingImageCount, 1);
-        expect(imageCache.statusForKey(imageProvider).pending, true);
-        final Completer<dynamic> caughtError = Completer<dynamic>();
-        result.addListener(ImageStreamListener(
-          (ImageInfo info, bool syncCall) {},
-          onError: (dynamic error, StackTrace stackTrace) {
-            caughtError.complete(error);
-          },
-        ));
-
-        final dynamic err = await caughtError.future;
-
-        expect(err, isA<SocketException>());
-
-        expect(imageCache.pendingImageCount, 0);
-        expect(imageCache.statusForKey(imageProvider).untracked, true);
-        expect(imageCache.containsKey(result), isFalse);
-
-        debugNetworkImageHttpClientProvider = null;
-      }, skip: isBrowser); // Browser does not resolve images this way.
-    });
+    expect(uncaught, false);
   });
 
-  test('ResizeImage resizes to the correct dimensions', () async {
-    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-    final MemoryImage imageProvider = MemoryImage(bytes);
-    final Size rawImageSize = await _resolveAndGetSize(imageProvider);
-    expect(rawImageSize, const Size(1, 1));
-
-    const Size resizeDims = Size(14, 7);
-    final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round());
-    const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
-    final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
-    expect(resizedImageSize, resizeDims);
-  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56312
-
-  test('ResizeImage does not resize when no size is passed', () async {
-    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-    final MemoryImage imageProvider = MemoryImage(bytes);
-    final Size rawImageSize = await _resolveAndGetSize(imageProvider);
-    expect(rawImageSize, const Size(1, 1));
-
-    // Cannot pass in two null arguments for cache dimensions, so will use the regular
-    // MemoryImage
-    final MemoryImage resizedImage = MemoryImage(bytes);
-    final Size resizedImageSize = await _resolveAndGetSize(resizedImage);
-    expect(resizedImageSize, const Size(1, 1));
-  });
-
-  test('ResizeImage stores values', () async {
-    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-    final MemoryImage memoryImage = MemoryImage(bytes);
-    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: 20);
-    expect(resizeImage.width, 10);
-    expect(resizeImage.height, 20);
-    expect(resizeImage.imageProvider, memoryImage);
-
-    expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
-  });
-
-  test('ResizeImage takes one dim', () async {
-    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-    final MemoryImage memoryImage = MemoryImage(bytes);
-    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: null);
-    expect(resizeImage.width, 10);
-    expect(resizeImage.height, null);
-    expect(resizeImage.imageProvider, memoryImage);
-
-    expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
-  });
-
-  test('ResizeImage forms closure', () async {
-    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-    final MemoryImage memoryImage = MemoryImage(bytes);
-    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
-
-    final DecoderCallback decode = (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
-      expect(cacheWidth, 123);
-      expect(cacheHeight, 321);
-      return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
+  test('File image with empty file throws expected error and evicts from cache', () async {
+    final Completer<StateError> error = Completer<StateError>();
+    FlutterError.onError = (FlutterErrorDetails details) {
+      error.complete(details.exception as StateError);
     };
+    final MemoryFileSystem fs = MemoryFileSystem();
+    final File file = fs.file('/empty.png')..createSync(recursive: true);
+    final FileImage provider = FileImage(file);
 
-    resizeImage.load(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
-  });
+    expect(imageCache.statusForKey(provider).untracked, true);
+    expect(imageCache.pendingImageCount, 0);
 
-  test('ResizeImage handles sync obtainKey', () async {
-    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-    final MemoryImage memoryImage = MemoryImage(bytes);
-    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
+    provider.resolve(ImageConfiguration.empty);
 
-    bool isAsync = false;
-    resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
-      expect(isAsync, false);
-    });
-    isAsync = true;
-    expect(isAsync, true);
-  });
+    expect(imageCache.statusForKey(provider).pending, true);
+    expect(imageCache.pendingImageCount, 1);
 
-  test('ResizeImage handles async obtainKey', () async {
-    final Uint8List bytes = Uint8List.fromList(kTransparentImage);
-    final AsyncKeyMemoryImage memoryImage = AsyncKeyMemoryImage(bytes);
-    final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
-
-    bool isAsync = false;
-    resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
-      expect(isAsync, true);
-    });
-    isAsync = true;
-    expect(isAsync, true);
+    expect(await error.future, isStateError);
+    expect(imageCache.statusForKey(provider).untracked, true);
+    expect(imageCache.pendingImageCount, 0);
   });
 
   test('File image with empty file throws expected error (load)', () async {
@@ -534,40 +135,3 @@
     expect(await error.future, isStateError);
   });
 }
-
-Future<Size> _resolveAndGetSize(ImageProvider imageProvider,
-    {ImageConfiguration configuration = ImageConfiguration.empty}) async {
-  final ImageStream stream = imageProvider.resolve(configuration);
-  final Completer<Size> completer = Completer<Size>();
-  final ImageStreamListener listener =
-    ImageStreamListener((ImageInfo image, bool synchronousCall) {
-      final int height = image.image.height;
-      final int width = image.image.width;
-      completer.complete(Size(width.toDouble(), height.toDouble()));
-    }
-  );
-  stream.addListener(listener);
-  return await completer.future;
-}
-
-// This version of MemoryImage guarantees obtainKey returns a future that has not been
-// completed synchronously.
-class AsyncKeyMemoryImage extends MemoryImage {
-  const AsyncKeyMemoryImage(Uint8List bytes) : super(bytes);
-
-  @override
-  Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
-    return Future<MemoryImage>(() => this);
-  }
-}
-
-class MockHttpClient extends Mock implements HttpClient {}
-class MockHttpClientRequest extends Mock implements HttpClientRequest {}
-class MockHttpClientResponse extends Mock implements HttpClientResponse {}
-
-class TestAssetBundle extends CachingAssetBundle {
-  @override
-  Future<ByteData> load(String key) async {
-    return null;
-  }
-}
diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart
index 289c1b2..6d24df8 100644
--- a/packages/flutter/test/rendering/rendering_tester.dart
+++ b/packages/flutter/test/rendering/rendering_tester.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/rendering.dart';
@@ -19,7 +21,15 @@
   /// while drawing the frame. If [onErrors] is null and [FlutterError] caught at least
   /// one error, this function fails the test. A test may override [onErrors] and
   /// inspect errors using [takeFlutterErrorDetails].
-  TestRenderingFlutterBinding({ this.onErrors });
+  ///
+  /// Errors caught between frames will cause the test to fail unless
+  /// [FlutterError.onError] has been overridden.
+  TestRenderingFlutterBinding({ this.onErrors }) {
+    FlutterError.onError = (FlutterErrorDetails details) {
+      FlutterError.dumpErrorToConsole(details);
+      Zone.current.parent.handleUncaughtError(details.exception, details.stack);
+    };
+  }
 
   final List<FlutterErrorDetails> _errors = <FlutterErrorDetails>[];