You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter FadeInImage组件测试:占位符与网络图切换验证求助

Flutter FadeInImage 组件测试用例实现

1. 解决"provide your own HttpClient implementation"错误

Flutter测试环境默认禁止真实网络请求,可通过HttpOverrides提供自定义HttpClient来Mock网络响应,避免触发该错误:

class MockHttpClient extends HttpClient {
  @override
  Future<HttpClientRequest> getUrl(Uri url) async {
    final request = await super.getUrl(url);
    request.headers.set('content-type', 'image/png');
    return MockHttpClientRequest(request, kTransparentImage);
  }
}

class MockHttpClientRequest extends HttpClientRequest {
  final HttpClientRequest _realRequest;
  final Uint8List _mockBytes;

  MockHttpClientRequest(this._realRequest, this._mockBytes);

  @override
  Future<HttpClientResponse> close() async {
    return MockHttpClientResponse(_mockBytes);
  }

  @override
  dynamic noSuchMethod(Invocation invocation) => _realRequest.noSuchMethod(invocation);
}

class MockHttpClientResponse extends HttpClientResponse {
  final Uint8List _bytes;

  MockHttpClientResponse(this._bytes);

  @override
  int get statusCode => 200;

  @override
  Stream<List<int>> listen(void Function(List<int>)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
    return Stream.value(_bytes);
  }

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

class _MockHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return MockHttpClient();
  }
}

在测试前注册Mock客户端:

setUp(() {
  HttpOverrides.global = _MockHttpOverrides();
});

2. 验证初始状态:占位符显示、网络图未加载

测试初始渲染时,kTransparentImage占位符存在,网络图片未显示:

await tester.pumpWidget(
  MaterialApp(
    home: Scaffold(
      body: FadeInImage.memoryNetwork(
        placeholder: kTransparentImage,
        image: 'https://example.com/test.png',
      ),
    ),
  ),
);

// 验证占位符存在
expect(find.byWidgetPredicate((widget) => 
  widget is Image && widget.image is MemoryImage && (widget.image as MemoryImage).bytes == kTransparentImage
), findsOneWidget);

// 验证网络图未加载
expect(find.byWidgetPredicate((widget) => 
  widget is Image && widget.image is NetworkImage && (widget.image as NetworkImage).url == 'https://example.com/test.png'
), findsNothing);

3. 等待网络图加载完成

FadeInImage默认淡入延迟为700ms,需等待足够时间完成加载和动画:

await tester.pump(const Duration(milliseconds: 700));
await tester.pumpAndSettle(); // 等待淡入动画结束

4. 验证网络图显示、占位符消失

等待完成后,验证占位符消失、网络图片成功显示:

// 验证占位符已消失
expect(find.byWidgetPredicate((widget) => 
  widget is Image && widget.image is MemoryImage && (widget.image as MemoryImage).bytes == kTransparentImage
), findsNothing);

// 验证网络图已显示
expect(find.byWidgetPredicate((widget) => 
  widget is Image && widget.image is NetworkImage && (widget.image as NetworkImage).url == 'https://example.com/test.png'
), findsOneWidget);

完整测试代码整合

将上述逻辑整合为完整测试文件,确保导入必要依赖:

import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';

class MockHttpClient extends HttpClient {
  @override
  Future<HttpClientRequest> getUrl(Uri url) async {
    final request = await super.getUrl(url);
    request.headers.set('content-type', 'image/png');
    return MockHttpClientRequest(request, kTransparentImage);
  }
}

class MockHttpClientRequest extends HttpClientRequest {
  final HttpClientRequest _realRequest;
  final Uint8List _mockBytes;

  MockHttpClientRequest(this._realRequest, this._mockBytes);

  @override
  Future<HttpClientResponse> close() async {
    return MockHttpClientResponse(_mockBytes);
  }

  @override
  dynamic noSuchMethod(Invocation invocation) => _realRequest.noSuchMethod(invocation);
}

class MockHttpClientResponse extends HttpClientResponse {
  final Uint8List _bytes;

  MockHttpClientResponse(this._bytes);

  @override
  int get statusCode => 200;

  @override
  Stream<List<int>> listen(void Function(List<int>)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) {
    return Stream.value(_bytes);
  }

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

class _MockHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return MockHttpClient();
  }
}

void main() {
  setUp(() {
    HttpOverrides.global = _MockHttpOverrides();
  });

  testWidgets('FadeInImage switches from placeholder to network image', (tester) async {
    const testImageUrl = 'https://example.com/test.png';

    // 初始渲染
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: FadeInImage.memoryNetwork(
            placeholder: kTransparentImage,
            image: testImageUrl,
          ),
        ),
      ),
    );

    // 验证初始状态
    expect(find.byWidgetPredicate((widget) =>
      widget is Image && widget.image is MemoryImage && (widget.image as MemoryImage).bytes == kTransparentImage
    ), findsOneWidget);
    expect(find.byWidgetPredicate((widget) =>
      widget is Image && widget.image is NetworkImage && (widget.image as NetworkImage).url == testImageUrl
    ), findsNothing);

    // 等待加载与动画
    await tester.pump(const Duration(milliseconds: 700));
    await tester.pumpAndSettle();

    // 验证最终状态
    expect(find.byWidgetPredicate((widget) =>
      widget is Image && widget.image is MemoryImage && (widget.image as MemoryImage).bytes == kTransparentImage
    ), findsNothing);
    expect(find.byWidgetPredicate((widget) =>
      widget is Image && widget.image is NetworkImage && (widget.image as NetworkImage).url == testImageUrl
    ), findsOneWidget);
  });
}

内容的提问来源于stack exchange,提问作者anasqadrei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:17:49