惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
爱范儿
爱范儿
GbyAI
GbyAI
博客园 - 叶小钗
Last Week in AI
Last Week in AI
Jina AI
Jina AI
Microsoft Security Blog
Microsoft Security Blog
云风的 BLOG
云风的 BLOG
C
Check Point Blog
H
Help Net Security
P
Proofpoint News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
大猫的无限游戏
大猫的无限游戏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog RSS Feed
Y
Y Combinator Blog
U
Unit 42
T
Tailwind CSS Blog
MyScale Blog
MyScale Blog
N
Netflix TechBlog - Medium
S
SegmentFault 最新的问题
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Flutter Testing Guide: Unit, Widget, and Integration — Wh...
kanta13jp1 · 2026-04-28 · via DEV Community

kanta13jp1

Flutter Testing Guide: Unit, Widget, and Integration — When to Use Each

Flutter gives you three test types. Knowing which to reach for—and when—is what makes testing feel useful rather than burdensome. Here's what I actually use in production.

The Three Layers

Unit Test:        verify logic in isolation (milliseconds)
Widget Test:      verify UI behavior without a device (seconds)
Integration Test: verify full user flows on an emulator (minutes)

Enter fullscreen mode Exit fullscreen mode

Build from the bottom up. Unit tests are your foundation. Integration tests are expensive — use them for critical paths only.

Unit Tests: Protect Business Logic

# pubspec.yaml
dev_dependencies:
  test: ^1.24.0

Enter fullscreen mode Exit fullscreen mode

// lib/utils/score_calculator.dart
class ScoreCalculator {
  static double calculate(int correct, int total) {
    if (total == 0) return 0;
    return correct / total * 100;
  }
}

// test/utils/score_calculator_test.dart
import 'package:test/test.dart';
import 'package:my_app/utils/score_calculator.dart';

void main() {
  group('ScoreCalculator', () {
    test('returns correct percentage', () {
      expect(ScoreCalculator.calculate(8, 10), equals(80.0));
    });

    test('returns 0 when total is 0', () {
      expect(ScoreCalculator.calculate(0, 0), equals(0.0));
    });
  });
}

Enter fullscreen mode Exit fullscreen mode

flutter test test/utils/  # run unit tests only

Enter fullscreen mode Exit fullscreen mode

Widget Tests: Protect UI Behavior

// test/widgets/achievement_card_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/achievement_card.dart';

void main() {
  testWidgets('shows title and description', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: AchievementCard(
          title: 'First Test',
          description: 'Wrote my first test',
        ),
      ),
    );

    expect(find.text('First Test'), findsOneWidget);
    expect(find.text('Wrote my first test'), findsOneWidget);
  });

  testWidgets('calls onTap when tapped', (tester) async {
    var tapped = false;
    await tester.pumpWidget(
      MaterialApp(
        home: AchievementCard(
          title: 'Test',
          onTap: () => tapped = true,
        ),
      ),
    );

    await tester.tap(find.byType(AchievementCard));
    expect(tapped, isTrue);
  });
}

Enter fullscreen mode Exit fullscreen mode

Riverpod: ProviderScope Overrides

// Override providers in widget tests
testWidgets('shows loading indicator while fetching', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        achievementsProvider.overrideWith(
          (_) async {
            await Future.delayed(const Duration(seconds: 1));
            return [];
          },
        ),
      ],
      child: const MaterialApp(home: AchievementsPage()),
    ),
  );

  await tester.pump();  // don't settle — stay in loading state
  expect(find.byType(CircularProgressIndicator), findsOneWidget);

  await tester.pumpAndSettle();  // settle → data shown
  expect(find.byType(CircularProgressIndicator), findsNothing);
});

Enter fullscreen mode Exit fullscreen mode

Mocking Supabase

// test/helpers/mock_supabase.dart
import 'package:mocktail/mocktail.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class MockSupabaseClient extends Mock implements SupabaseClient {}
class MockGoTrueClient extends Mock implements GoTrueClient {}

// In your test setUp:
setUp(() {
  final mockClient = MockSupabaseClient();
  final mockAuth = MockGoTrueClient();
  when(() => mockClient.auth).thenReturn(mockAuth);
  when(() => mockAuth.currentUser).thenReturn(null);  // unauthenticated
});

Enter fullscreen mode Exit fullscreen mode

Integration Tests: E2E Flow Verification

// integration_test/login_flow_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('login to home flow', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    await tester.enterText(find.byKey(const Key('email')), 'test@example.com');
    await tester.enterText(find.byKey(const Key('password')), 'password');
    await tester.tap(find.byKey(const Key('login_button')));
    await tester.pumpAndSettle();

    expect(find.text('Home'), findsOneWidget);
  });
}

Enter fullscreen mode Exit fullscreen mode

flutter test integration_test/ --device-id emulator-5554

Enter fullscreen mode Exit fullscreen mode

CI Integration

# .github/workflows/ci.yml
- name: Unit + Widget Tests
  run: flutter test --coverage

- name: Coverage check
  run: |
    lcov --summary coverage/lcov.info
    # fail if coverage drops below 70%

Enter fullscreen mode Exit fullscreen mode

Where to Start

Step 1: Write Unit Tests for critical business logic
Step 2: Write Widget Tests for UI you repeatedly test manually
Step 3: Write one Integration Test for your most important user flow

Enter fullscreen mode Exit fullscreen mode

Start small, add tests as you go. Any tests are better than no tests.

Summary

  • Unit: highest ROI — fast, easy to write, catches logic regressions
  • Widget: catches UI regressions — use Riverpod overrides to isolate dependencies
  • Integration: catches flow regressions — expensive, limit to critical paths

Even as a solo developer, building a test habit is the foundation for shipping with confidence.