How to write widget test for CachedNetworkImage in flutter

1,371

The CachedNetworkImage widget accepts an optional cacheManager property (default to a DefaultCacheManager instance).

The idea is to use get_it to inject a DefaultCacheManager instance in your app, and a custom one in the test. This custom cache manager can return a test asset file.

Custom cache manager

import 'dart:io';

import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_cache_manager/src/cache_store.dart';
import 'package:flutter_cache_manager/src/storage/non_storing_object_provider.dart';

class TestCacheManager extends BaseCacheManager {
  TestCacheManager()
      : super(
          null,
          cacheStore: CacheStore(
            Future.value(null),
            null,
            null,
            null,
            cacheRepoProvider: Future.value(NonStoringObjectProvider()),
          ),
        );

  @override
  Future<String> getFilePath() async {
    return null;
  }

  @override
  Stream<FileResponse> getFileStream(String url,
      {Map<String, String> headers, bool withProgress}) async* {
    if (url == 'https://myownservice.com/example') {
      yield FileInfo(
        File('test/assets/mock_image.jpg'),
        FileSource.Cache,
        DateTime(2050),
        url,
      );
    }
  }
}

Widget you want to test:

class MyImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CachedNetworkImage(
      imageUrl: 'https://myownservice.com/example',
      cacheManager: GetIt.instance.get<BaseCacheManager>(),
    );
  }
}

App main():

GetIt.instance.registerSingleton<BaseCacheManager>(
  DefaultCacheManager(),
);

Test main():

GetIt.instance.registerSingleton<BaseCacheManager>(
  TestCacheManager(),
);

Then you should be able to pump an instance of the MyImage widget:

await tester.pumpWidget(MyImage());

// Important: you need to pump an other frame so the CachedNetworkImage
// can replace the placeholder by the image received from the
// cache manager stream.
await tester.pump();

See this blog post for full explanations, code sample and example app.

Share:
1,371
Admin
Author by

Admin

Updated on December 16, 2022

Comments

  • Admin
    Admin over 1 year

    I can widget test Image.network using HttpOverrides and tried following code to test CachedNetworkImage with no success Is there anybody who tested this package already? I've also tried to use sqflite using setMockMethodCallHandler to MethodChannel('com.tekartik.sqflite') but just getDatabasesPath method get called what is the correct approach to test this package?

    import 'dart:async';
    import 'dart:io';
    
    import 'package:cached_network_image/cached_network_image.dart';
    import 'package:flutter/widgets.dart';
    import 'package:flutter_test/flutter_test.dart';
    import 'package:mockito/mockito.dart';
    
    import '../unit_test/sqlcool.dart';
    
    const List<int> kTransparentImage = <int>[
      0x89,
      0x50,
      0x4E,
      0x47,
      0x0D,
      0x0A,
      0x1A,
      0x0A,
      0x00,
      0x00,
      0x00,
      0x0D,
      0x49,
      0x48,
      0x44,
      0x52,
      0x00,
      0x00,
      0x00,
      0x01,
      0x00,
      0x00,
      0x00,
      0x01,
      0x08,
      0x06,
      0x00,
      0x00,
      0x00,
      0x1F,
      0x15,
      0xC4,
      0x89,
      0x00,
      0x00,
      0x00,
      0x0A,
      0x49,
      0x44,
      0x41,
      0x54,
      0x78,
      0x9C,
      0x63,
      0x00,
      0x01,
      0x00,
      0x00,
      0x05,
      0x00,
      0x01,
      0x0D,
      0x0A,
      0x2D,
      0xB4,
      0x00,
      0x00,
      0x00,
      0x00,
      0x49,
      0x45,
      0x4E,
      0x44,
      0xAE,
    ];
    void main() async {
      TestWidgetsFlutterBinding.ensureInitialized();
      await setup();
      final MockHttpClient client = MockHttpClient();
      final MockHttpClientRequest request = MockHttpClientRequest();
      final MockHttpClientResponse response = MockHttpClientResponse();
      final MockHttpHeaders headers = MockHttpHeaders();
    
      testWidgets('Headers', (WidgetTester tester) async {
        HttpOverrides.runZoned<Future<void>>(() async {
          // await tester.pumpWidget(Image.network(
          //   'https://www.example.com/images/frame.png',
          //   headers: const <String, String>{'flutter': 'flutter'},
          // ));
    
          await tester.pumpWidget(CachedNetworkImage(
              imageUrl: 'https://www.example.com/images/frame.png',
              errorWidget: (context, err, o) {
                print(
                    "===========>>>>> CachedNetworkImage error= $err <<<<=================");
              }));
        }, createHttpClient: (SecurityContext _) {
          when(client.getUrl(any)).thenAnswer((invocation) {
            print(
                "================>>>>>> getUrl = ${invocation.positionalArguments}  <<<<<===============");
            return Future<HttpClientRequest>.value(request);
          });
          when(request.headers).thenReturn(headers);
          when(request.close()).thenAnswer((invocation) {
            print(
                "================>>>>>> request.close = ${invocation.toString()}  <<<<<===============");
            return Future<HttpClientResponse>.value(response);
          });
          when(response.contentLength).thenReturn(kTransparentImage.length);
          when(response.statusCode).thenReturn(HttpStatus.ok);
          when(response.listen(any)).thenAnswer((Invocation invocation) {
            final void Function(List<int>) onData =
                invocation.positionalArguments[0] as void Function(List<int>);
            print(
                "================>>>>>> onData = ${onData}  <<<<<===============");
            final void Function() onDone =
                invocation.namedArguments[#onDone] as void Function();
            print(
                "================>>>>>> onDone = ${onDone}  <<<<<===============");
            final void Function(Object, [StackTrace]) onError = invocation
                .namedArguments[#onError] as void Function(Object, [StackTrace]);
            final bool cancelOnError =
                invocation.namedArguments[#cancelOnError] as bool;
            return Stream<List<int>>.fromIterable(<List<int>>[kTransparentImage])
                .listen(onData,
                    onDone: onDone, onError: onError, cancelOnError: cancelOnError);
          });
          return client;
        });
      }, skip: isBrowser);
    }
    
    class MockHttpClient extends Mock implements HttpClient {}
    
    class MockHttpClientRequest extends Mock implements HttpClientRequest {}
    
    class MockHttpClientResponse extends Mock implements HttpClientResponse {}
    
    class MockHttpHeaders extends Mock implements HttpHeaders {}
    
    Directory directory;
    const MethodChannel channel = MethodChannel('com.tekartik.sqflite');
    final List<MethodCall> log = <MethodCall>[];
    bool setupDone = false;
    
    Future<void> setup() async {
      // WidgetsFlutterBinding.ensureInitialized();
      if (setupDone) {
        return;
      }
      directory = await Directory.systemTemp.createTemp();
      String response;
      channel.setMockMethodCallHandler((MethodCall methodCall) async {
        print("METHOD CALL: $methodCall");
        log.add(methodCall);
        switch (methodCall.method) {
          case "getDatabasesPath":
            return directory.path;
            break;
          case "query":
            return 1;
            break;
         }
        return response;
    }
    
    
  • Rob
    Rob over 2 years
    It seems that the blog post is out of date and now there is no example on how to do this.