|
| 1 | +--- |
| 2 | +title: Display Images |
| 3 | +navOrder: 35 |
| 4 | +--- |
| 5 | +Displaying images in a Flutter golden test is an especially annoying and tedious |
| 6 | +process. The root cause is that Flutter's test system manually controls the passage |
| 7 | +of time, which is important for inspecting UI frames, but prevents all real |
| 8 | +asynchronous behavior from executing, including loading images. |
| 9 | + |
| 10 | +There are three non-standard image loading scenarios in a golden test: |
| 11 | + * Loading from the network |
| 12 | + * Loading from a file |
| 13 | + * Loading from memory |
| 14 | + |
| 15 | +## Display an Image from the Network |
| 16 | +Apps often load images from the network. To load and display these images in a |
| 17 | +golden test, you need to tell Flutter to allow network requests, and you need to |
| 18 | +explicitly pre-cache the network image. |
| 19 | + |
| 20 | +By default, Flutter forcibly prevents HTTP calls through an `HttpClient`. Fortunately, |
| 21 | +there's a mechanism to tell Flutter to mind its own business. |
| 22 | + |
| 23 | +The following code is a minimal sample for loading images from the network within |
| 24 | +a test. |
| 25 | + |
| 26 | +```dart |
| 27 | +void testWidgets("load a file image", (tester) async { |
| 28 | + // Normally, Flutter forcibly prevents HTTP calls. Turn that off by null'ing out |
| 29 | + // the HttpOverrides. |
| 30 | + final testOverride = HttpOverrides.current; |
| 31 | + HttpOverrides.global = null; |
| 32 | + addTearDown(() => HttpOverrides.global = testOverride); |
| 33 | + |
| 34 | + const imageUrl = "https://upload.wikimedia.org/wikipedia/commons/b/b3/Vista_Satelital_de_Nohyaxch%C3%A9_y_Edzn%C3%A1%2C_Campeche.png"; |
| 35 | + |
| 36 | + // Load the image from the internet. This must be done in `runAsync` because |
| 37 | + // network communication is a real asynchronous behavior. |
| 38 | + await tester.runAsync(() async { |
| 39 | + await precacheImage(NetworkImage(imageUrl), tester.binding.rootElement!); |
| 40 | + }); |
| 41 | + |
| 42 | + // Display the image in the widget tree. |
| 43 | + await tester.pumpWidget( |
| 44 | + MyApp( |
| 45 | + // Network images are globally key'd on their URL, so you don't have to |
| 46 | + // pass the same `ImageProvider` that you used to load the image. |
| 47 | + child: Image.network(imageUrl), |
| 48 | + ), |
| 49 | + ); |
| 50 | +}); |
| 51 | +``` |
| 52 | + |
| 53 | +The first thing you'll notice in the network image example is that we have to mess |
| 54 | +with Flutter's `HttpOverrides`. This is where Flutter's test system installs its |
| 55 | +own `HttpClient` that rejects every request. We turn that off, and we also setup |
| 56 | +a test teardown method that restores it - we don't want to turn it off for all tests. |
| 57 | + |
| 58 | +The one nice detail about using network images is that the image URL `String` can |
| 59 | +be used to refer to that image anywhere. We don't have to pass the `ImageProvider` |
| 60 | +into our widget tree, which means this is an approach that might actually work for |
| 61 | +real-world apps. |
| 62 | + |
| 63 | +If you use this approach, be mindful that images can disappear from the internet at any |
| 64 | +time. Also, network conditions can change at any time. Network images introduce a |
| 65 | +source of flakiness that you might need to deal with from time to time. |
| 66 | + |
| 67 | +## Display an Image from File |
| 68 | +Though less common than network images, an app might want to load an image from |
| 69 | +the local file system. This load path only requires image pre-caching. |
| 70 | + |
| 71 | +The following code is a minimal sample for loading images from a file within a test. |
| 72 | + |
| 73 | +```dart |
| 74 | +void testWidgets("load a file image", (tester) async { |
| 75 | + await tester.runAsync(() async { |
| 76 | + await precacheImage(FileImage(File("path/to/my_image.png")), tester.binding.rootElement!); |
| 77 | + }); |
| 78 | +
|
| 79 | + // Display the image in the widget tree. |
| 80 | + await tester.pumpWidget( |
| 81 | + MyApp( |
| 82 | + // File images are globally key'd on their `File`, so you don't have to |
| 83 | + // pass the same `ImageProvider` that you used to load the image. |
| 84 | + child: Image.network(imageUrl), |
| 85 | + ), |
| 86 | + ); |
| 87 | +}); |
| 88 | +``` |
| 89 | + |
| 90 | +Loading images from file is probably the easiest load path to orchestrate in a |
| 91 | +golden test. However, it's only useful if your app happens to load images from |
| 92 | +the local file system. As a result, this load path is probably only useful for |
| 93 | +some desktop apps. |
| 94 | + |
| 95 | +## Display an Image from Memory |
| 96 | +In rare situations, you might want to load an image into a widget tree based on |
| 97 | +an in-memory representation. This would probably only happen if an image were |
| 98 | +generated or altered within a test. However, we cover this load path for completeness. |
| 99 | + |
| 100 | +Using an in-memory image in a test requires the same pre-caching as other loading |
| 101 | +methods. This approach also requires that you use the same instance of your `ImageProvider` |
| 102 | +both to load the image, as well as to display the image. |
| 103 | + |
| 104 | +The following code is a minimal sample for displaying images from memory within a test. |
| 105 | + |
| 106 | +```dart |
| 107 | +void testWidgets("load an in-memory image", (tester) async { |
| 108 | + // Load or create your image data. |
| 109 | + final imageProvider = MemoryImage( |
| 110 | + // We use a File for code brevity, but if you want to load from a File |
| 111 | + // then use the File loading path. |
| 112 | + File("path/to/my_image.png").readAsBytesSync(), |
| 113 | + ); |
| 114 | +
|
| 115 | + // Use Flutter's `runAsync()` method so that you can run a real |
| 116 | + // asynchronous call from a test. Use this opportunity to call |
| 117 | + // Flutter's `precacheImage()` method to load your `ImageProvider`. |
| 118 | + await tester.runAsync(() async { |
| 119 | + await precacheImage(imageProvider, tester.binding.rootElement!); |
| 120 | + }); |
| 121 | +
|
| 122 | + await tester.pumpWidget( |
| 123 | + MyApp( |
| 124 | + child: Image( |
| 125 | + // Use the same `ImageProvider` in your widget tree. |
| 126 | + image: imageProvider, |
| 127 | + ), |
| 128 | + ), |
| 129 | + ); |
| 130 | +}); |
| 131 | +``` |
| 132 | + |
| 133 | +The biggest drawback of this load path, compared to other load paths, is that |
| 134 | +you need to somehow get your `ImageProvider` from the test into your widget tree. |
| 135 | +This is easy for an example where the entire tree is assembled in the test. However, |
| 136 | +real tests use widget trees that don't expose inner `ImageProvider`s. |
| 137 | + |
0 commit comments