flutter_cached_network_image icon indicating copy to clipboard operation
flutter_cached_network_image copied to clipboard

How to widget test Cached network manager

Open flutteradv opened this issue 5 years ago • 19 comments

Is there any tests code sample on Cached network manager? how we should write widget tests for it

flutteradv avatar Dec 15 '19 09:12 flutteradv

I always have the same problem

aBuder avatar Dec 30 '19 11:12 aBuder

Hi, does anyone successfully test integration test with CachedNetworkImage?

wheel1992 avatar Mar 19 '20 01:03 wheel1992

@renefloor any pointers on this one? Or do we just mock the dependencies.

jogboms avatar Jun 13 '20 10:06 jogboms

That's indeed something that should be improved I think. With smaller unit tests you can mock the CacheManager in the image widget.

renefloor avatar Jun 14 '20 12:06 renefloor

Any updates on this? What's the way to go here?

DevNico avatar Jul 06 '20 12:07 DevNico

This blog post might help: https://medium.com/flutter-community/golden-testing-using-cachednetworkimage-1b488c653af3

ncuillery avatar Sep 01 '20 12:09 ncuillery

I have a issue to test when I use CircularProgressIndicator as progress widget like this :

 CachedNetworkImage(
    imageUrl: '$baseUrl/pictures/${image}',
    cacheManager: context.watch<CacheManager>(),
    progressIndicatorBuilder: (context, url, downloadProgress)
      return Center(
        child: CircularProgressIndicator(
          value: downloadProgress.progress,
        ),
      );
    },
  ),

I didn't check code yet but the behavior looks like the animation never complete since WidgetTester.pumpAndSettle() will always timeout. Replacing with a SizedBox when downloadProgress.progress is null fixe the issue

woprandi avatar Nov 12 '21 11:11 woprandi

it would be nice to

That's indeed something that should be improved I think. With smaller unit tests you can mock the CacheManager in the image widget.

This is not always an option or even recommended.

It would be nice if the cache manager could be set project wide instead on having to pass it directly on the widget.

cedvdb avatar Feb 01 '22 04:02 cedvdb

I see the following error:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following MissingPluginException was thrown running a test:
MissingPluginException(No implementation found for method getDatabasesPath on channel
com.tekartik.sqflite)

When the exception was thrown, this was the stack:
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

The test description was:
  simplest
════════════════════════════════════════════════════════════════════════════════════════════════════

anyone has the same problem?

fzyzcjy avatar May 15 '22 11:05 fzyzcjy

Just tried with updated code from bloc post

class MockBaseCacheManager extends Mock implements BaseCacheManager {
  static const fileSystem = LocalFileSystem();
  @override
  Stream<FileResponse> getFileStream(
    String url, {
    String? key,
    Map<String, String>? headers,
    bool? withProgress,
  }) async* {
    final fileName = './test/assets/${File(url).uri.pathSegments.last}';

    final file = fileSystem.file(fileName);

    yield FileInfo(
      file, // Path to the asset
      FileSource.Cache, // Simulate a cache hit
      DateTime(2050), // Very long validity
      url, // Source url
    );
  }
}

my issue is that on _image_loader.dart line 58, file.readAsBytes() never completes. I did some research, it seems that widget tests run on some isolate and that's the reason why it fails. Any ideas?

diegogarciar avatar May 19 '22 03:05 diegogarciar

It worked at v3.2.1!

// ignore_for_file: depend_on_referenced_packages

import 'package:cached_network_image/cached_network_image.dart';
import 'package:file/local.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

void main() {
  testWidgets('The image should be displayed', (tester) async {
    await tester.runAsync(() async {
      ImageProvider? receiveImageProvider;

      await tester.pumpWidget(
        CachedNetworkImage(
          imageUrl: 'dummy url',
          cacheManager: MockCacheManager(),
          imageBuilder: (context, imageProvider) {
            receiveImageProvider = imageProvider;
            return Image(
              image: imageProvider,
            );
          },
        ),
      );

      expect(receiveImageProvider, isNull);

      // Wait for the image to be loaded.
      await Future<void>.delayed(const Duration(milliseconds: 1000));
      await tester.pump();

      expect(receiveImageProvider, isNotNull);
    });
  });
}

class MockCacheManager extends Mock implements DefaultCacheManager {
  static const fileSystem = LocalFileSystem();

  @override
  Stream<FileResponse> getImageFile(
    String url, {
    String? key,
    Map<String, String>? headers,
    bool withProgress = false,
    int? maxHeight,
    int? maxWidth,
  }) async* {
    yield FileInfo(
      fileSystem
          .file('./test/assets/13707135.png'), // Return your image file path
      FileSource.Cache,
      DateTime(2050),
      url,
    );
  }
}

susatthi avatar Jun 15 '22 02:06 susatthi

The best and simplest solution I've found is to create a wrapper widget for CachedNetworkImage that uses a regular network image if it detects it's a testing environment. It'd be nice to not need a workaround like that.

smith897 avatar Sep 22 '22 16:09 smith897

Are there any updates to this issue?

yasht01 avatar Apr 05 '23 08:04 yasht01

I personally workaround this by using JsonCacheInfoRepository instead of the default repository.

fzyzcjy avatar Apr 05 '23 10:04 fzyzcjy

The best and simplest solution I've found is to create a wrapper widget for CachedNetworkImage that uses a regular network image if it detects it's a testing environment. It'd be nice to not need a workaround like that.

Offtopic: But how do you detect test environment?

Maxim-Filimonov avatar May 13 '23 16:05 Maxim-Filimonov

Offtopic: But how do you detect test environment?

You could use this Platform.environment.containsKey('FLUTTER_TEST') @Maxim-Filimonov

jogboms avatar May 14 '23 06:05 jogboms

Offtopic: But how do you detect test environment?

Or check the type of binding

fzyzcjy avatar May 14 '23 09:05 fzyzcjy

On the note of testing Cached Network Manager. I spent about 2 hours debugging one of the tests using MockCacheManager yesterday.

I had two different cache managers to test both loading and error case:

class MockCacheManager extends Mock implements BaseCacheManager {
  static const fileSystem = LocalFileSystem();
  @override
  Stream<FileResponse> getFileStream(
    String url, {
    String? key,
    Map<String, String>? headers,
    bool? withProgress,
  }) async* {
    final fileName = ImageConstant.img1splashscreen;

    final file = fileSystem.file(fileName);

    yield FileInfo(
      file, // Path to the asset
      FileSource.Cache, // Simulate a cache hit
      DateTime(2050), // Very long validity
      url, // Source url
    );
  }
}
class InvalidCacheManager extends Mock implements BaseCacheManager {
  @override
  Stream<FileResponse> getFileStream(
    String url, {
    String? key,
    Map<String, String>? headers,
    bool? withProgress,
  }) async* {
    throw Exception('Cannot download file');
  }
}

And tests:

testWidgets('can render network image', (tester) async {
    // Create a mock cache manager instance
    final cacheManager = MockCacheManager();

    const url = 'https://local_file_system.com/200.jpeg';
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: CachedNetworkImage(
            imageUrl: url,
            cacheManager: cacheManager,
          ),
        ),
      ),
    );
});
testWidgets('renders error when cannot load network image', (tester) async {
    // Create a mock cache manager instance
    final cacheManager = InvalidCacheManager();

    const url = 'https://local_file_system.com/200.jpeg';
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: CachedNetworkImage(
            imageUrl: url,
            cacheManager: cacheManager,
          ),
        ),
      ),
    );
});

The culprit was the url. If it's the same exact url cache_manager is not even called by flutter itself so you get EXACTLY the same output in both of these scenarios as flutter image caching is sharing amongst all tests. Something to keep in mind when testing those that url ALWAYS need to be different.

Maxim-Filimonov avatar May 15 '23 05:05 Maxim-Filimonov