1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:async'; |
6 | import 'dart:convert'; |
7 | import 'dart:io'; |
8 | |
9 | import 'package:flutter/foundation.dart'; |
10 | import 'package:flutter/services.dart'; |
11 | import 'package:path/path.dart'as path; |
12 | import 'package:test_api/scaffolding.dart'as test_package; |
13 | |
14 | import 'binding.dart'; |
15 | |
16 | /// Ensure the appropriate test binding is initialized. |
17 | TestWidgetsFlutterBinding ensureInitialized([@visibleForTesting Map<String, String>? environment]) { |
18 | environment ??= Platform.environment; |
19 | if (environment.containsKey('FLUTTER_TEST') && environment[ 'FLUTTER_TEST'] != 'false') { |
20 | return AutomatedTestWidgetsFlutterBinding.ensureInitialized(); |
21 | } |
22 | return LiveTestWidgetsFlutterBinding.ensureInitialized(); |
23 | } |
24 | |
25 | /// Setup mocking of the global [HttpClient]. |
26 | void setupHttpOverrides() { |
27 | HttpOverrides.global = _MockHttpOverrides(); |
28 | } |
29 | |
30 | /// Setup mocking of platform assets if `UNIT_TEST_ASSETS` is defined. |
31 | void mockFlutterAssets() { |
32 | if (!Platform.environment.containsKey('UNIT_TEST_ASSETS')) { |
33 | return; |
34 | } |
35 | final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS']!; |
36 | assert(Platform.environment['APP_NAME'] != null); |
37 | final String prefix = 'packages/${Platform.environment[ 'APP_NAME']!} /'; |
38 | |
39 | /// Navigation related actions (pop, push, replace) broadcasts these actions via |
40 | /// platform messages. |
41 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( |
42 | SystemChannels.navigation, |
43 | (MethodCall methodCall) async { |
44 | return null; |
45 | }, |
46 | ); |
47 | |
48 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMessageHandler( |
49 | 'flutter/assets', |
50 | (ByteData? message) { |
51 | assert(message != null); |
52 | String key = utf8.decode(message!.buffer.asUint8List()); |
53 | File asset = File(path.join(assetFolderPath, key)); |
54 | |
55 | if (!asset.existsSync()) { |
56 | // For tests in package, it will load assets with its own package prefix. |
57 | // In this case, we do a best-effort look up. |
58 | if (!key.startsWith(prefix)) { |
59 | return null; |
60 | } |
61 | |
62 | key = key.replaceFirst(prefix, ''); |
63 | asset = File(path.join(assetFolderPath, key)); |
64 | if (!asset.existsSync()) { |
65 | return null; |
66 | } |
67 | } |
68 | |
69 | final Uint8List encoded = Uint8List.fromList(asset.readAsBytesSync()); |
70 | return SynchronousFuture<ByteData>(encoded.buffer.asByteData()); |
71 | }, |
72 | ); |
73 | } |
74 | |
75 | /// Provides a default [HttpClient] which always returns empty 400 responses. |
76 | /// |
77 | /// If another [HttpClient] is provided using [HttpOverrides.runZoned], that will |
78 | /// take precedence over this provider. |
79 | class _MockHttpOverrides extends HttpOverrides { |
80 | bool warningPrinted = false; |
81 | @override |
82 | HttpClient createHttpClient(SecurityContext? context) { |
83 | if (!warningPrinted) { |
84 | test_package.printOnFailure( |
85 | 'Warning: At least one test in this suite creates an HttpClient. When ' |
86 | 'running a test suite that uses TestWidgetsFlutterBinding, all HTTP ' |
87 | 'requests will return status code 400, and no network request will ' |
88 | 'actually be made. Any test expecting a real network connection and ' |
89 | 'status code will fail.\n' |
90 | 'To test code that needs an HttpClient, provide your own HttpClient ' |
91 | 'implementation to the code under test, so that your test can ' |
92 | 'consistently provide a testable response to the code under test.' |
93 | .split('\n') |
94 | .expand<String>((String line) => debugWordWrap(line, FlutterError.wrapWidth)) |
95 | .join('\n'), |
96 | ); |
97 | warningPrinted = true; |
98 | } |
99 | return _MockHttpClient(); |
100 | } |
101 | } |
102 | |
103 | /// A mocked [HttpClient] which always returns a [_MockHttpRequest]. |
104 | class _MockHttpClient implements HttpClient { |
105 | @override |
106 | bool autoUncompress = true; |
107 | |
108 | @override |
109 | Duration? connectionTimeout; |
110 | |
111 | @override |
112 | Duration idleTimeout = const Duration(seconds: 15); |
113 | |
114 | @override |
115 | int? maxConnectionsPerHost; |
116 | |
117 | @override |
118 | String? userAgent; |
119 | |
120 | @override |
121 | void addCredentials(Uri url, String realm, HttpClientCredentials credentials) {} |
122 | |
123 | @override |
124 | void addProxyCredentials( |
125 | String host, |
126 | int port, |
127 | String realm, |
128 | HttpClientCredentials credentials, |
129 | ) {} |
130 | |
131 | @override |
132 | Future<ConnectionTask<Socket>> Function(Uri url, String? proxyHost, int? proxyPort)? |
133 | connectionFactory; |
134 | |
135 | @override |
136 | Future<bool> Function(Uri url, String scheme, String realm)? authenticate; |
137 | |
138 | @override |
139 | Future<bool> Function(String host, int port, String scheme, String realm)? authenticateProxy; |
140 | |
141 | @override |
142 | bool Function(X509Certificate cert, String host, int port)? badCertificateCallback; |
143 | |
144 | @override |
145 | void Function(String line)? keyLog; |
146 | |
147 | @override |
148 | void close({bool force = false}) {} |
149 | |
150 | @override |
151 | Future<HttpClientRequest> delete(String host, int port, String path) { |
152 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
153 | } |
154 | |
155 | @override |
156 | Future<HttpClientRequest> deleteUrl(Uri url) { |
157 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
158 | } |
159 | |
160 | @override |
161 | String Function(Uri url)? findProxy; |
162 | |
163 | @override |
164 | Future<HttpClientRequest> get(String host, int port, String path) { |
165 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
166 | } |
167 | |
168 | @override |
169 | Future<HttpClientRequest> getUrl(Uri url) { |
170 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
171 | } |
172 | |
173 | @override |
174 | Future<HttpClientRequest> head(String host, int port, String path) { |
175 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
176 | } |
177 | |
178 | @override |
179 | Future<HttpClientRequest> headUrl(Uri url) { |
180 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
181 | } |
182 | |
183 | @override |
184 | Future<HttpClientRequest> open(String method, String host, int port, String path) { |
185 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
186 | } |
187 | |
188 | @override |
189 | Future<HttpClientRequest> openUrl(String method, Uri url) { |
190 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
191 | } |
192 | |
193 | @override |
194 | Future<HttpClientRequest> patch(String host, int port, String path) { |
195 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
196 | } |
197 | |
198 | @override |
199 | Future<HttpClientRequest> patchUrl(Uri url) { |
200 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
201 | } |
202 | |
203 | @override |
204 | Future<HttpClientRequest> post(String host, int port, String path) { |
205 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
206 | } |
207 | |
208 | @override |
209 | Future<HttpClientRequest> postUrl(Uri url) { |
210 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
211 | } |
212 | |
213 | @override |
214 | Future<HttpClientRequest> put(String host, int port, String path) { |
215 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
216 | } |
217 | |
218 | @override |
219 | Future<HttpClientRequest> putUrl(Uri url) { |
220 | return Future<HttpClientRequest>.value(_MockHttpRequest()); |
221 | } |
222 | } |
223 | |
224 | /// A mocked [HttpClientRequest] which always returns a [_MockHttpResponse]. |
225 | class _MockHttpRequest implements HttpClientRequest { |
226 | @override |
227 | bool bufferOutput = true; |
228 | |
229 | @override |
230 | int contentLength = -1; |
231 | |
232 | @override |
233 | late Encoding encoding; |
234 | |
235 | @override |
236 | bool followRedirects = true; |
237 | |
238 | @override |
239 | final HttpHeaders headers = _MockHttpHeaders(); |
240 | |
241 | @override |
242 | void add(List<int> data) {} |
243 | |
244 | @override |
245 | void addError(Object error, [StackTrace? stackTrace]) {} |
246 | |
247 | @override |
248 | Future<void> addStream(Stream<List<int>> stream) { |
249 | return Future<void>.value(); |
250 | } |
251 | |
252 | @override |
253 | Future<HttpClientResponse> close() { |
254 | return Future<HttpClientResponse>.value(_MockHttpResponse()); |
255 | } |
256 | |
257 | @override |
258 | void abort([Object? exception, StackTrace? stackTrace]) {} |
259 | |
260 | @override |
261 | HttpConnectionInfo? get connectionInfo => null; |
262 | |
263 | @override |
264 | List<Cookie> get cookies => <Cookie>[]; |
265 | |
266 | @override |
267 | Future<HttpClientResponse> get done async => _MockHttpResponse(); |
268 | |
269 | @override |
270 | Future<void> flush() { |
271 | return Future<void>.value(); |
272 | } |
273 | |
274 | @override |
275 | int maxRedirects = 5; |
276 | |
277 | @override |
278 | String get method => ''; |
279 | |
280 | @override |
281 | bool persistentConnection = true; |
282 | |
283 | @override |
284 | Uri get uri => Uri(); |
285 | |
286 | @override |
287 | void write(Object? obj) {} |
288 | |
289 | @override |
290 | void writeAll(Iterable<dynamic> objects, [String separator = '']) {} |
291 | |
292 | @override |
293 | void writeCharCode(int charCode) {} |
294 | |
295 | @override |
296 | void writeln([Object? obj = '']) {} |
297 | } |
298 | |
299 | /// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 400. |
300 | // TODO(tvolkert): Change to `extends Stream |
301 | // https://dart-review.googlesource.com/c/sdk/+/104525 is rolled into the framework. |
302 | class _MockHttpResponse implements HttpClientResponse { |
303 | final Stream<Uint8List> _delegate = Stream<Uint8List>.fromIterable( |
304 | const Iterable<Uint8List>.empty(), |
305 | ); |
306 | |
307 | @override |
308 | final HttpHeaders headers = _MockHttpHeaders(); |
309 | |
310 | @override |
311 | X509Certificate? get certificate => null; |
312 | |
313 | @override |
314 | HttpConnectionInfo? get connectionInfo => null; |
315 | |
316 | @override |
317 | int get contentLength => -1; |
318 | |
319 | @override |
320 | HttpClientResponseCompressionState get compressionState { |
321 | return HttpClientResponseCompressionState.decompressed; |
322 | } |
323 | |
324 | @override |
325 | List<Cookie> get cookies => <Cookie>[]; |
326 | |
327 | @override |
328 | Future<Socket> detachSocket() { |
329 | return Future<Socket>.error(UnsupportedError('Mocked response')); |
330 | } |
331 | |
332 | @override |
333 | bool get isRedirect => false; |
334 | |
335 | @override |
336 | StreamSubscription<Uint8List> listen( |
337 | void Function(Uint8List event)? onData, { |
338 | Function? onError, |
339 | void Function()? onDone, |
340 | bool? cancelOnError, |
341 | }) { |
342 | return const Stream<Uint8List>.empty().listen( |
343 | onData, |
344 | onError: onError, |
345 | onDone: onDone, |
346 | cancelOnError: cancelOnError, |
347 | ); |
348 | } |
349 | |
350 | @override |
351 | bool get persistentConnection => false; |
352 | |
353 | @override |
354 | String get reasonPhrase => ''; |
355 | |
356 | @override |
357 | Future<HttpClientResponse> redirect([String? method, Uri? url, bool? followLoops]) { |
358 | return Future<HttpClientResponse>.error(UnsupportedError('Mocked response')); |
359 | } |
360 | |
361 | @override |
362 | List<RedirectInfo> get redirects => <RedirectInfo>[]; |
363 | |
364 | @override |
365 | int get statusCode => 400; |
366 | |
367 | @override |
368 | Future<bool> any(bool Function(Uint8List element) test) { |
369 | return _delegate.any(test); |
370 | } |
371 | |
372 | @override |
373 | Stream<Uint8List> asBroadcastStream({ |
374 | void Function(StreamSubscription<Uint8List> subscription)? onListen, |
375 | void Function(StreamSubscription<Uint8List> subscription)? onCancel, |
376 | }) { |
377 | return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel); |
378 | } |
379 | |
380 | @override |
381 | Stream<E> asyncExpand<E>(Stream<E>? Function(Uint8List event) convert) { |
382 | return _delegate.asyncExpand<E>(convert); |
383 | } |
384 | |
385 | @override |
386 | Stream<E> asyncMap<E>(FutureOr<E> Function(Uint8List event) convert) { |
387 | return _delegate.asyncMap<E>(convert); |
388 | } |
389 | |
390 | @override |
391 | Stream<R> cast<R>() { |
392 | return _delegate.cast<R>(); |
393 | } |
394 | |
395 | @override |
396 | Future<bool> contains(Object? needle) { |
397 | return _delegate.contains(needle); |
398 | } |
399 | |
400 | @override |
401 | Stream<Uint8List> distinct([bool Function(Uint8List previous, Uint8List next)? equals]) { |
402 | return _delegate.distinct(equals); |
403 | } |
404 | |
405 | @override |
406 | Future<E> drain<E>([E? futureValue]) { |
407 | return _delegate.drain<E>(futureValue); |
408 | } |
409 | |
410 | @override |
411 | Future<Uint8List> elementAt(int index) { |
412 | return _delegate.elementAt(index); |
413 | } |
414 | |
415 | @override |
416 | Future<bool> every(bool Function(Uint8List element) test) { |
417 | return _delegate.every(test); |
418 | } |
419 | |
420 | @override |
421 | Stream<S> expand<S>(Iterable<S> Function(Uint8List element) convert) { |
422 | return _delegate.expand(convert); |
423 | } |
424 | |
425 | @override |
426 | Future<Uint8List> get first => _delegate.first; |
427 | |
428 | @override |
429 | Future<Uint8List> firstWhere( |
430 | bool Function(Uint8List element) test, { |
431 | List<int> Function()? orElse, |
432 | }) { |
433 | return _delegate.firstWhere( |
434 | test, |
435 | orElse: |
436 | orElse == null |
437 | ? null |
438 | : () { |
439 | return Uint8List.fromList(orElse()); |
440 | }, |
441 | ); |
442 | } |
443 | |
444 | @override |
445 | Future<S> fold<S>(S initialValue, S Function(S previous, Uint8List element) combine) { |
446 | return _delegate.fold<S>(initialValue, combine); |
447 | } |
448 | |
449 | @override |
450 | Future<dynamic> forEach(void Function(Uint8List element) action) { |
451 | return _delegate.forEach(action); |
452 | } |
453 | |
454 | @override |
455 | Stream<Uint8List> handleError(Function onError, {bool Function(dynamic error)? test}) { |
456 | return _delegate.handleError(onError, test: test); |
457 | } |
458 | |
459 | @override |
460 | bool get isBroadcast => _delegate.isBroadcast; |
461 | |
462 | @override |
463 | Future<bool> get isEmpty => _delegate.isEmpty; |
464 | |
465 | @override |
466 | Future<String> join([String separator = '']) { |
467 | return _delegate.join(separator); |
468 | } |
469 | |
470 | @override |
471 | Future<Uint8List> get last => _delegate.last; |
472 | |
473 | @override |
474 | Future<Uint8List> lastWhere( |
475 | bool Function(Uint8List element) test, { |
476 | List<int> Function()? orElse, |
477 | }) { |
478 | return _delegate.lastWhere( |
479 | test, |
480 | orElse: |
481 | orElse == null |
482 | ? null |
483 | : () { |
484 | return Uint8List.fromList(orElse()); |
485 | }, |
486 | ); |
487 | } |
488 | |
489 | @override |
490 | Future<int> get length => _delegate.length; |
491 | |
492 | @override |
493 | Stream<S> map<S>(S Function(Uint8List event) convert) { |
494 | return _delegate.map<S>(convert); |
495 | } |
496 | |
497 | @override |
498 | Future<dynamic> pipe(StreamConsumer<List<int>> streamConsumer) { |
499 | return _delegate.cast<List<int>>().pipe(streamConsumer); |
500 | } |
501 | |
502 | @override |
503 | Future<Uint8List> reduce(List<int> Function(Uint8List previous, Uint8List element) combine) { |
504 | return _delegate.reduce((Uint8List previous, Uint8List element) { |
505 | return Uint8List.fromList(combine(previous, element)); |
506 | }); |
507 | } |
508 | |
509 | @override |
510 | Future<Uint8List> get single => _delegate.single; |
511 | |
512 | @override |
513 | Future<Uint8List> singleWhere( |
514 | bool Function(Uint8List element) test, { |
515 | List<int> Function()? orElse, |
516 | }) { |
517 | return _delegate.singleWhere( |
518 | test, |
519 | orElse: |
520 | orElse == null |
521 | ? null |
522 | : () { |
523 | return Uint8List.fromList(orElse()); |
524 | }, |
525 | ); |
526 | } |
527 | |
528 | @override |
529 | Stream<Uint8List> skip(int count) { |
530 | return _delegate.skip(count); |
531 | } |
532 | |
533 | @override |
534 | Stream<Uint8List> skipWhile(bool Function(Uint8List element) test) { |
535 | return _delegate.skipWhile(test); |
536 | } |
537 | |
538 | @override |
539 | Stream<Uint8List> take(int count) { |
540 | return _delegate.take(count); |
541 | } |
542 | |
543 | @override |
544 | Stream<Uint8List> takeWhile(bool Function(Uint8List element) test) { |
545 | return _delegate.takeWhile(test); |
546 | } |
547 | |
548 | @override |
549 | Stream<Uint8List> timeout( |
550 | Duration timeLimit, { |
551 | void Function(EventSink<Uint8List> sink)? onTimeout, |
552 | }) { |
553 | return _delegate.timeout(timeLimit, onTimeout: onTimeout); |
554 | } |
555 | |
556 | @override |
557 | Future<List<Uint8List>> toList() { |
558 | return _delegate.toList(); |
559 | } |
560 | |
561 | @override |
562 | Future<Set<Uint8List>> toSet() { |
563 | return _delegate.toSet(); |
564 | } |
565 | |
566 | @override |
567 | Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) { |
568 | return _delegate.cast<List<int>>().transform<S>(streamTransformer); |
569 | } |
570 | |
571 | @override |
572 | Stream<Uint8List> where(bool Function(Uint8List event) test) { |
573 | return _delegate.where(test); |
574 | } |
575 | } |
576 | |
577 | /// A mocked [HttpHeaders] that ignores all writes. |
578 | class _MockHttpHeaders implements HttpHeaders { |
579 | @override |
580 | List<String>? operator [](String name) => <String>[]; |
581 | |
582 | @override |
583 | void add(String name, Object value, {bool preserveHeaderCase = false}) {} |
584 | |
585 | @override |
586 | late bool chunkedTransferEncoding; |
587 | |
588 | @override |
589 | void clear() {} |
590 | |
591 | @override |
592 | int contentLength = -1; |
593 | |
594 | @override |
595 | ContentType? contentType; |
596 | |
597 | @override |
598 | DateTime? date; |
599 | |
600 | @override |
601 | DateTime? expires; |
602 | |
603 | @override |
604 | void forEach(void Function(String name, List<String> values) f) {} |
605 | |
606 | @override |
607 | String? host; |
608 | |
609 | @override |
610 | DateTime? ifModifiedSince; |
611 | |
612 | @override |
613 | void noFolding(String name) {} |
614 | |
615 | @override |
616 | late bool persistentConnection; |
617 | |
618 | @override |
619 | int? port; |
620 | |
621 | @override |
622 | void remove(String name, Object value) {} |
623 | |
624 | @override |
625 | void removeAll(String name) {} |
626 | |
627 | @override |
628 | void set(String name, Object value, {bool preserveHeaderCase = false}) {} |
629 | |
630 | @override |
631 | String? value(String name) => null; |
632 | } |
633 |
Definitions
- ensureInitialized
- setupHttpOverrides
- mockFlutterAssets
- _MockHttpOverrides
- createHttpClient
- _MockHttpClient
- addCredentials
- addProxyCredentials
- close
- delete
- deleteUrl
- get
- getUrl
- head
- headUrl
- open
- openUrl
- patch
- patchUrl
- post
- postUrl
- put
- putUrl
- _MockHttpRequest
- add
- addError
- addStream
- close
- abort
- connectionInfo
- cookies
- done
- flush
- method
- uri
- write
- writeAll
- writeCharCode
- writeln
- _MockHttpResponse
- certificate
- connectionInfo
- contentLength
- compressionState
- cookies
- detachSocket
- isRedirect
- listen
- persistentConnection
- reasonPhrase
- redirect
- redirects
- statusCode
- any
- asBroadcastStream
- asyncExpand
- asyncMap
- cast
- contains
- distinct
- drain
- elementAt
- every
- expand
- first
- firstWhere
- fold
- forEach
- handleError
- isBroadcast
- isEmpty
- join
- last
- lastWhere
- length
- map
- pipe
- reduce
- single
- singleWhere
- skip
- skipWhile
- take
- takeWhile
- timeout
- toList
- toSet
- transform
- where
- _MockHttpHeaders
- []
- add
- clear
- forEach
- noFolding
- remove
- removeAll
- set
Learn more about Flutter for embedded and desktop on industrialflutter.com