blob: 19ec7dbd1fa7ee06db095122a2d92f3dfb2d5ead [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as path;
// ignore: deprecated_member_use
import 'package:test_api/test_api.dart' as test_package;
import 'binding.dart';
/// Ensure the [WidgetsBinding] is initialized.
WidgetsBinding ensureInitialized([@visibleForTesting Map<String, String>? environment]) {
environment ??= Platform.environment;
if (WidgetsBinding.instance == null) {
if (environment.containsKey('FLUTTER_TEST') && environment['FLUTTER_TEST'] != 'false') {
AutomatedTestWidgetsFlutterBinding();
} else {
LiveTestWidgetsFlutterBinding();
}
}
assert(WidgetsBinding.instance is TestWidgetsFlutterBinding);
return WidgetsBinding.instance!;
}
/// Setup mocking of the global [HttpClient].
void setupHttpOverrides() {
HttpOverrides.global = _MockHttpOverrides();
}
/// Setup mocking of platform assets if `UNIT_TEST_ASSETS` is defined.
void mockFlutterAssets() {
if (!Platform.environment.containsKey('UNIT_TEST_ASSETS')) {
return;
}
final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS']!;
assert(Platform.environment['APP_NAME'] != null);
final String prefix = 'packages/${Platform.environment['APP_NAME']!}/';
/// Navigation related actions (pop, push, replace) broadcasts these actions via
/// platform messages.
SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {});
ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData? message) async {
assert(message != null);
String key = utf8.decode(message!.buffer.asUint8List());
File asset = File(path.join(assetFolderPath, key));
if (!asset.existsSync()) {
// For tests in package, it will load assets with its own package prefix.
// In this case, we do a best-effort look up.
if (!key.startsWith(prefix)) {
return null;
}
key = key.replaceFirst(prefix, '');
asset = File(path.join(assetFolderPath, key));
if (!asset.existsSync()) {
return null;
}
}
final Uint8List encoded = Uint8List.fromList(asset.readAsBytesSync());
return Future<ByteData>.value(encoded.buffer.asByteData());
});
}
/// Provides a default [HttpClient] which always returns empty 400 responses.
///
/// If another [HttpClient] is provided using [HttpOverrides.runZoned], that will
/// take precedence over this provider.
class _MockHttpOverrides extends HttpOverrides {
bool warningPrinted = false;
@override
HttpClient createHttpClient(SecurityContext? _) {
if (!warningPrinted) {
test_package.printOnFailure(
'Warning: At least one test in this suite creates an HttpClient. When\n'
'running a test suite that uses TestWidgetsFlutterBinding, all HTTP\n'
'requests will return status code 400, and no network request will\n'
'actually be made. Any test expecting a real network connection and\n'
'status code will fail.\n'
'To test code that needs an HttpClient, provide your own HttpClient\n'
'implementation to the code under test, so that your test can\n'
'consistently provide a testable response to the code under test.');
warningPrinted = true;
}
return _MockHttpClient();
}
}
/// A mocked [HttpClient] which always returns a [_MockHttpRequest].
class _MockHttpClient implements HttpClient {
@override
bool autoUncompress = true;
@override
Duration? connectionTimeout;
@override
Duration idleTimeout = const Duration(seconds: 15);
@override
int? maxConnectionsPerHost;
@override
String? userAgent;
@override
void addCredentials(Uri url, String realm, HttpClientCredentials credentials) { }
@override
void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) { }
@override
set authenticate(Future<bool> Function(Uri url, String scheme, String realm)? f) { }
@override
set authenticateProxy(Future<bool> Function(String host, int port, String scheme, String realm)? f) { }
@override
set badCertificateCallback(bool Function(X509Certificate cert, String host, int port)? callback) { }
@override
void close({ bool force = false }) { }
@override
Future<HttpClientRequest> delete(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> deleteUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
set findProxy(String Function(Uri url)? f) { }
@override
Future<HttpClientRequest> get(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> getUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> head(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> headUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> open(String method, String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> openUrl(String method, Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> patch(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> patchUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> post(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> postUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> put(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> putUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
}
/// A mocked [HttpClientRequest] which always returns a [_MockHttpClientResponse].
class _MockHttpRequest extends HttpClientRequest {
@override
late Encoding encoding;
@override
final HttpHeaders headers = _MockHttpHeaders();
@override
void add(List<int> data) { }
@override
void addError(Object error, [ StackTrace? stackTrace ]) { }
@override
Future<void> addStream(Stream<List<int>> stream) {
return Future<void>.value();
}
@override
Future<HttpClientResponse> close() {
return Future<HttpClientResponse>.value(_MockHttpResponse());
}
@override
void abort([Object? exception, StackTrace? stackTrace]) {}
@override
HttpConnectionInfo? get connectionInfo => null;
@override
List<Cookie> get cookies => <Cookie>[];
@override
Future<HttpClientResponse> get done async => _MockHttpResponse();
@override
Future<void> flush() {
return Future<void>.value();
}
@override
String get method => '';
@override
Uri get uri => Uri();
@override
void write(Object? obj) { }
@override
void writeAll(Iterable<dynamic> objects, [ String separator = '' ]) { }
@override
void writeCharCode(int charCode) { }
@override
void writeln([ Object? obj = '' ]) { }
}
/// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 400.
// TODO(tvolkert): Change to `extends Stream<Uint8List>` once
// https://dart-review.googlesource.com/c/sdk/+/104525 is rolled into the framework.
class _MockHttpResponse implements HttpClientResponse {
final Stream<Uint8List> _delegate = Stream<Uint8List>.fromIterable(const Iterable<Uint8List>.empty());
@override
final HttpHeaders headers = _MockHttpHeaders();
@override
X509Certificate? get certificate => null;
@override
HttpConnectionInfo? get connectionInfo => null;
@override
int get contentLength => -1;
@override
HttpClientResponseCompressionState get compressionState {
return HttpClientResponseCompressionState.decompressed;
}
@override
List<Cookie> get cookies => <Cookie>[];
@override
Future<Socket> detachSocket() {
return Future<Socket>.error(UnsupportedError('Mocked response'));
}
@override
bool get isRedirect => false;
@override
StreamSubscription<Uint8List> listen(void Function(Uint8List event)? onData, { Function? onError, void Function()? onDone, bool? cancelOnError }) {
return const Stream<Uint8List>.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
}
@override
bool get persistentConnection => false;
@override
String get reasonPhrase => '';
@override
Future<HttpClientResponse> redirect([ String? method, Uri? url, bool? followLoops ]) {
return Future<HttpClientResponse>.error(UnsupportedError('Mocked response'));
}
@override
List<RedirectInfo> get redirects => <RedirectInfo>[];
@override
int get statusCode => 400;
@override
Future<bool> any(bool Function(Uint8List element) test) {
return _delegate.any(test);
}
@override
Stream<Uint8List> asBroadcastStream({
void Function(StreamSubscription<Uint8List> subscription)? onListen,
void Function(StreamSubscription<Uint8List> subscription)? onCancel,
}) {
return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel);
}
@override
Stream<E> asyncExpand<E>(Stream<E>? Function(Uint8List event) convert) {
return _delegate.asyncExpand<E>(convert);
}
@override
Stream<E> asyncMap<E>(FutureOr<E> Function(Uint8List event) convert) {
return _delegate.asyncMap<E>(convert);
}
@override
Stream<R> cast<R>() {
return _delegate.cast<R>();
}
@override
Future<bool> contains(Object? needle) {
return _delegate.contains(needle);
}
@override
Stream<Uint8List> distinct([bool Function(Uint8List previous, Uint8List next)? equals]) {
return _delegate.distinct(equals);
}
@override
Future<E> drain<E>([E? futureValue]) {
return _delegate.drain<E>(futureValue);
}
@override
Future<Uint8List> elementAt(int index) {
return _delegate.elementAt(index);
}
@override
Future<bool> every(bool Function(Uint8List element) test) {
return _delegate.every(test);
}
@override
Stream<S> expand<S>(Iterable<S> Function(Uint8List element) convert) {
return _delegate.expand(convert);
}
@override
Future<Uint8List> get first => _delegate.first;
@override
Future<Uint8List> firstWhere(
bool Function(Uint8List element) test, {
List<int> Function()? orElse,
}) {
return _delegate.firstWhere(test, orElse: orElse == null ? null : () {
return Uint8List.fromList(orElse());
});
}
@override
Future<S> fold<S>(S initialValue, S Function(S previous, Uint8List element) combine) {
return _delegate.fold<S>(initialValue, combine);
}
@override
Future<dynamic> forEach(void Function(Uint8List element) action) {
return _delegate.forEach(action);
}
@override
Stream<Uint8List> handleError(
Function onError, {
bool Function(dynamic error)? test,
}) {
return _delegate.handleError(onError, test: test);
}
@override
bool get isBroadcast => _delegate.isBroadcast;
@override
Future<bool> get isEmpty => _delegate.isEmpty;
@override
Future<String> join([String separator = '']) {
return _delegate.join(separator);
}
@override
Future<Uint8List> get last => _delegate.last;
@override
Future<Uint8List> lastWhere(
bool Function(Uint8List element) test, {
List<int> Function()? orElse,
}) {
return _delegate.lastWhere(test, orElse: orElse == null ? null : () {
return Uint8List.fromList(orElse());
});
}
@override
Future<int> get length => _delegate.length;
@override
Stream<S> map<S>(S Function(Uint8List event) convert) {
return _delegate.map<S>(convert);
}
@override
Future<dynamic> pipe(StreamConsumer<List<int>> streamConsumer) {
return _delegate.cast<List<int>>().pipe(streamConsumer);
}
@override
Future<Uint8List> reduce(List<int> Function(Uint8List previous, Uint8List element) combine) {
return _delegate.reduce((Uint8List previous, Uint8List element) {
return Uint8List.fromList(combine(previous, element));
});
}
@override
Future<Uint8List> get single => _delegate.single;
@override
Future<Uint8List> singleWhere(bool Function(Uint8List element) test, {List<int> Function()? orElse}) {
return _delegate.singleWhere(test, orElse: orElse == null ? null : () {
return Uint8List.fromList(orElse());
});
}
@override
Stream<Uint8List> skip(int count) {
return _delegate.skip(count);
}
@override
Stream<Uint8List> skipWhile(bool Function(Uint8List element) test) {
return _delegate.skipWhile(test);
}
@override
Stream<Uint8List> take(int count) {
return _delegate.take(count);
}
@override
Stream<Uint8List> takeWhile(bool Function(Uint8List element) test) {
return _delegate.takeWhile(test);
}
@override
Stream<Uint8List> timeout(
Duration timeLimit, {
void Function(EventSink<Uint8List> sink)? onTimeout,
}) {
return _delegate.timeout(timeLimit, onTimeout: onTimeout);
}
@override
Future<List<Uint8List>> toList() {
return _delegate.toList();
}
@override
Future<Set<Uint8List>> toSet() {
return _delegate.toSet();
}
@override
Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) {
return _delegate.cast<List<int>>().transform<S>(streamTransformer);
}
@override
Stream<Uint8List> where(bool Function(Uint8List event) test) {
return _delegate.where(test);
}
}
/// A mocked [HttpHeaders] that ignores all writes.
class _MockHttpHeaders extends HttpHeaders {
@override
List<String>? operator [](String name) => <String>[];
@override
void add(String name, Object value, {bool preserveHeaderCase = false}) { }
@override
void clear() { }
@override
void forEach(void Function(String name, List<String> values) f) { }
@override
void noFolding(String name) { }
@override
void remove(String name, Object value) { }
@override
void removeAll(String name) { }
@override
void set(String name, Object value, {bool preserveHeaderCase = false}) { }
@override
String? value(String name) => null;
}