# การทดสอบ Flutter อย่างครบถ้วน: Widget Test, Integration Test และกลยุทธ์สัมภาษณ์งานสายเทคนิค 2026 > คู่มือการทดสอบ Flutter ฉบับสมบูรณ์: widget test, integration test, golden test และ mocking ด้วย Mocktail พร้อมตัวอย่างโค้ดจริงและแนวทางที่พบบ่อยในการสัมภาษณ์งานสายเทคนิค 2026 - Published: 2026-05-14 - Updated: 2026-05-14 - Author: Anthony Fillion-Maillet - Tags: flutter, dart, testing, widget-testing, integration-testing, interview - Reading time: 10 min --- การทดสอบ Flutter เป็นปัจจัยสำคัญที่แยกแอปพลิเคชันระดับ production ออกจาก prototype ทั่วไป Flutter มีระบบการทดสอบในตัวสามประเภท ได้แก่ unit, widget และ integration โดยแต่ละประเภทมุ่งเป้าไปที่ชั้นที่แตกต่างกันของแอปพลิเคชัน บทความนี้นำเสนอ pattern การทดสอบที่ใช้ได้จริงสำหรับ widget test, integration test, golden test และกลยุทธ์ mocking ที่สามารถนำไปใช้ได้โดยตรงทั้งในงานพัฒนาประจำวันและการสัมภาษณ์งานสายเทคนิคในปี 2026 > **ปิรามิดการทดสอบ Flutter** > > ชุดทดสอบ Flutter ที่มีโครงสร้างดีจะเป็นไปตามปิรามิดการทดสอบ: unit test จำนวนมากที่ทำงานเร็วอยู่ฐานล่าง, widget test ที่แข็งแกร่งอยู่ชั้นกลาง และ integration test จำนวนน้อยที่เจาะจงอยู่ส่วนบน Widget test ทำงานได้ในเวลาไม่ถึงหนึ่งวินาทีโดยไม่ต้องใช้อุปกรณ์ ทำให้เป็นชั้นที่ให้ผลตอบแทนจากการลงทุนดีที่สุดสำหรับแอปพลิเคชัน Flutter ส่วนใหญ่ ## พื้นฐาน Widget Testing ใน Flutter Widget test ตรวจสอบส่วนประกอบ UI แบบแยกส่วน โดยไม่ต้องใช้ emulator หรืออุปกรณ์จริง Flutter สร้าง widget tree ในสภาพแวดล้อมทดสอบแบบ headless ช่วยให้ได้ feedback loop ที่รวดเร็วเพื่อตรวจจับปัญหาด้าน layout, event handler และการเปลี่ยนแปลง state ก่อนที่จะถูกนำขึ้น production Package `flutter_test` มี `testWidgets()`, `WidgetTester` สำหรับจำลองการโต้ตอบ และคลาส `Finder` สำหรับค้นหา widget ใน tree ```dart // counter_widget_test.dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:my_app/counter_widget.dart'; void main() { testWidgets('Counter increments on tap', (WidgetTester tester) async { // Inflate the widget tree await tester.pumpWidget( const MaterialApp(home: CounterWidget()), ); // Verify initial state expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Simulate user tap on the increment button await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Rebuild after state change // Assert updated state expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); } ``` การเรียก `pump()` จะทริกเกอร์การ rebuild หนึ่ง frame สำหรับ animation หรือการทำงานที่มีการหน่วงเวลา `pumpAndSettle()` จะรอจนกว่า frame ทั้งหมดจะเสร็จสมบูรณ์ การรู้ว่าควรใช้แต่ละวิธีเมื่อใดเป็นคำถามสัมภาษณ์ที่พบบ่อย โดย `pump()` ให้การควบคุมที่แม่นยำ ขณะที่ `pumpAndSettle()` สะดวกกว่าแต่อาจค้างกับ animation แบบไม่มีที่สิ้นสุด ## การทดสอบ Async Operations และ Widget ที่ขับเคลื่อนด้วย Stream Widget ในโลกจริงพึ่งพาการเรียก network, ฐานข้อมูล หรือ stream การทดสอบส่วนประกอบเหล่านี้ต้องควบคุมพฤติกรรม async อย่างชัดเจน `WidgetTester` ทำงานร่วมกับ event loop ของ Dart ได้ แต่ dependency ภายนอกจำเป็นต้อง mock เพื่อหลีกเลี่ยง test ที่ไม่เสถียร ```dart // user_profile_widget_test.dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:my_app/user_profile_widget.dart'; import 'package:my_app/user_repository.dart'; class MockUserRepository extends Mock implements UserRepository {} void main() { late MockUserRepository mockRepo; setUp(() { mockRepo = MockUserRepository(); }); testWidgets('displays user name after loading', (tester) async { // Arrange: mock returns a user after a delay when(() => mockRepo.fetchUser(1)).thenAnswer( (_) async => User(id: 1, name: 'Alice'), ); await tester.pumpWidget( MaterialApp( home: UserProfileWidget(repository: mockRepo), ), ); // Assert: loading indicator visible initially expect(find.byType(CircularProgressIndicator), findsOneWidget); // Wait for the Future to complete and rebuild await tester.pumpAndSettle(); // Assert: user name displayed, loader gone expect(find.text('Alice'), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsNothing); // Verify the repository was called exactly once verify(() => mockRepo.fetchUser(1)).called(1); }); } ``` [Mocktail](https://pub.dev/packages/mocktail) เป็นไลบรารี mocking ที่แนะนำในระบบนิเวศ Flutter สำหรับปี 2026 ต่างจาก Mockito ตรงที่ Mocktail ไม่ต้องการขั้นตอน code generation ทำให้การตั้งค่า test เร็วขึ้น API `when()` / `verify()` อ่านง่ายและครอบคลุมสถานการณ์ dependency injection ส่วนใหญ่ ## กลยุทธ์ Mocking ด้วย Mocktail สำหรับโค้ดพร้อมสัมภาษณ์ ผู้สัมภาษณ์มักถามเกี่ยวกับ dependency injection และความสามารถในการทดสอบใน Flutter Pattern ที่ได้คะแนนดีอย่างสม่ำเสมอคือ: constructor injection ร่วมกับ abstract class (หรือ interface ผ่านระบบ implicit interface ของ Dart) ```dart // weather_service.dart abstract class WeatherService { Future getWeather(String city); } // real implementation class OpenMeteoWeatherService implements WeatherService { final http.Client _client; OpenMeteoWeatherService(this._client); @override Future getWeather(String city) async { final response = await _client.get( Uri.parse('https://api.open-meteo.com/v1/forecast?city=$city'), ); return Weather.fromJson(jsonDecode(response.body)); } } ``` ```dart // weather_service_test.dart class MockHttpClient extends Mock implements http.Client {} void main() { late MockHttpClient mockClient; late OpenMeteoWeatherService service; setUp(() { mockClient = MockHttpClient(); service = OpenMeteoWeatherService(mockClient); }); test('parses weather JSON correctly', () async { // Arrange: return a known JSON payload when(() => mockClient.get(any())).thenAnswer( (_) async => http.Response( '{"temperature": 22.5, "condition": "sunny"}', 200, ), ); // Act final weather = await service.getWeather('Paris'); // Assert expect(weather.temperature, 22.5); expect(weather.condition, 'sunny'); }); } ``` Pattern นี้ คือการแยก dependency ภายนอกไว้เบื้องหลัง interface และ inject mock เข้าไป สามารถนำไปใช้ได้เหมือนกันกับ repository, บริการ analytics และ platform channel ผู้สัมภาษณ์ให้คุณค่ากับการแยกส่วนนี้เพราะแสดงถึงความเข้าใจ[หลักการ SOLID](https://en.wikipedia.org/wiki/SOLID) และสถาปัตยกรรมที่พร้อมสำหรับ production ## Integration Testing ด้วย Package integration_test Integration test ตรวจสอบว่าหลายส่วนประกอบทำงานร่วมกันบนอุปกรณ์จริงหรือ emulator Package `integration_test` ในตัวเป็นสะพานเชื่อมระหว่าง API ของ `flutter_test` กับการทำงานระดับอุปกรณ์ ```dart // 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('complete login flow', (tester) async { // Launch the full app app.main(); await tester.pumpAndSettle(); // Navigate to login screen await tester.tap(find.text('Sign In')); await tester.pumpAndSettle(); // Fill in credentials await tester.enterText( find.byKey(const Key('email_field')), 'test@example.com', ); await tester.enterText( find.byKey(const Key('password_field')), 'securePassword123', ); // Submit the form await tester.tap(find.byKey(const Key('login_button'))); await tester.pumpAndSettle(); // Verify navigation to dashboard expect(find.text('Dashboard'), findsOneWidget); }); } ``` Integration test รันด้วยคำสั่ง `flutter test integration_test/` การทดสอบเหล่านี้ทำงานบนอุปกรณ์จึงใช้เวลานานกว่า widget test แนวทางที่แนะนำคือ: เน้น integration test ไปที่ user flow ที่สำคัญ (login, checkout, onboarding) และพึ่งพา widget test สำหรับพฤติกรรมของแต่ละหน้าจอ สำหรับแอปที่โต้ตอบกับ UI ของ platform native เช่น dialog ขอสิทธิ์, push notification, prompt ยืนยันตัวตนด้วยไบโอเมตริก package [Patrol](https://patrol.leancode.co/) ขยายความสามารถการทดสอบ Flutter ด้วยการรองรับ native automation ## Golden Test สำหรับตรวจจับ Visual Regression Golden test จับภาพหน้าจอของ widget ที่ render แล้วและเปรียบเทียบกับภาพ baseline ความแตกต่างระดับ pixel ใดก็ตามจะทำให้ test ล้มเหลว ช่วยตรวจจับ visual regression ที่ไม่ได้ตั้งใจก่อน deploy Flutter 3.29+ รองรับ golden test ในตัว สำหรับสถานการณ์ขั้นสูง เช่น หลายขนาดหน้าจอ, theme, locale ต่างกัน [Alchemist](https://pub.dev/packages/alchemist) มี API ที่มีโครงสร้างชัดเจนแทนที่ `golden_toolkit` ที่ยุติการพัฒนาแล้ว ```dart // button_golden_test.dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:my_app/primary_button.dart'; void main() { testWidgets('PrimaryButton matches golden file', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: PrimaryButton( label: 'Submit', onPressed: () {}, ), ), ), ), ); // Compare against baseline image await expectLater( find.byType(PrimaryButton), matchesGoldenFile('goldens/primary_button.png'), ); }); } ``` สร้างภาพ baseline ด้วยคำสั่ง `flutter test --update-goldens` เก็บ golden file ไว้ใน version control Pipeline CI ควรรัน golden test บน platform ที่สม่ำเสมอ (container Linux เป็นมาตรฐาน) เพื่อหลีกเลี่ยงความแตกต่างของ pixel ระหว่าง platform ## การจัดโครงสร้างชุดทดสอบ Flutter ให้ขยายได้ การจัดระเบียบ test มีความสำคัญเมื่อ codebase เติบโต Pattern ที่ขยายได้ดีคือ: สะท้อนโครงสร้างไดเรกทอรี `lib/` ภายใน `test/` โดยแยกชัดเจนระหว่างชั้น unit, widget และ integration ``` test/ unit/ services/ weather_service_test.dart models/ user_test.dart widget/ screens/ login_screen_test.dart dashboard_test.dart components/ primary_button_test.dart goldens/ primary_button.png integration_test/ login_flow_test.dart checkout_flow_test.dart ``` โค้ดการตั้งค่าที่ใช้ร่วมกัน เช่น mock instance, factory ข้อมูลทดสอบ, custom matcher ควรอยู่ในไดเรกทอรี `test/helpers/` การแชร์ส่วนประกอบเหล่านี้ระหว่างไฟล์ test ช่วยลดความซ้ำซ้อนโดยไม่สร้าง coupling ที่แน่นเกินไป ตัวชี้วัดสำคัญที่ควรติดตามด้วย[เครื่องมือ coverage ของ Flutter](https://docs.flutter.dev/testing/overview): - **Line coverage**: เป้าหมาย 80%+ สำหรับ business logic, 60%+ สำหรับโค้ด UI - **Branch coverage**: ตรวจสอบว่า conditional path ถูกทดสอบแล้ว - **เวลาทำงานของ test**: unit + widget test ต่ำกว่า 5 นาที, integration test ต่ำกว่า 30 นาที การรัน `flutter test --coverage` จะสร้างไฟล์ `lcov.info` Pipeline CI สามารถใช้ `lcov` หรือ Codecov เพื่อแสดงภาพแนวโน้ม coverage และบังคับใช้เกณฑ์ขั้นต่ำ ## คำถามสัมภาษณ์เกี่ยวกับ Flutter Testing ที่พบบ่อย การสัมภาษณ์งานสายเทคนิคในปี 2026 มักรวมสถานการณ์เกี่ยวกับ Flutter testing เป็นประจำ ต่อไปนี้เป็น pattern ที่พบบ่อยที่สุด จากข้อมูลจริงของนักพัฒนา Flutter **"ความแตกต่างระหว่าง pump() กับ pumpAndSettle() คืออะไร?"** `pump()` ทริกเกอร์การ rebuild หนึ่ง frame เท่านั้น `pumpAndSettle()` เรียก `pump()` ซ้ำจนกว่าจะไม่มี frame ที่ถูกจัดคิวอีก เหมาะสำหรับ animation และการเปลี่ยน route จุดที่ต้องระวัง: `pumpAndSettle()` จะ timeout กับ widget ที่มี animation ไม่สิ้นสุด (loading spinner, shimmer effect) ให้ใช้ `pump(Duration)` สำหรับกรณีเหล่านั้น **"Widget test ต่างจาก integration test อย่างไร?"** Widget test ทำงานในสภาพแวดล้อม headless โดยไม่ต้องใช้อุปกรณ์ ทดสอบส่วนประกอบแบบแยกส่วน Integration test ทำงานบนอุปกรณ์จริงหรือ emulator ทดสอบแอปทั้งหมดหรือส่วนใหญ่ Widget test ทำงานเร็ว (มิลลิวินาที) และควรรันใน CI ทุกครั้งที่ commit Integration test ช้ากว่า (นาที) และมักรันเมื่อ merge หรือ build กลางคืน **"จะทดสอบ widget ที่พึ่งพา Provider หรือ Riverpod ได้อย่างไร?"** ครอบ widget ที่ต้องการทดสอบด้วย provider scope ที่เหมาะสม และ inject override เฉพาะสำหรับการทดสอบ: ```dart // testing a Riverpod-dependent widget import 'package:flutter_riverpod/flutter_riverpod.dart'; testWidgets('CartWidget shows item count', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ // Override the cart provider with test data cartProvider.overrideWith( (ref) => CartNotifier(initialItems: [ CartItem(name: 'Widget Book', price: 29.99), ]), ), ], child: const MaterialApp(home: CartWidget()), ), ); expect(find.text('1 item'), findsOneWidget); expect(find.text('\$29.99'), findsOneWidget); }); ``` แนวทางนี้หลีกเลี่ยงการพึ่งพา global state และทำให้แต่ละ test มีความ deterministic การเตรียมคำตอบพร้อมตัวอย่างโค้ดที่เป็นรูปธรรม แทนที่จะเป็นคำอธิบายเชิงนามธรรม ให้ผลลัพธ์ที่ดีกว่าในการสัมภาษณ์ Flutter อย่างสม่ำเสมอ สำหรับการเตรียมตัวสัมภาษณ์ Flutter เพิ่มเติม สามารถศึกษา[โมดูล widget testing](/technologies/flutter/interview-questions/widget-testing) และ[โมดูล unit testing](/technologies/flutter/interview-questions/unit-testing) บน SharpSkill ## สรุป - Widget test ให้ ROI สูงสุดในแอป Flutter ด้วยการทำงานที่เร็ว ไม่ต้องพึ่งอุปกรณ์ และตรวจสอบ UI โดยตรงผ่าน `testWidgets()` และ `WidgetTester` - ใช้ Mocktail สำหรับ mock dependency: ไม่ต้อง codegen, API `when()`/`verify()` ที่สะอาด และเข้ากันได้เต็มรูปแบบกับระบบ implicit interface ของ Dart - Integration test ควรเน้นเฉพาะ user flow ที่สำคัญ (login, checkout, ชำระเงิน) เท่านั้น ทำให้เจาะจงและรันเมื่อ merge หรือใน pipeline CI กลางคืน - Golden test ตรวจจับ visual regression โดยอัตโนมัติ เก็บ baseline ใน version control และรันบน platform CI ที่สม่ำเสมอเพื่อหลีกเลี่ยงความแตกต่างของ pixel ระหว่าง OS - สะท้อนโครงสร้าง `lib/` ในไดเรกทอรี `test/` ติดตาม coverage ด้วย `flutter test --coverage` และบังคับใช้เกณฑ์ 80%+ สำหรับ business logic - การเตรียมสัมภาษณ์: ฝึกฝนความแตกต่างระหว่าง `pump()` กับ `pumpAndSettle()`, override test Provider/Riverpod และ pattern constructor injection พร้อมตัวอย่างโค้ดที่เป็นรูปธรรม --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/flutter/flutter-testing-widget-integration-interview-2026