Reputation: 5069
I have a Widget
with NetworkImage
(so far with hard-coded url).
I would like to widget test this Widget, but I got 404 when I run widget test (url is 100% valid).
How can I make NetworkImages
load themselves or (which would be better) ignore them so that my tests won't fail because of 404?
Upvotes: 33
Views: 20042
Reputation: 8447
If you have this really unusual situation where the widget test is all about whether the images get correctly fetched, you can undo the override.
For every test:
setUpAll(() => HttpOverrides.global = null);
For a single test:
testWidgets('Image gets correctly fetched.', (tester) {
HttpOverrides.runZoned(
// Run your tests.
() {},
createHttpClient: (securityContext) => MockHttpClient(securityContext),
);
});
Upvotes: 19
Reputation: 652
I updated the code from Günter Zöchbauer answer using null safety and the mocktail package.
image_mock_http_client.dart
import 'dart:io';
import 'package:mocktail/mocktail.dart';
class MockHttpOverrides extends HttpOverrides {
MockHttpOverrides(this.data);
final Map<Uri, List<int>> data;
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = MockHttpClient();
final request = MockHttpClientRequest();
final response = MockHttpClientResponse(data);
final headers = MockHttpHeaders();
/// Comment the exception when stub is missing from client
/// because it complains about missing autoUncompress stub
/// even setting it up as shown bellow.
// throwOnMissingStub(client);
throwOnMissingStub(request);
throwOnMissingStub(response);
throwOnMissingStub(headers);
// This line is not necessary, it can be omitted.
when(() => client.autoUncompress).thenReturn(true);
// Use decompressed, otherwise you will get bad data.
when(() => response.compressionState)
.thenReturn(HttpClientResponseCompressionState.decompressed);
// Capture the url and assigns it to requestedUrl from MockHttpClientResponse.
when(() => client.getUrl(captureAny())).thenAnswer((invocation) {
response.requestedUrl = invocation.positionalArguments[0] as Uri;
return Future<HttpClientRequest>.value(request);
});
// This line is not necessary, it can be omitted.
when(() => request.headers).thenAnswer((_) => headers);
when(() => request.close())
.thenAnswer((_) => Future<HttpClientResponse>.value(response));
when(() => response.contentLength)
.thenAnswer((_) => data[response.requestedUrl]!.length);
when(() => response.statusCode).thenReturn(HttpStatus.ok);
when(
() => response.listen(
captureAny(),
cancelOnError: captureAny(named: 'cancelOnError'),
onDone: captureAny(named: 'onDone'),
onError: captureAny(named: 'onError'),
),
).thenAnswer((invocation) {
final onData =
invocation.positionalArguments[0] as void Function(List<int>);
final onDone = invocation.namedArguments[#onDone] as void Function();
final onError = invocation.namedArguments[#onError] as void
Function(Object, [StackTrace]);
final cancelOnError = invocation.namedArguments[#cancelOnError] as bool;
return Stream<List<int>>.fromIterable([data[response.requestedUrl]!])
.listen(
onData,
onDone: onDone,
onError: onError,
cancelOnError: cancelOnError,
);
});
return client;
}
}
class MockHttpClient extends Mock implements HttpClient {}
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
class MockHttpClientResponse extends Mock implements HttpClientResponse {
MockHttpClientResponse(this.data);
final Map<Uri, List<int>> data;
Uri? requestedUrl;
// It is not necessary to override this method to pass the test.
@override
Future<S> fold<S>(
S initialValue,
S Function(S previous, List<int> element) combine,
) {
return Stream.fromIterable([data[requestedUrl]])
.fold(initialValue, combine as S Function(S, List<int>?));
}
}
class MockHttpHeaders extends Mock implements HttpHeaders {}
my_test.dart
const _imageUrl = 'https://your.image.uri.here';
void main() {
setUp(() async {
registerFallbackValue(Uri());
// Load an image from assets and transform it from bytes to List<int>
final _imageByteData = await rootBundle.load('assets/images/image.png');
final _imageIntList = _imageByteData.buffer.asInt8List();
final _requestsMap = {
Uri.parse(_imageUrl): _imageIntList,
};
HttpOverrides.global = MockHttpOverrides(_requestsMap);
});
...
}
Upvotes: 5
Reputation: 4544
A few years later and now that image_test_utils package seems no longer maintained, here is another easy solution to this problem.
I used the network_image_mock package (supports nullsafety) and added just two lines of code to my test. Wrap your pumpWidget
call with mockNetworkImagesFor
like this and you won't get the image loading errors anymore:
mockNetworkImagesFor(() => tester.pumpWidget(makeTestableWidget()));
Upvotes: 23
Reputation: 5239
In widget tests, the default HTTP client has been replaced with one that always returns 400s. There's a sample on how to do this in the flutter_markdown repo along with couple other places. I used to copy and paste this to every project, but I did it enough times to get quite bored.
There's now a library for this (by me), called "image_test_utils". You can wrap your widget tests with a provideMockedNetworkImages
method, which replaces the mocked HTTP client with one that always returns transparent images. Which in turn makes your tests pass.
pubspec.yaml:
dev_dependencies:
image_test_utils: ^1.0.0
my_image_test.dart:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image_test_utils/image_test_utils.dart';
void main() {
testWidgets('my image test', (WidgetTester tester) async {
provideMockedNetworkImages(() async {
/// Now we can pump NetworkImages without crashing our tests. Yay!
await tester.pumpWidget(
MaterialApp(
home: Image.network('https://example.com/image.png'),
),
);
/// No crashes.
});
});
}
Upvotes: 30
Reputation: 657929
I use
import 'package:flutter/services.dart' show createHttpClient;
final imageUri = Uri.parse('http://example.com$dummyImagePath');
testWidgets( ...) {
createHttpClient = createMockImageHttpClient;
await tester.pumpWidget(new TestWrapperWidget(
child: (_) => new ImageWidget(name: text, url: imageUri)));
}
import 'dart:async' show Future;
import 'package:http/http.dart' show Client, Response;
import 'package:http/testing.dart' show MockClient;
import 'dummy_image_data.dart'
show dummyImageData;
const String dummyImagePath = '/image.jpg';
Client createMockImageHttpClient() => new MockClient((request) {
switch (request.url.path) {
case dummyImagePath:
return new Future<Response>.value(new Response.bytes(
dummyImageData, 200,
request: request, headers: {'Content-type': 'image/jpg'}));
default:
return new Future<Response>.value(new Response('', 404));
}
});
Uint8List get dummyImageData => BASE64.decode(dummyJpgImageBase64);
(I created the image data Base64 using http://base64.wutils.com/encoding-online/)
const String dummyAvatarJpgImageBase64 =
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIi'
...
'itf93F+MLRdehP4ZutvWj8m+rjzpz//Z';
This way the test also works when I start it with flutter run -t test/image_test.dart
, but the image data can also be just served from an image file for normal test runs.
Using the mockito
package
image_mock_http_client.dart
import 'dart:async' show Future, Stream;
import 'dart:io'
show
HttpClient,
HttpClientRequest,
HttpClientResponse,
HttpHeaders,
HttpOverrides,
HttpStatus,
SecurityContext;
import '.dummy_image_data.dart';
import 'package:mockito/mockito.dart'
show Mock, any, anyNamed, captureAny, throwOnMissingStub, when;
const String dummyAvatarImagePath = '/avatar.jpg';
class TestHttpOverrides extends HttpOverrides {
TestHttpOverrides(this.data);
final Map<Uri, List<int>> data;
@override
HttpClient createHttpClient(SecurityContext context) =>
createMockImageHttpClient(context, data);
}
// Returns a mock HTTP client that responds with an image to all requests.
MockHttpClient createMockImageHttpClient(
SecurityContext _, Map<Uri, List<int>> data) {
final client = new MockHttpClient();
final request = new MockHttpClientRequest();
final response = new MockHttpClientResponse(data);
final headers = new MockHttpHeaders();
throwOnMissingStub(client);
throwOnMissingStub(request);
throwOnMissingStub(response);
throwOnMissingStub(headers);
when<dynamic>(client.getUrl(captureAny)).thenAnswer((invocation) {
response.requestedUrl = invocation.positionalArguments[0] as Uri;
return new Future<HttpClientRequest>.value(request);
});
when(request.headers).thenAnswer((_) => headers);
when(request.close())
.thenAnswer((_) => new Future<HttpClientResponse>.value(response));
when(response.contentLength)
.thenAnswer((_) => data[response.requestedUrl].length);
when(response.statusCode).thenReturn(HttpStatus.ok);
when(
response.listen(
any,
cancelOnError: anyNamed('cancelOnError'),
onDone: anyNamed('onDone'),
onError: anyNamed('onError'),
),
).thenAnswer((invocation) {
final onData =
invocation.positionalArguments[0] as void Function(List<int>);
final onDone = invocation.namedArguments[#onDone] as void Function();
final onError = invocation.namedArguments[#onError] as void Function(Object,
[StackTrace]);
final cancelOnError = invocation.namedArguments[#cancelOnError] as bool;
return new Stream<List<int>>.fromIterable([data[response.requestedUrl]])
.listen(onData,
onDone: onDone, onError: onError, cancelOnError: cancelOnError);
});
return client;
}
class MockHttpClient extends Mock implements HttpClient {}
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
class MockHttpClientResponse extends Mock implements HttpClientResponse {
MockHttpClientResponse(this.data);
final Map<Uri, List<int>> data;
Uri requestedUrl;
@override
Future<S> fold<S>(S initialValue, S combine(S previous, List<int> element)) =>
new Stream.fromIterable([data[requestedUrl]]).fold(initialValue, combine);
}
class MockHttpHeaders extends Mock implements HttpHeaders {}
my_test.dart
import 'image_mock_http_client.dart' show TestHttpOverrides;
...
setUp(() async {
HttpOverrides.global = new TestHttpOverrides({
'http://example.com/my_image.png': dummyAvatarImageData,
'http://example.com/other_image.png: dummyPngImageData,
});
});
dummyAvatarImageData
and dummyPngImageData
are list<int>
and contain the image data.
Upvotes: 12