How to mock http request in flutter integration test? - dart

I'm trying to do so using Mockito, this is my test:
import 'package:http/http.dart' as http;
import 'package:utgard/globals.dart' as globals;
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
class MockClient extends Mock implements http.Client {}
void main() {
group('Login flow', () {
final SerializableFinder loginContinuePasswordButton =
find.byValueKey('login_continue_password_button');
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
//await driver.close();
}
});
test('login with correct password', () async {
final client = MockClient();
when(client.post('http://wwww.google.com'))
.thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
globals.httpClient = client;
await driver.enterText('000000');
await driver.tap(loginContinuePasswordButton);
});
});
}
And this is my http request code:
Future<Map<String, dynamic>> post({
RequestType requestType,
Map<String, dynamic> body,
}) async {
final http.Response response =
await globals.httpClient.post('http://wwww.google.com');
print(response);
final Map<String, dynamic> finalResponse = buildResponse(response);
_managerErrors(finalResponse);
return finalResponse;
}
And here I have the global:
library utgard.globals;
import 'package:http/http.dart' as http;
http.Client httpClient = http.Client();
However I continue to receive http errors, that indicates to me that the http wasn't replaced by the mock.

The solution I found was to define the mock in test_driver/app.dart and call the runApp function after that:
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:utgard/business/config/globals.dart';
import 'package:utgard/main.dart' as app;
class MockClient extends Mock implements http.Client {}
void main() {
enableFlutterDriverExtension();
final MockClient client = MockClient();
// make your mocks here
httpClient = client;
runApp(app.MyApp());
}

Instead of
when(client.post('http://wwww.google.com'))
.thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
try any and then assert it later
when(
mockHttpClient.send(any),
).thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
// ...
final String capt = verify(client.send(captureAny)).captured;
expect(capt, 'http://wwww.google.com');
There's a small chance the call param is not exactly what you mock, so go with any is safer.

We don't see the code you're testing, but it's unlikely that it is going to make this request :
client.post('http://wwww.google.com')
It is anyway a good practice to use mock json files, and you don't want to change the request anytime those mock files change.
I recommend you to use Mocktail instead of Mockito.
That way, you can simulate any call with any :
// Simulate any GET :
mockHttpClient.get(any()))
// Simulate any POST :
mockHttpClient.post(any()))
Here's the complete solution :
class MockClient extends Mock implements http.Client {}
class FakeUri extends Fake implements Uri {}
void main() {
setUp(() {
registerFallbackValue(FakeUri()); // Required by Mocktail
});
tearDown(() {});
MockHttpClient mockHttpClient = MockHttpClient();
group('Login flow', () {
test('login with correct password', () async {
when(() => mockHttpClient.get(any())).thenAnswer(((_) async {
return Response(mockWeatherResponse, 200);
}));
// Call the functions you need to test here
}
}
}

Related

Mocktail error: No method stub was called from within `when()`

I'm using mocktail 0.3.0 package to test the request() method from HttpAdapter class. This method should call post() method of the Client class of the http package.
class HttpAdapter {
static const headers = {
HttpHeaders.contentTypeHeader: 'application/json',
HttpHeaders.acceptHeader: 'application/json',
};
final Client client;
HttpAdapter(this.client);
Future<void> request({
required String url,
required String method,
HttpClientBody? body,
}) async {
await client.post(Uri.parse(url), headers: HttpAdapter.headers);
}
}
class ClientSpy extends Mock implements Client {
void mockPost(String url) {
when(() => post(
Uri.parse(url),
headers: any(named: 'headers'),
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
}
}
When I test using the code below, everything goes well:
void main() {
test('Should call post() with correct parameters', () async {
// Arrange
final client = ClientSpy();
final sut = HttpAdapter(client);
when(() => client.post(
Uri.parse(url),
headers: HttpAdapter.headers,
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
// Act
await sut.request(url: url, method: method);
// Assert
verify(() => client.post(
Uri.parse(url),
headers: HttpAdapter.headers,
));
});
}
But if I replace the when() instruction by mockPost() method I get the error: Bad state: No method stub was called from within 'when()'. Was a real method called, or perhaps an extension method?
void main() {
test('Should call post() with correct parameters', () async {
// Arrange
final client = ClientSpy();
final sut = HttpAdapter(client);
client.mockPost();
// Act
await sut.request(url: url, method: method);
// Assert
verify(() => client.post(
Uri.parse(url),
headers: HttpAdapter.headers,
));
});
}
What am I doing wrong?
I think I figured out the problem.
The ClientSpy class implements the Client class from http package.
The Client class has a method called post(), witch is the one I want to call.
However, http package has a post() function also (I did't know that). So, I was calling the post() function instead Client.post() method.
The code below is working fine now:
class ClientSpy extends Mock implements Client {
void mockPost(String url) {
when(() => this.post(
Uri.parse(url),
headers: any(named: 'headers'),
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
}
}
or
import 'package:http/http.dart' as http;
class ClientSpy extends Mock implements http.Client {
void mockPost(String url) {
when(() => post(
Uri.parse(url),
headers: any(named: 'headers'),
)).thenAnswer(
(_) async => Response('{}', HttpStatus.ok),
);
}
}

Flutter/Mockito. Testing API provider with mockito, problem with api headers

I want to test my provider with mockito plugin but there is a problem with, as i understand with headers.
shows null when i do print(response)
if i remove headers from API
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Authorization': 'Bearer $token',
},
the test works fine but with headers:
NoSuchMethodError: The getter 'statusCode' was called on null.
Receiver: null
Tried calling: statusCode
dart:core Object.noSuchMethod
package:mba/resources/providers/post_provider.dart 38:20 PostProvider.fetchPosts
===== asynchronous gap ===========================
dart:async _AsyncAwaitCompleter.completeError
package:mba/resources/providers/post_provider.dart PostProvider.fetchPosts
===== asynchronous gap ===========================
dart:async _asyncThenWrapperHelper
package:mba/resources/providers/post_provider.dart PostProvider.fetchPosts
test/resources/providers/post_provider_test.dart 46:39 main.<fn>.<fn>
provider:
import 'package:http/http.dart' as http;
import 'package:mba/env.dart';
import 'package:mba/core/models/post_model.dart';
class PostProvider {
final http.Client client;
final String _api = API;
// Map<String, List<Post>> _postsList = Map();
PostProvider(this.client);
Future<Map<String, List<Post>>> fetchPosts(String token) async {
final response = await client.get(
'${_api}posts',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Authorization': 'Bearer $token',
},
);
print('-------------');
print(response);
print(response.statusCode);
print(response.body);
//todo: decode and mapping
if (response.statusCode == 200) {
return Map();
} else {
throw 'xxxx';
}
}
test file:
import 'dart:io';
import 'package:mba/resources/providers/post_provider.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_test/flutter_test.dart';
import 'package:matcher/matcher.dart';
class MockClient extends Mock implements http.Client {}
void main() {
String fixture(String name) =>
File('test/fixtures/posts/$name.json').readAsStringSync();
MockClient mockClient;
PostProvider dataSource;
setUp(() {
mockClient = MockClient();
dataSource = PostProvider(mockClient);
});
group('searchVideos', () {
test(
'returns YoutubeSearchResult when the call completes successfully',
() async {
when(
mockClient.get(
argThat(
startsWith('http://localhost:55005/api'),
),
),
).thenAnswer(
(_) async => http.Response(
fixture('posts'),
200,
headers: {'content-type': 'application/json; charset=utf-8'},
),
);
var result = await dataSource.fetchPosts('resocoder');
print(result);
},
);
});
}
Mockito will respond to your when sentence if and only if all args of the mockClient.get() matches what you have specified.
In you case, you specified an URL, and nothing more. As your code use an URL and headers mockito won't match and won't do your thenAnswer.
In your when sentence, you have to specify the headers argument to match you tested code
when(
mockClient.get(
argThat(
startsWith('http://localhost:55005/api'),
),
headers: anyNamed('headers'), // Add this line
),
).thenAnswer(...);
Here, the anyNamed('headers') will match any header.

Flutter Shared Preferences Auth FIle

I'm trying to write an auth file, with a list of finals with shared preferences values in it. I could import that auth file in my other files and i could get like the name or email without importing shared preferences in every file.
It would probably look way smoother and cleaner.
I thought something like this would have worked but it didn't
/// ------------Auth------------ ///
final email = getEmail();
getEmail() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('email');
}
Does anybody have any idea how to do this?
Greetings,
Jente
I assume you want to use the method in multiple files. The problem with your code is that the getEmail method is marked async that means it will have to return a Future. Think about it like this, when you mark a method as async it means it will return something (or finish executing) in the near future. When ? Well you don't know exactly when, so you'll need to get "notified" when the method is "done", that's why you'll use a Future. Something like this:
Future<String> getEmail() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('email');
}
class ThisIsTheClassWhereYouWantToUseTheFunction {
//let's say you have a method in your class called like the one below (it can be any other name)
void _iNeedTheEmailToPrintIt() {
//this is the way you can call the method in your classes, this class is just an example.
getEmail().then((thisIsTheResult){ // here you "register" to get "notifications" when the getEmail method is done.
print("This is the email $thisIsTheResult");
});
}
}
you can define a class Auth or much better a scoped_model.
Here's a class implementation
class Auth {
get email() {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('email');
}
set email(String em) {
final SharedPreferences prefs = await SharedPreferences.getInstance();
pref.setString('email', em);
}
}
and now you can call it in your widgets :)
Try this;
make dart file (Filename and Class Name ShareUtils)
add follow Code
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
class ShareUtils {
static ShareUtils _instance;
SharedPreferences ShareSave;
factory ShareUtils() => _instance ?? new ShareUtils._();
ShareUtils._();
void Instatce() async {
ShareSave = await SharedPreferences.getInstance();
}
Future<bool> set(key, value) async {
return ShareSave.setString(key, value);
}
Future<String> get(key) async {
return ShareSave.getString(key);
}
}
2.Add main.dart
class MyApp extends StatelessWidget {
static ShareUtils shareUtils;
#override
Widget build(BuildContext context) {
ThemeData mainTheme = new ThemeData(
);
shareUtils = new ShareUtils();
shareUtils.Instatce();
MaterialApp mainApp = new MaterialApp(
title: "Your app",
theme: mainTheme,
home: new SplashPage(),
debugShowCheckedModeBanner: true,
routes: <String, WidgetBuilder>{
"RegisterPage": (BuildContext context) => new RegisterPage(),
"HomePage": (BuildContext context) => new HomePage(),
},
);
return mainApp;
}
}
3.SET
void UserInfo(code, token) async{
await MyApp.shareUtils.set("token", token);
await MyApp.shareUtils.set("code", code);
await Navigator.of(context).pushNamed("HomePage");
}
4.GET
Future NextPage() async {
MyApp.shareUtils.get("token").then((token) {
print(token);
if (token == null || token == "") {
Navigator.of(context).popAndPushNamed("RegisterPage");
} else {
Navigator.of(context).popAndPushNamed("HomePage");
}
});
}
Hope to help.

Flutter code to create fcm token in ScopedModel and save to firestore not working

I'm trying to create an fcm token and save it to firestore during login, but no success. The login logic is in a ScopedModel class:
void signIn({#required String email, #required String pass,
#required VoidCallback onSuccess, #required VoidCallback onFail}) async {
isLoading = true;
notifyListeners();
_auth.signInWithEmailAndPassword(email: email, password: pass).then(
(user) async {
firebaseUser = user;
await _loadCurrentUser();
onSuccess();
isLoading = false;
notifyListeners();
}).then((_) async{
print("calling _saveFCMtoken");
await _saveFCMtoken();
}).catchError((e){
onFail();
print("erro1: ${e.toString()}");
isLoading = false;
notifyListeners();
});
}
Future<Null> _saveFCMtoken() async {
print("calling _getToken");
await _messaging.getToken().then((token) async{
print("token: $token");
}).then((token) async{
print("calling saveToken");
await firestore.instance.collection("fcm")
.document(firebaseUser.uid)
.setData({"token":token});
}).catchError((e){
print("erro2: ${e.toString()}");
});
}
No errors are thrown and the login succeeds normally. "print("calling _getToken");" is reached but never "print("token: $token");" nor "print("calling saveToken");"
Tried taking out "await" from "await _messaging.getToken().then((token) async{..." but the result is the same.
Here are my imports and init variables
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
class UserModel extends Model {
final FirebaseMessaging _messaging = FirebaseMessaging();
FirebaseAuth _auth = FirebaseAuth.instance;
FirebaseUser firebaseUser;
Need help figuring out how to make this work. Please anyone?

custom annotation / Metadata in dart lang

Can any one explain me the use of annotations in Dart?
In the documentations, I found this example:
library todo;
class todo {
final String who;
final String what;
const todo(this.who, this.what);
}
followed by
import 'todo.dart';
#todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
so, what shall I write in the main() to get the doSomething() function executed?
thanks
Something like
import 'dart:mirrors';
import 'do_something.dart';
import 'todo.dart';
void main() {
currentMirrorSystem().libraries.forEach((uri, lib) {
//print('lib: ${uri}');
lib.declarations.forEach((s, decl) {
//print('decl: ${s}');
decl.metadata.where((m) => m.reflectee is Todo).forEach((m) {
var anno = m.reflectee as Todo;
if(decl is MethodMirror) {
print('Todo(${anno.who}, ${anno.what})');
((decl as MethodMirror).owner as LibraryMirror).invoke(s, []);
};
});
});
});
}

Resources