I am trying to follow the flutter cookbook guide for creating mockito tests. Here is the link https://docs.flutter.dev/cookbook/testing/unit/mocking.
After solving some little issues here and there with dependency conflicts I have reached a blocker I can't seem to find much information on.
When running the tests the import 'package:mocking/main.dart'; I am getting an error saying that it cannot be resolved. I am pretty new to dart, flutter, and mockito. From what I can understand this import is supposed to mock the functions from the main.dart file.
The main.dart file lives in the lib folder while the fetch_album_test.dart lives in the test folder.
I added the http(0.13.5) dependency, and the mockito(5.3.2) and build_runner(2.3.2) dev_dependency to the pubspec.yaml file and have run pub get. I also ran flutter pub run build_runner build.
I have followed the steps in the above link and also searched the web for mentions of "mocking" and "package:mocking" but I can't find anything. There are examples of building mock tests within the same .dart file as the class but I find that it would be helpful to keep the tests separate.
So what are my questions?
Why is the import 'package:mocking/main.dart'; not found?
Can this be resolved?
Is there a better solution?
The code is the same as the Flutter docs link but here it is again so you don't have to link.
lib/main.dart
`
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum(http.Client client) async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
const Album({required this.userId, required this.id, required this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final Future<Album> futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum(http.Client());
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
`
test/fetch_album_test.dart
`
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'fetch_album_test.mocks.dart';
// Generate a MockClient using the Mockito package.
// Create new instances of this class in each test.
#GenerateMocks([http.Client])
void main() {
group('fetchAlbum', () {
test('returns an Album if the http call completes successfully', () async {
final client = MockClient();
// Use Mockito to return a successful response when it calls the
// provided http.Client.
when(client
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
.thenAnswer((_) async =>
http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200));
expect(await fetchAlbum(client), isA<Album>());
});
test('throws an exception if the http call completes with an error', () {
final client = MockClient();
// Use Mockito to return an unsuccessful response when it calls the
// provided http.Client.
when(client
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
.thenAnswer((_) async => http.Response('Not Found', 404));
expect((client), throwsException);
});
});
}
`
I have tried moving around the files, and changing the import package name and I have done a lot of searching online.
Related
I list all pptx files from storage now I want to display these files inside my app using a file path. I used different packages like power file view, flutter file reader. but engine load failed all time so i need a material related opening pptx files inside my flutter app
Add this in your pubspec.yaml
pdftron_flutter:
git:
url: git://github.com/PDFTron/pdftron-flutter.git
main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:pdftron_flutter/pdftron_flutter.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _version = 'Unknown';
#override
void initState() {
super.initState();
initPlatformState();
PdftronFlutter.openDocument("https://pdftron.s3.amazonaws.com/downloads/pdfref.pdf");
}
// Platform messages are asynchronous, so we initialize via an async method.
Future<void> initPlatformState() async {
String version;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
PdftronFlutter.initialize();
version = await PdftronFlutter.version;
} on PlatformException {
version = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_version = version;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('PDFTron flutter app'),
),
body: Center(
child: Text('Running on: $_version\n'),
),
),
);
}
}
For more help read this blog
I'm trying to test a widget that receives and displays some data. This widget uses a controller. In the constructor I start receiving data, after which I execute the parser in a separate isolate. During the tests, the function passed to the compute is not executed until the end, and the widget state does not change. In fact, the structure of the widget looks a little more complicated, but I wrote smaller widget that saves my problem:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart/rxdart.dart';
class TestObj {
int id;
String name;
String number;
TestObj(this.id, this.name, this.number);
static List<TestObj> jsonListParser(String data) {
List mapObjs = json.decode(data) as List;
if (mapObjs.isEmpty) return [];
List<TestObj> testObjs = [];
for (final Map mapObj in mapObjs as List<Map>)
testObjs.add(
TestObj(
mapObj['id'] as int,
mapObj['name'] as String,
mapObj['number'] as String,
),
);
return testObjs;
}
}
class TestController {
final BehaviorSubject<List<TestObj>> testSubj;
final String responseBody =
'[{"id":2,"number":"1","name":"Объект 1"},{"id":1,"number":"2","name":"Объект 2"}]';
TestController(this.testSubj) {
getData(responseBody, testSubj);
}
Future<void> getData(
String responseBody, BehaviorSubject<List<TestObj>> testSubj) async {
List<TestObj> data = await compute(TestObj.jsonListParser, responseBody);
testSubj.sink.add(data);
}
}
class TestWidget extends StatelessWidget {
final BehaviorSubject<List<TestObj>> testSubj;
final TestController controller;
const TestWidget(this.testSubj, this.controller);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<TestObj>>(
stream: testSubj.stream,
builder: (context, snapshot) => snapshot.data == null
? const CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) => Text(snapshot.data[index].name),
),
);
}
}
void main() {
testWidgets('example test', (tester) async {
final BehaviorSubject<List<TestObj>> testSubj =
BehaviorSubject.seeded(null);
final TestController testController = TestController(testSubj);
await tester.pumpWidget(
TestWidget(testSubj, testController),
);
expect(find.byType(CircularProgressIndicator), findsNothing);
});
}
I have tried using tester.pump, tester.pumpAndSettle (crashed by timeout) and tester.runAsync, but so far without success. What are the solutions of this problem?
As indicated in runAsync docs, it is not supported to have isolates/compute in tests that are proceeded by pump().
To make a self-contained solution, check if you run in test environment or not in your code and skip isolates when you run in a test:
import 'dart:io';
if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) {
calc()
} else {
calcInIsolate()
}
So, I'm using BLoC and Provider packages in one app.
In my 'moviesprovider.dart' I am fetching some data from my API which returns a json, when app is opening first time. How can I get access to Provider.of(context) from main.dart in MultiProvider? Basically, I want to get access to the same instance of List movies, but don't know how.
The error I'm getting:
Error: Could not find the correct Provider above this MyApp Widget
This happens because you used a BuildContext that does not include the provider
of your choice.
Code:
Main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Movies(),
),
Provider<SwipeBloc>(create: (_) {
SwipeBloc()
..add(
LoadMoviesEvent(
movies: context.read<Movies>().movies,
),
);
}),
ChangeNotifierProvider.value(
value: User(),
),
ChangeNotifierProvider.value(
value: Auth(),
),
],
child: ...
}
}
movies_provider.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:movies_recomendations/constants.dart';
import 'package:http/http.dart' as http;
import './single_movie_provider.dart';
class Movies with ChangeNotifier {
String plotText = "";
List<Movie> _movies = [];
List<Movie> get movies {
return <Movie>[..._movies];
}
.....
Future<void> fetchAndSetMovies() async {
const url = 'http://192.168.1.142:8000/Desktop/textData.json';
try {
final response = await http.get(
Uri.parse(url),
);
String source = Utf8Decoder().convert(response.bodyBytes);
final extractedData =
List<Map<String, dynamic>>.from(json.decode(source));
final List<Movie> loadedMovies = [];
extractedData.forEach(
((movieInfo) => {
loadedMovies.add(Movie(
id: movieInfo['id'],
age: 12,
countries: List<String>.from(movieInfo['country']),
description: movieInfo['descriprion'],
frames: movieInfo['frames'],
genre: movieInfo['genre'],
poster: movieInfo['poster'],
premiereWorld: movieInfo['date'].toString(),
ratingIMDb: movieInfo['ratingIMDb'],
ratingKinopoisk: movieInfo['ratingKinopoisk'],
title: movieInfo['title'][1],
ifSeries: movieInfo['ifSeries'],
dateTo: movieInfo['dateTo'].toString(),
isFavourite: true,
seasons: movieInfo['seasons'],
)),
}),
);
_movies = loadedMovies;
notifyListeners();
} on Exception catch (e) {
print('error');
print(e.toString());
}
}
}
Swipe_event.dart
part of 'swipe_block.dart';
abstract class SwipeEvent extends Equatable {
const SwipeEvent();
#override
List<Object> get props => [];
}
class LoadMoviesEvent extends SwipeEvent {
final List<Movie> movies ;
LoadMoviesEvent({
required this.movies,
});
#override
List<Object> get props => [movies];
}
class SwipeLeftEvent extends SwipeEvent {
final Movie movie;
SwipeLeftEvent({
required this.movie,
});
#override
List<Object> get props => [movie];
}
class SwipeRightEvent extends SwipeEvent {
final Movie movie;
SwipeRightEvent({
required this.movie,
});
#override
List<Object> get props => [movie];
}
You probably need to move the code calling Provider.of(context) into its own widget. As the error implies you can't use Provider to retrieve dependencies within the same BuildContext you used to set the Provider scope. Creating a new widget will also generate a new BuildContext.
If you really need to use Provider.of(context) in the same class you define MultiProvider you could use the Builder widget to generate a new context.
So, to solve this problem you should NOT use BlocProvider in main.dart. You should use it in that direct widget where BLoC Provider will be implemented. So I use it in one screen - recomendations, so I write it there like this
class RecomendationsScreen extends StatelessWidget {
static const routeName = '/recomendations';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kBackgroundColor,
body: BlocProvider(
create: (_) => SwipeBloc()
..add(
LoadMoviesEvent(
movies: Provider.of<Movies>(context).movies,
),
),
child: RecomendationsBody(),
),
);
}
}
I'm trying to download an image from the network and save it locally in the Downloads folder of a computer. I need to achieve that for flutter web, I'm not sure how to do it.
I found some questions related to how to achieve download and save a file or an image for android and IOS, such as Flutter save a network image to local directory. I also took a look at How do I read and write image file locally for Flutter Web?. However, I don't see how those answers can help me.
I think that for IOS and Flutter I can use the following function without getting any error, but I don't know where the files are being saved in my emulator:
void _downloadAndSavePhoto() async {
var response = await http.get(Uri.parse(imageUrl));
try {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
File file = File('$tempPath/$name.jpeg');
file.writeAsBytesSync(response.bodyBytes);
} catch (e) {
print(e.toString());
}
}
However, when I try the above function for flutter web (using a chrome simulator) I get the following error:
MissingPluginException(No implementation found for method getTemporaryDirectory on channel plugins.flutter.io/path_provider)
I will be more than happy if someone knows a way to do it or have some suggestions to implement that functionality.
Thanks in advance!
To achieve this I would suggest you first to add the universal_html package to your pubspec.yaml because in the newer versions of Flutter you will get warnings for importing dart:html.
In pubspec.yaml:
dependencies:
flutter:
sdk: flutter
http: ^0.13.1 // add http
universal_html: ^2.0.8 // add universal_html
I created a fully working example Flutter web app, you can try it, but the only thing that interests you is the downloadImage function.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// if you don't add universal_html to your dependencies you should
// write import 'dart:html' as html; instead
import 'package:universal_html/html.dart' as html;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final imageUrls = <String>[
'https://images.pexels.com/photos/208745/pexels-photo-208745.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/1470707/pexels-photo-1470707.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/2671089/pexels-photo-2671089.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/2670273/pexels-photo-2670273.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.count(
crossAxisCount: 3,
children: imageUrls
.map(
(imageUrl) => ImageCard(imageUrl: imageUrl),
)
.toList(),
),
);
}
}
class ImageCard extends StatefulWidget {
#override
_ImageCardState createState() => _ImageCardState();
final String imageUrl;
ImageCard({
#required this.imageUrl,
});
}
class _ImageCardState extends State<ImageCard> {
Future<void> downloadImage(String imageUrl) async {
try {
// first we make a request to the url like you did
// in the android and ios version
final http.Response r = await http.get(
Uri.parse(imageUrl),
);
// we get the bytes from the body
final data = r.bodyBytes;
// and encode them to base64
final base64data = base64Encode(data);
// then we create and AnchorElement with the html package
final a = html.AnchorElement(href: 'data:image/jpeg;base64,$base64data');
// set the name of the file we want the image to get
// downloaded to
a.download = 'download.jpg';
// and we click the AnchorElement which downloads the image
a.click();
// finally we remove the AnchorElement
a.remove();
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () => downloadImage(widget.imageUrl),
child: Card(
child: Image.network(
widget.imageUrl,
fit: BoxFit.cover,
),
),
);
}
}
I'm trying to return a json string in Flutter. I'm using the print function to display the output in the console. However my code is returning the String twice. Here is my code:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(new HomePage());
class HomePage extends StatelessWidget {
Future<http.Response> fetchPost() async{
return await http.get('https://api.npoint.io/8c7aafe809d73af5f2b9');
}
void Data() async {
var jsonString = await fetchPost();
print(jsonString.body);
}
#override
Widget build(BuildContext context) {
Data();
return new MaterialApp(
home: new Center(
child: new Text('Data'),
),
);
}
}
The build method is called twice causing the whole widget to be called again. Consider converting StatelessWidget to StatefulWidget and add your http call method in
#overridde
initState() {
your code
}
I had the same issue and solved using try catch
Future<http.Response> fetchPost() async{
try{
return await http.get('https://api.npoint.io/8c7aafe809d73af5f2b9');
}
catch (e) {
print(e);
}
}