原文地址:Testing tidbit #2 - Why does using Image.network crash widget tests?
在沒有真機和模擬器的時候,使用widget tests來測試是很有用的。而且test中的自動點擊、滑動等的模擬,可以爲你節省很多時間,因爲等App運行起來,你可能需要盯着屏幕上的Waiting for emulator to start...
好幾分鐘。
但是,當你在widget tests中使用了Image.network
控件,你可能就沒這麼快樂了。
一段非常簡單的代碼:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('my image test', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Image.network('https://example.com/image.png'),
),
);
/// Crash!
});
}
當你運行這段測試代碼的時候,它會crash。無論你給的url是否正確,都是同樣的結果。你應該會看到類似的報錯信息:
══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
The following _Exception was thrown resolving an image codec:
Exception: HTTP request failed, statusCode: 400, https://example.com/image.png
...
When the exception was thrown, this was the stack:
#0 NetworkImage._loadAsync (package:flutter/src/painting/image_provider.dart:490:7)
<asynchronous suspension>
#1 NetworkImage.load (package:flutter/src/painting/image_provider.dart:469:14)
#2 ImageProvider.resolve.<anonymous closure>.<anonymous closure> (package:flutter/src/painting/image_provider.dart:266:86)
...
Test failed. See exception logs above.
你可能一開始沒法理解Flutter的這種行爲,但是其實這麼做是有道理的。
爲什麼會出現這種情況?
因爲在測試模塊中,默認HTTP請求的返回值永遠都是空的,返回碼都是400 - Bad Request
。換句話說,只要是HTTP請求,就會讓widget tests崩潰。
這種默認的行爲是沒有問題的,因爲在測試代碼中執行一個真實的HTTP操作,會讓我們的測試變慢。還有一個因素就是,HTTP請求會讓我們的測試結果不穩定,沒法預知。因爲可能由於網絡不好或者服務器沒有響應等等我們沒法控制的外部因素,導致測試失敗。但是,我們不需要這些由於外部因素導致的假的測試結果。
所以,解決crash的辦法就是,想辦法攔截HTTP請求。
解決方案
我們可以將默認的HTTP返回全部替換爲200 - OK
,同時返回一張透明的圖片。Flutter官網又一個模擬Http請求的例子:mock_image_http.dart,以及一個 issue 提供了一種爲image tests模擬httpClient的方法,可以看一下。
我將這個解決方案封裝爲了一個小插件 image_test_utils ,你可以這麼用:
- 在
pubspec.yaml
中添加依賴:
dev_dependencies:
image_test_utils: ^1.0.0
- 把你的控件包裹在
provideMockedNetworkImages
方法中:
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.
});
});
}
其實,provideMockedNetworkImages
創建了一個新區(Zone),在這個獨立的區裏面,HTTP client被替換爲了一個假的。這個模擬的client每次都返回200 - OK
和一張透明的圖片,這樣Image.network
控件就不會崩潰了。
要注意的是,```provideMockedNetworkImages``會改寫所有的HTTP GET請求。
關於Zone的概念,建議看下這篇文章:Dart學習筆記(32):Zone分區,應該是中文版的講Zone分區最好的一篇文章了,強烈建議讀一讀。