Open flutteradv opened 4 years ago
I always have the same problem
Hi, does anyone successfully test integration test with CachedNetworkImage
?
@renefloor any pointers on this one? Or do we just mock the dependencies.
That's indeed something that should be improved I think. With smaller unit tests you can mock the CacheManager in the image widget.
Any updates on this? What's the way to go here?
This blog post might help: https://medium.com/flutter-community/golden-testing-using-cachednetworkimage-1b488c653af3
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
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.
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?
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?
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,
);
}
}
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.
Are there any updates to this issue?
I personally workaround this by using JsonCacheInfoRepository
instead of the default repository.
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?
Offtopic: But how do you detect test environment?
You could use this Platform.environment.containsKey('FLUTTER_TEST')
@Maxim-Filimonov
Offtopic: But how do you detect test environment?
Or check the type of binding
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.
I've also achieved golden testing in version v3.4.1 .
late BaseCacheManager cacheManager;
void main() {
WidgetsFlutterBinding.ensureInitialized();
cacheManager = DefaultCacheManager();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'MyApp',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
static const image = 'https://picsum.photos/200/300';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: 40,
itemBuilder: (context, index) {
return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: CachedNetworkImage(
imageUrl: image,
cacheManager: cacheManager,
fit: BoxFit.cover,
),
);
},
),
),
);
}
}
@visibleForTesting
class MockCacheManager extends Mock implements BaseCacheManager {
@override
Stream<FileResponse> getFileStream(
String url, {
String? key,
Map<String, String>? headers,
bool? withProgress,
}) async* {
File file = const LocalFileSystem().file('test/test.jpg');
yield FileInfo(
file,
FileSource.Online, // Simulate a cache hit
DateTime(2050), // Very long validity
url, // Source url
);
}
}
import 'package:cached_network_image_golden_test/main.dart';
import 'package:file/local.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/file.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:mockito/mockito.dart';
void main() {
// You can use a service locator like GetIt instead.
cacheManager = MockCacheManager();
testWidgets(
'should match with the golden file (basic)',
(WidgetTester tester) async {
await tester.runAsync(() async {
const widget = MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
);
await tester.pumpWidgetBuilder(widget);
final elements = tester.elementList(find.byType(Image));
for (var element in elements) {
final image = element.widget as Image;
await precacheImage(image.image, element);
}
await tester.pumpAndSettle();
});
await expectLater(
find.byType(HomePage),
matchesGoldenFile('goldens/home_page.png'),
);
},
);
testGoldens(
'should match with the golden file (golden_toolkit)',
(WidgetTester tester) async {
await tester.runAsync(() async {
const widget = MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
);
await tester.pumpWidgetBuilder(widget);
final elements = tester.elementList(find.byType(Image));
for (var element in elements) {
final image = element.widget as Image;
await precacheImage(image.image, element);
}
await tester.pumpAndSettle();
});
await screenMatchesGolden(tester, 'home_page');
},
);
}
Is there any tests code sample on Cached network manager? how we should write widget tests for it