diff --git a/pkgs/flutter_http_example/lib/main.dart b/pkgs/flutter_http_example/lib/main.dart index cda8589d3f..8f159f167e 100644 --- a/pkgs/flutter_http_example/lib/main.dart +++ b/pkgs/flutter_http_example/lib/main.dart @@ -7,13 +7,25 @@ import 'dart:convert'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart'; +import 'package:provider/provider.dart'; import 'book.dart'; import 'http_client_factory.dart' if (dart.library.html) 'http_client_factory_web.dart' as http_factory; void main() { - runWithClient(() => runApp(const BookSearchApp()), http_factory.httpClient); + // Some plugins may offer a way to inject a `package:http` `Client` so + // use `runWithClient` to control the `Client` that they use. + // + // `runWithClient` is not sufficient, however, because flutter tests do + // not preserve the `Zone` used as part of the `runWithClient` + // implementation. See https://github.com/flutter/flutter/issues/96939. + runWithClient( + () => runApp(Provider( + create: (_) => http_factory.httpClient(), + child: const BookSearchApp(), + dispose: (_, client) => client.close())), + http_factory.httpClient); } class BookSearchApp extends StatelessWidget { @@ -38,16 +50,18 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { List? _books; String? _lastQuery; + late Client _client; @override void initState() { super.initState(); + _client = context.read(); } // Get the list of books matching `query`. // The `get` call will automatically use the `client` configurated in `main`. Future> _findMatchingBooks(String query) async { - final response = await get( + final response = await _client.get( Uri.https( 'www.googleapis.com', '/books/v1/volumes', @@ -55,7 +69,6 @@ class _HomePageState extends State { ), ); - print(utf8.decode(response.bodyBytes)); final json = jsonDecode(utf8.decode(response.bodyBytes)) as Map; return Book.listFromJson(json); } diff --git a/pkgs/flutter_http_example/pubspec.yaml b/pkgs/flutter_http_example/pubspec.yaml index 5e9d0cc348..eb143f1363 100644 --- a/pkgs/flutter_http_example/pubspec.yaml +++ b/pkgs/flutter_http_example/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: flutter: sdk: flutter http: ^0.13.5 + provider: ^6.0.5 dev_dependencies: dart_flutter_team_lints: ^1.0.0 diff --git a/pkgs/flutter_http_example/test/widget_test.dart b/pkgs/flutter_http_example/test/widget_test.dart index a8bca283df..b550e89b01 100644 --- a/pkgs/flutter_http_example/test/widget_test.dart +++ b/pkgs/flutter_http_example/test/widget_test.dart @@ -3,58 +3,60 @@ // BSD-style license that can be found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:http/http.dart'; import 'package:flutter_http_example/main.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; import 'package:http/testing.dart'; +import 'package:provider/provider.dart'; const _singleBookResponse = ''' { - "kind": "books#volumes", - "totalItems": 2069, "items": [ { - "kind": "books#volume", - "id": "gcnAEAAAQBAJ", - "etag": "8yZ12V0pNUI", - "selfLink": "https://www.googleapis.com/books/v1/volumes/gcnAEAAAQBAJ", "volumeInfo": { "title": "Flutter Cookbook", - "subtitle": "100+ step-by-step recipes for building cross...", - "authors": [ - "Simone Alessandria" - ], - "publisher": "Packt Publishing Ltd", - "publishedDate": "2023-05-31", "description": "Write, test, and publish your web, desktop...", - }] + "imageLinks": { + "smallThumbnail": "http://books.google.com/books/content?id=gcnAEAAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api" + } + } + } + ] } '''; void main() { + Widget app(Client client) => + Provider(create: (_) => client, child: const BookSearchApp()); + testWidgets('Test initial load', (WidgetTester tester) async { - await tester.pumpWidget(const BookSearchApp()); + final mockClient = MockClient( + (request) async => throw StateError('unexpected HTTP request')); + + await tester.pumpWidget(app(mockClient)); expect(find.text('Please enter a query'), findsOneWidget); }); testWidgets('Test search', (WidgetTester tester) async { final mockClient = MockClient((request) async { - if (request.url.path != '/books/v1/volumes') { + if (request.url.path != '/books/v1/volumes' && + request.url.queryParameters['q'] != 'Flutter') { return Response('', 404); } return Response(_singleBookResponse, 200); }); - // `runWithClient` doesn't work because `pumpWidget` does not - // preserve the `Zone`. - await runWithClient( - () => tester.pumpWidget(const BookSearchApp()), () => mockClient); - await tester.enterText(find.byType(TextField), 'Flutter Cookbook'); + await tester.pumpWidget(app(mockClient)); + await tester.enterText(find.byType(TextField), 'Flutter'); await tester.pump(); + // The book title. expect(find.text('Flutter Cookbook'), findsOneWidget); - expect(find.text('Write, test, and publish your web, desktop...'), + // The book description. + expect( + find.text('Write, test, and publish your web, desktop...', + skipOffstage: false), findsOneWidget); }); }