การทดสอบหน่วยใน Flutter: จากสิ่งจำเป็นสำหรับเวิร์กโฟลว์ไปจนถึงสถานการณ์ที่ซับซ้อน
เผยแพร่แล้ว: 2022-09-21ความสนใจใน Flutter นั้นสูงเป็นประวัติการณ์—และเกินกำหนดมานาน SDK โอเพ่นซอร์สของ Google เข้ากันได้กับ Android, iOS, macOS, เว็บ, Windows และ Linux Codebase Flutter เดียวรองรับทั้งหมด และการทดสอบหน่วยเป็นเครื่องมือในการนำเสนอแอป Flutter ที่สม่ำเสมอและเชื่อถือได้ โดยจะช่วยป้องกันข้อผิดพลาด ข้อบกพร่อง และข้อบกพร่องโดยการปรับปรุงคุณภาพของโค้ดล่วงหน้าก่อนที่จะประกอบ
ในบทช่วยสอนนี้ เราแชร์การเพิ่มประสิทธิภาพเวิร์กโฟลว์สำหรับการทดสอบหน่วย Flutter สาธิตการทดสอบหน่วย Flutter พื้นฐาน จากนั้นไปยังกรณีการทดสอบและไลบรารี Flutter ที่ซับซ้อนยิ่งขึ้น
การไหลของการทดสอบหน่วยใน Flutter
เราใช้การทดสอบหน่วยใน Flutter ในลักษณะเดียวกับที่เราทำในเทคโนโลยีอื่นๆ:
- ประเมินรหัส
- ตั้งค่าการจำลองข้อมูล
- กำหนดกลุ่มทดสอบ
- กำหนดลายเซ็นของฟังก์ชันการทดสอบสำหรับแต่ละกลุ่มการทดสอบ
- เขียนแบบทดสอบ
เพื่อสาธิตการทดสอบหน่วย ฉันได้เตรียมตัวอย่างโปรเจ็กต์ Flutter และสนับสนุนให้คุณใช้และทดสอบโค้ดในยามว่าง โปรเจ็กต์นี้ใช้ API ภายนอกเพื่อดึงและแสดงรายชื่อมหาวิทยาลัยที่เราสามารถกรองตามประเทศได้
หมายเหตุเล็กน้อยเกี่ยวกับวิธีการทำงานของ Flutter: เฟรมเวิร์กช่วยอำนวยความสะดวกในการทดสอบโดยโหลดไลบรารี flutter_test
โดยอัตโนมัติเมื่อสร้างโปรเจ็กต์ ไลบรารีช่วยให้ Flutter สามารถอ่าน เรียกใช้ และวิเคราะห์การทดสอบหน่วยได้ Flutter ยังสร้างโฟลเดอร์ test
โดยอัตโนมัติเพื่อจัดเก็บการทดสอบ สิ่งสำคัญคือต้องหลีกเลี่ยงการเปลี่ยนชื่อและ/หรือย้ายโฟลเดอร์ test
เนื่องจากจะทำให้ฟังก์ชันการทำงานเสียหาย และด้วยเหตุนี้ ความสามารถในการเรียกใช้การทดสอบของเราจึงเป็นสิ่งสำคัญ สิ่งสำคัญคือต้องรวม _test.dart
ไว้ในชื่อไฟล์ทดสอบของเรา เนื่องจากส่วนต่อท้ายนี้คือวิธีที่ Flutter รู้จักไฟล์ทดสอบ
โครงสร้างไดเร็กทอรีทดสอบ
เพื่อส่งเสริมการทดสอบหน่วยในโครงการของเรา เราได้นำ MVVM ไปใช้ด้วยสถาปัตยกรรมที่สะอาดและการฉีดพึ่งพา (DI) ตามหลักฐานในชื่อที่เลือกสำหรับโฟลเดอร์ย่อยซอร์สโค้ด การผสมผสานหลักการ MVVM และ DI ช่วยให้มั่นใจถึงการแยกข้อกังวล:
- แต่ละชั้นโครงการสนับสนุนวัตถุประสงค์เดียว
- แต่ละฟังก์ชันภายในคลาสจะเติมเต็มขอบเขตของตัวเองเท่านั้น
เราจะสร้างพื้นที่จัดเก็บที่เป็นระเบียบสำหรับไฟล์ทดสอบที่เราจะเขียน ซึ่งเป็นระบบที่กลุ่มการทดสอบจะมี "บ้าน" ที่ระบุได้ง่าย ตามข้อกำหนดของ Flutter ในการค้นหาตำแหน่งการทดสอบภายในโฟลเดอร์ test
ให้จำลองโครงสร้างโฟลเดอร์ของซอร์สโค้ดภายใต้ test
จากนั้นเมื่อเราเขียนการทดสอบ เราจะจัดเก็บไว้ในโฟลเดอร์ย่อยที่เหมาะสม เช่นเดียวกับถุงเท้าที่สะอาดอยู่ในลิ้นชักถุงเท้าของโต๊ะเครื่องแป้งและเสื้อที่พับอยู่ในลิ้นชักเสื้อ การทดสอบหน่วยของคลาส Model
จะไปในโฟลเดอร์ชื่อ model
, ตัวอย่างเช่น.
การนำระบบไฟล์นี้มาใช้จะสร้างความโปร่งใสให้กับโปรเจ็กต์และช่วยให้ทีมดูได้ง่ายๆ ว่าโค้ดส่วนใดบ้างที่มีการทดสอบที่เกี่ยวข้อง
ตอนนี้เราพร้อมที่จะนำการทดสอบหน่วยไปปฏิบัติแล้ว
การทดสอบหน่วย Flutter อย่างง่าย
เราจะเริ่มด้วยคลาส model
(ในชั้น data
ของซอร์สโค้ด) และจะจำกัดตัวอย่างของเราให้รวมคลาส model
เท่านั้น ApiUniversityModel
คลาสนี้มีฟังก์ชั่นสองอย่าง:
- เริ่มต้นโมเดลของเราด้วยการเยาะเย้ยวัตถุ JSON ด้วย
Map
- สร้างแบบจำลองข้อมูล
University
ในการทดสอบแต่ละฟังก์ชันของโมเดล เราจะปรับแต่งขั้นตอนสากลที่อธิบายไว้ก่อนหน้านี้:
- ประเมินรหัส
- ตั้งค่าการเยาะเย้ยข้อมูล: เราจะกำหนดการตอบสนองของเซิร์ฟเวอร์ต่อการเรียก API ของเรา
- กำหนดกลุ่มทดสอบ: เราจะมีกลุ่มทดสอบสองกลุ่ม กลุ่มหนึ่งสำหรับแต่ละฟังก์ชัน
- กำหนดลายเซ็นฟังก์ชันการทดสอบสำหรับแต่ละกลุ่มการทดสอบ
- เขียนแบบทดสอบ
หลังจากประเมินโค้ดของเราแล้ว เราก็พร้อมที่จะบรรลุวัตถุประสงค์ที่สองของเรา: เพื่อตั้งค่าการเยาะเย้ยข้อมูลเฉพาะสำหรับสองฟังก์ชันภายในคลาส ApiUniversityModel
ในการเยาะเย้ยฟังก์ชันแรก (เริ่มต้นโมเดลของเราโดยจำลอง JSON ด้วย Map
) fromJson
เราจะสร้างออบเจ็กต์ Map
สองรายการเพื่อจำลองข้อมูลอินพุตสำหรับฟังก์ชัน เราจะสร้างวัตถุ ApiUniversityModel
ที่เทียบเท่ากันสองรายการเพื่อแสดงผลลัพธ์ที่คาดหวังของฟังก์ชันด้วยอินพุตที่ให้มา
ในการเยาะเย้ยฟังก์ชันที่สอง (การสร้างโมเดลข้อมูลของ University
) toDomain
เราจะสร้างสองอ็อบเจ็กต์ University
ซึ่งเป็นผลลัพธ์ที่คาดหวังหลังจากเรียกใช้ฟังก์ชันนี้ในอ็อบเจ็กต์ ApiUniversityModel
ที่สร้างอินสแตนซ์ก่อนหน้านี้:
void main() { Map<String, dynamic> apiUniversityOneAsJson = { "alpha_two_code": "US", "domains": ["marywood.edu"], "country": "United States", "state-province": null, "web_pages": ["http://www.marywood.edu"], "name": "Marywood University" }; ApiUniversityModel expectedApiUniversityOne = ApiUniversityModel( alphaCode: "US", country: "United States", state: null, name: "Marywood University", websites: ["http://www.marywood.edu"], domains: ["marywood.edu"], ); University expectedUniversityOne = University( alphaCode: "US", country: "United States", state: "", name: "Marywood University", websites: ["http://www.marywood.edu"], domains: ["marywood.edu"], ); Map<String, dynamic> apiUniversityTwoAsJson = { "alpha_two_code": "US", "domains": ["lindenwood.edu"], "country": "United States", "state-province":"MJ", "web_pages": null, "name": "Lindenwood University" }; ApiUniversityModel expectedApiUniversityTwo = ApiUniversityModel( alphaCode: "US", country: "United States", state:"MJ", name: "Lindenwood University", websites: null, domains: ["lindenwood.edu"], ); University expectedUniversityTwo = University( alphaCode: "US", country: "United States", state: "MJ", name: "Lindenwood University", websites: [], domains: ["lindenwood.edu"], ); }
ต่อไป สำหรับวัตถุประสงค์ที่สามและสี่ เราจะเพิ่มภาษาอธิบายเพื่อกำหนดกลุ่มการทดสอบและลายเซ็นของฟังก์ชันการทดสอบ:
void main() { // Previous declarations group("Test ApiUniversityModel initialization from JSON", () { test('Test using json one', () {}); test('Test using json two', () {}); }); group("Test ApiUniversityModel toDomain", () { test('Test toDomain using json one', () {}); test('Test toDomain using json two', () {}); }); }
เราได้กำหนดลายเซ็นของการทดสอบสองครั้งเพื่อตรวจสอบฟังก์ชัน fromJson
และอีกสองรายการเพื่อตรวจสอบฟังก์ชัน toDomain
เพื่อบรรลุวัตถุประสงค์ที่ห้าของเราและเขียนการทดสอบ ลองใช้วิธีการ expect
ของไลบรารี flutter_test เพื่อเปรียบเทียบผลลัพธ์ของฟังก์ชันกับความคาดหวังของเรา:
void main() { // Previous declarations group("Test ApiUniversityModel initialization from json", () { test('Test using json one', () { expect(ApiUniversityModel.fromJson(apiUniversityOneAsJson), expectedApiUniversityOne); }); test('Test using json two', () { expect(ApiUniversityModel.fromJson(apiUniversityTwoAsJson), expectedApiUniversityTwo); }); }); group("Test ApiUniversityModel toDomain", () { test('Test toDomain using json one', () { expect(ApiUniversityModel.fromJson(apiUniversityOneAsJson).toDomain(), expectedUniversityOne); }); test('Test toDomain using json two', () { expect(ApiUniversityModel.fromJson(apiUniversityTwoAsJson).toDomain(), expectedUniversityTwo); }); }); }
เมื่อบรรลุวัตถุประสงค์ทั้ง 5 ประการแล้ว ตอนนี้เราสามารถเรียกใช้การทดสอบได้จาก IDE หรือจากบรรทัดคำสั่ง
ที่เทอร์มินัล เราสามารถเรียกใช้การทดสอบทั้งหมดที่อยู่ในโฟลเดอร์ test
ได้โดยการป้อนคำสั่ง flutter test
และเห็นว่าการทดสอบของเราผ่าน
อีกทางหนึ่ง เราสามารถเรียกใช้การทดสอบกลุ่มเดียวหรือกลุ่มทดสอบโดยป้อนคำสั่ง flutter test --plain-name "ReplaceWithName"
แทนที่ชื่อกลุ่มทดสอบหรือกลุ่มทดสอบสำหรับ ReplaceWithName
หน่วยทดสอบจุดปลายใน Flutter
หลังจากเสร็จสิ้นการทดสอบอย่างง่ายโดยไม่มีการขึ้นต่อกัน มาสำรวจตัวอย่างที่น่าสนใจกันดีกว่า: เราจะทดสอบคลาส endpoint
ซึ่งมีขอบเขตครอบคลุม:
- ดำเนินการเรียก API ไปยังเซิร์ฟเวอร์
- การแปลงการตอบสนอง API JSON เป็นรูปแบบอื่น
หลังจากประเมินโค้ดของเราแล้ว เราจะใช้วิธี setUp ของไลบรารี setUp
เพื่อเริ่มต้นคลาสภายในกลุ่มทดสอบของเรา:
group("Test University Endpoint API calls", () { setUp(() { baseUrl = "https://test.url"; dioClient = Dio(BaseOptions()); endpoint = UniversityEndpoint(dioClient, baseUrl: baseUrl); }); }
หากต้องการส่งคำขอเครือข่ายไปยัง API ฉันชอบใช้ไลบรารีสำหรับติดตั้งเพิ่มเติม ซึ่งสร้างโค้ดที่จำเป็นส่วนใหญ่ เพื่อทดสอบคลาส UniversityEndpoint
อย่างถูกต้อง เราจะบังคับให้ไลบรารี dio ซึ่ง Retrofit
ใช้ในการเรียกใช้ API เพื่อส่งคืนผลลัพธ์ที่ต้องการโดยจำลองพฤติกรรมของคลาส Dio
ผ่านอะแด็ปเตอร์การตอบสนองที่กำหนดเอง
Custom Network Interceptor Mock
การเยาะเย้ยเป็นไปได้เนื่องจากเราได้สร้างคลาส UniversityEndpoint
ผ่าน DI (หากคลาส UniversityEndpoint
เริ่มต้นคลาส Dio
ด้วยตัวเอง เราจะไม่มีทางล้อเลียนพฤติกรรมของชั้นเรียนได้)
เพื่อล้อเลียนพฤติกรรมของคลาส Dio
เราจำเป็นต้องรู้วิธี Dio
ที่ใช้ในไลบรารี Retrofit
แต่เราไม่สามารถเข้าถึง Dio
ได้โดยตรง ดังนั้น เราจะล้อเลียน Dio
โดยใช้ตัวสกัดกั้นการตอบสนองเครือข่ายแบบกำหนดเอง:
class DioMockResponsesAdapter extends HttpClientAdapter { final MockAdapterInterceptor interceptor; DioMockResponsesAdapter(this.interceptor); @override void close({bool force = false}) {} @override Future<ResponseBody> fetch(RequestOptions options, Stream<Uint8List>? requestStream, Future? cancelFuture) { if (options.method == interceptor.type.name.toUpperCase() && options.baseUrl == interceptor.uri && options.queryParameters.hasSameElementsAs(interceptor.query) && options.path == interceptor.path) { return Future.value(ResponseBody.fromString( jsonEncode(interceptor.serializableResponse), interceptor.responseCode, headers: { "content-type": ["application/json"] }, )); } return Future.value(ResponseBody.fromString( jsonEncode( {"error": "Request doesn't match the mock interceptor details!"}), -1, statusMessage: "Request doesn't match the mock interceptor details!")); } } enum RequestType { GET, POST, PUT, PATCH, DELETE } class MockAdapterInterceptor { final RequestType type; final String uri; final String path; final Map<String, dynamic> query; final Object serializableResponse; final int responseCode; MockAdapterInterceptor(this.type, this.uri, this.path, this.query, this.serializableResponse, this.responseCode); }
ตอนนี้เราได้สร้าง interceptor เพื่อเยาะเย้ยการตอบสนองของเครือข่ายแล้ว เราสามารถกำหนดกลุ่มการทดสอบและลายเซ็นของฟังก์ชันการทดสอบได้
ในกรณีของเรา เรามีฟังก์ชันเดียวที่จะทดสอบ ( getUniversitiesByCountry
) ดังนั้นเราจะสร้างกลุ่มทดสอบเพียงกลุ่มเดียว เราจะทดสอบการตอบสนองของฟังก์ชันของเราต่อสามสถานการณ์:
- ฟังก์ชันของคลาส
Dio
ถูกเรียกโดยgetUniversitiesByCountry
จริงหรือ - หากคำขอ API ของเราส่งคืนข้อผิดพลาด จะเกิดอะไรขึ้น
- หากคำขอ API ของเราส่งคืนผลลัพธ์ที่คาดไว้ จะเกิดอะไรขึ้น
นี่คือกลุ่มทดสอบและลายเซ็นฟังก์ชันการทดสอบของเรา:
group("Test University Endpoint API calls", () { test('Test endpoint calls dio', () async {}); test('Test endpoint returns error', () async {}); test('Test endpoint calls and returns 2 valid universities', () async {}); });
เราพร้อมที่จะเขียนแบบทดสอบของเรา สำหรับแต่ละกรณีทดสอบ เราจะสร้างอินสแตนซ์ของ DioMockResponsesAdapter
ด้วยการกำหนดค่าที่เกี่ยวข้อง:
group("Test University Endpoint API calls", () { setUp(() { baseUrl = "https://test.url"; dioClient = Dio(BaseOptions()); endpoint = UniversityEndpoint(dioClient, baseUrl: baseUrl); }); test('Test endpoint calls dio', () async { dioClient.httpClientAdapter = _createMockAdapterForSearchRequest( 200, [], ); var result = await endpoint.getUniversitiesByCountry("us"); expect(result, <ApiUniversityModel>[]); }); test('Test endpoint returns error', () async { dioClient.httpClientAdapter = _createMockAdapterForSearchRequest( 404, {"error": "Not found!"}, ); List<ApiUniversityModel>? response; DioError? error; try { response = await endpoint.getUniversitiesByCountry("us"); } on DioError catch (dioError, _) { error = dioError; } expect(response, null); expect(error?.error, "Http status error [404]"); }); test('Test endpoint calls and returns 2 valid universities', () async { dioClient.httpClientAdapter = _createMockAdapterForSearchRequest( 200, generateTwoValidUniversities(), ); var result = await endpoint.getUniversitiesByCountry("us"); expect(result, expectedTwoValidUniversities()); }); });
เมื่อการทดสอบปลายทางของเราเสร็จสมบูรณ์แล้ว มาทดสอบคลาสแหล่งข้อมูลของเรากันดีกว่า UniversityRemoteDataSource
ก่อนหน้านี้ เราสังเกตว่าคลาส UniversityEndpoint
เป็นส่วนหนึ่งของคอนสตรัคเตอร์ UniversityRemoteDataSource({UniversityEndpoint? universityEndpoint})
ซึ่งระบุว่า UniversityRemoteDataSource
ใช้คลาส UniversityEndpoint
เพื่อบรรลุขอบเขต ดังนั้นนี่คือคลาสที่เราจะล้อเลียน
เยาะเย้ยกับม็อกคิโต
ในตัวอย่างก่อนหน้านี้ เราจำลองคำขอของไคลเอ็นต์ Dio
ด้วยตนเองโดยใช้ NetworkInterceptor
ที่กำหนดเอง ที่นี่เรากำลังเยาะเย้ยทั้งชั้นเรียน การทำเช่นนี้ด้วยตนเอง—เยาะเย้ยชั้นเรียนและหน้าที่ของคลาส—จะใช้เวลานาน โชคดีที่ไลบรารีจำลองได้รับการออกแบบมาเพื่อจัดการกับสถานการณ์ดังกล่าว และสามารถสร้างคลาสจำลองได้โดยใช้ความพยายามเพียงเล็กน้อย มาใช้ห้องสมุด mockito ห้องสมุดมาตรฐานอุตสาหกรรมสำหรับการเยาะเย้ยใน Flutter
ในการเยาะเย้ย Mockito
อื่นเราเพิ่มคำอธิบายประกอบ “ @GenerateMocks([class_1,class_2,…])
” ก่อนโค้ดของการทดสอบ ซึ่งอยู่เหนือฟังก์ชัน void main() {}
ในคำอธิบายประกอบ เราจะรวมรายชื่อคลาสเป็นพารามิเตอร์ (แทนที่ class_1,class_2…
)
ต่อไป เรารันคำสั่ง flutter pub run build_runner build
ที่สร้างโค้ดสำหรับคลาสจำลองของเราในไดเร็กทอรีเดียวกันกับการทดสอบ ชื่อไฟล์จำลองที่เป็นผลลัพธ์จะเป็นการรวมกันของชื่อไฟล์ทดสอบและ .mocks.dart
โดยแทนที่ส่วนต่อท้าย . .dart
ของการทดสอบ เนื้อหาของไฟล์จะรวมถึงคลาสจำลองที่มีชื่อขึ้นต้นด้วยคำนำหน้า Mock
ตัวอย่างเช่น UniversityEndpoint
กลายเป็น MockUniversityEndpoint
ตอนนี้ เรานำเข้า university_remote_data_source_test.dart.mocks.dart
(ไฟล์จำลองของเรา) ลงใน university_remote_data_source_test.dart
(ไฟล์ทดสอบ)
จากนั้น ในฟังก์ชัน setUp
เราจะจำลอง UniversityEndpoint
โดยใช้ MockUniversityEndpoint
และเริ่มต้นคลาส UniversityRemoteDataSource
:
import 'university_remote_data_source_test.mocks.dart'; @GenerateMocks([UniversityEndpoint]) void main() { late UniversityEndpoint endpoint; late UniversityRemoteDataSource dataSource; group("Test function calls", () { setUp(() { endpoint = MockUniversityEndpoint(); dataSource = UniversityRemoteDataSource(universityEndpoint: endpoint); }); }
เราล้อเลียน UniversityEndpoint
สำเร็จแล้วจึงเริ่มต้นคลาส UniversityRemoteDataSource
ของเรา ตอนนี้เราพร้อมที่จะกำหนดกลุ่มทดสอบและลายเซ็นฟังก์ชันการทดสอบแล้ว:
group("Test function calls", () { test('Test dataSource calls getUniversitiesByCountry from endpoint', () {}); test('Test dataSource maps getUniversitiesByCountry response to Stream', () {}); test('Test dataSource maps getUniversitiesByCountry response to Stream with error', () {}); });
ด้วยเหตุนี้ เราจึงตั้งค่าการเยาะเย้ย กลุ่มทดสอบ และฟังก์ชันการทดสอบของเรา เราพร้อมที่จะเขียนแบบทดสอบจริง
การทดสอบครั้งแรกของเราตรวจสอบว่ามีการเรียกใช้ฟังก์ชัน UniversityEndpoint
หรือไม่เมื่อแหล่งข้อมูลเริ่มต้นการดึงข้อมูลประเทศ เราเริ่มต้นด้วยการกำหนดว่าแต่ละคลาสจะตอบสนองอย่างไรเมื่อมีการเรียกฟังก์ชัน เนื่องจากเราล้อเลียนคลาส UniversityEndpoint
นั่นคือคลาสที่เราจะทำงานด้วย โดยใช้โครงสร้างโค้ด when( function_that_will_be_called ).then( what_will_be_returned )
ฟังก์ชันที่เรากำลังทดสอบเป็นแบบอะซิงโครนัส (ฟังก์ชันที่ส่งคืนอ็อบเจ็กต์ Future
) ดังนั้นเราจะใช้โครงสร้างโค้ด when(function name).thenanswer( (_) {modified function result} )
เพื่อแก้ไขผลลัพธ์ของเรา
เพื่อตรวจสอบว่าฟังก์ชัน getUniversitiesByCountry
เรียกใช้ฟังก์ชัน getUniversitiesByCountry
ภายในคลาส UniversityEndpoint
หรือไม่ เราจะใช้ when(...).thenAnswer( (_) {...} )
เพื่อจำลองฟังก์ชัน getUniversitiesByCountry
ภายในคลาส UniversityEndpoint
:
when(endpoint.getUniversitiesByCountry("test")) .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[]));
ตอนนี้เราได้ล้อเลียนคำตอบของเราแล้ว เราเรียกฟังก์ชันแหล่งข้อมูลและตรวจสอบ—โดยใช้ฟังก์ชัน verify
—ไม่ว่าจะเรียกฟังก์ชัน UniversityEndpoint
หรือไม่:
test('Test dataSource calls getUniversitiesByCountry from endpoint', () { when(endpoint.getUniversitiesByCountry("test")) .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[])); dataSource.getUniversitiesByCountry("test"); verify(endpoint.getUniversitiesByCountry("test")); });
เราสามารถใช้หลักการเดียวกันนี้ในการเขียนการทดสอบเพิ่มเติมที่ตรวจสอบว่าฟังก์ชันของเราแปลงผลลัพธ์ปลายทางของเราเป็นสตรีมข้อมูลที่เกี่ยวข้องอย่างถูกต้องหรือไม่:
import 'university_remote_data_source_test.mocks.dart'; @GenerateMocks([UniversityEndpoint]) void main() { late UniversityEndpoint endpoint; late UniversityRemoteDataSource dataSource; group("Test function calls", () { setUp(() { endpoint = MockUniversityEndpoint(); dataSource = UniversityRemoteDataSource(universityEndpoint: endpoint); }); test('Test dataSource calls getUniversitiesByCountry from endpoint', () { when(endpoint.getUniversitiesByCountry("test")) .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[])); dataSource.getUniversitiesByCountry("test"); verify(endpoint.getUniversitiesByCountry("test")); }); test('Test dataSource maps getUniversitiesByCountry response to Stream', () { when(endpoint.getUniversitiesByCountry("test")) .thenAnswer((realInvocation) => Future.value(<ApiUniversityModel>[])); expect( dataSource.getUniversitiesByCountry("test"), emitsInOrder([ const AppResult<List<University>>.loading(), const AppResult<List<University>>.data([]) ]), ); }); test( 'Test dataSource maps getUniversitiesByCountry response to Stream with error', () { ApiError mockApiError = ApiError( statusCode: 400, message: "error", errors: null, ); when(endpoint.getUniversitiesByCountry("test")) .thenAnswer((realInvocation) => Future.error(mockApiError)); expect( dataSource.getUniversitiesByCountry("test"), emitsInOrder([ const AppResult<List<University>>.loading(), AppResult<List<University>>.apiError(mockApiError) ]), ); }); }); }
เราได้ทำการทดสอบหน่วย Flutter หลายครั้งและสาธิตวิธีการเยาะเย้ยที่แตกต่างกัน ฉันขอเชิญให้คุณใช้โปรเจ็กต์ Flutter ตัวอย่างของฉันต่อไปเพื่อทำการทดสอบเพิ่มเติม
การทดสอบหน่วย Flutter: กุญแจสู่ UX ที่เหนือกว่า
หากคุณรวมการทดสอบหน่วยเข้ากับโปรเจ็กต์ Flutter แล้ว บทความนี้อาจแนะนำตัวเลือกใหม่ๆ ที่คุณสามารถใส่ลงในเวิร์กโฟลว์ของคุณได้ ในบทช่วยสอนนี้ เราแสดงให้เห็นว่าการรวมการทดสอบหน่วยเข้ากับโปรเจ็กต์ Flutter ถัดไปของคุณนั้นตรงไปตรงมาเพียงใด และวิธีจัดการกับความท้าทายของสถานการณ์การทดสอบที่เหมาะสมยิ่งขึ้น คุณอาจไม่ต้องการข้ามการทดสอบหน่วยใน Flutter อีกต่อไป
ทีมบรรณาธิการของ Toptal Engineering Blog ขอขอบคุณ Matija Becirevic และ Paul Hoskins สำหรับการตรวจสอบตัวอย่างโค้ดและเนื้อหาทางเทคนิคอื่นๆ ที่นำเสนอในบทความนี้