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

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.

Related

Dio unit test error, type 'Null' is not a subtype of type 'Interceptors'

Currently I am working on my flutter app using Dio package as my networking and I want to create test on the dio post (wrapped within appclient). Here my code for
app_client.dart
abstract class AppClient{
Future<Map<String, dynamic>> get(String path, {Map<String, String>? queryParameters, Map<String, String>? headers});
Future<Map<String, dynamic>> post(String path, dynamic data, {Map<String, String>? queryParameters, Map<String, String>? headers});
}
class AppClientImpl implements AppClient{
late Dio dio;
late Interceptors interceptors;
AppClientImpl({required this.dio}){
//get language code
String languageCode = StorageUtil.getSavedLanguage();
if(languageCode == "id-ID"){
languageCode = "id_ID";
}else{
languageCode = "en_US";
}
BaseOptions baseOptions = BaseOptions(
baseUrl: ServiceUrl.baseUrl,
headers: {
"Accept" : "application/json",
"Content-Type" : "application/json",
},
queryParameters: {
"language" : languageCode,
"channel" : "mobile"
}
);
dio.options = baseOptions;
interceptors = Interceptors();
interceptors.add(LogInterceptor(request: true, requestBody: true, requestHeader: true, responseBody: true, responseHeader: true));
//COMMENT BECAUSE FAILED WHEN TESTING
dio.interceptors.addAll(interceptors); // ERROR HERE!!!!
}
#override
Future<Map<String, dynamic>> get(String path, {Map<String, String>? queryParameters, Map<String, String>? headers}) async {
// return await dio.get(path, queryParameters: queryParameters);
throw UnimplementedError();
}
Future<Map<String, dynamic>> post(String path, dynamic data, {Map<String, String>? queryParameters, Map<String, String>? headers}) async{
Response response = await dio.post(path, queryParameters: queryParameters, data: Map<String, dynamic>.from(data));
if(response.statusCode == 200){
return jsonDecode(response.data);
}else{
throw Exception();
}
}
}
and here is my test class
class MockDio extends Mock implements Dio{}
void main(){
late MockDio mockDio;
late AppClientImpl appClient;
setUp((){
mockDio = MockDio();
appClient = AppClientImpl(dio: mockDio);
});
final tResponse = jsonDecode(fixture("token/token_success.json"));
final tData = {};
group("post method", (){
test(
"should return data when status code is 200",
()async{
when(
() => mockDio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))
).thenAnswer(
(invocation) async => Response(requestOptions: RequestOptions(path: "/sample"), data: fixture("token/token_success.json"), statusCode: 200)
);
final result = await appClient.post("/sample", tData);
verify(() => mockDio.post(any(), queryParameters: any(named: "queryParameters"), data: any(named: "data"))).called(1);
expect(result, tResponse);
}
);
});
}
As you can see, I inject dio instance to my appclient class and add global configuration there including interceptors.
I think everything is ok until I get these error.
Testing started at 08.54 ...
package:dio/src/dio.dart 46:20 MockDio.interceptors
package:eazyconnect/data/network/app_client/app_client.dart 44:9 new AppClientImpl
test/data/network/app_client/app_client_test.dart 23:17 main.<fn>
type 'Null' is not a subtype of type 'Interceptors'
Why this is happen? Any help and suggestion would be great!
Thanks!
You need mock interceptors too. You need pass interceptors like parameters, for class http, you can see the list interceptors have setted on Dio instance dont no in DioMock, that's why the error are happening

How to auth in SignalR wih token in Dart?

How to get access token in SignalR package?
I get access token doing POST request and after that I get the access token. I have a model where I have parsed JSON and have token field.
Auth authFromJson(String str) => Auth.fromJson(json.decode(str));
String authToJson(Auth data) => json.encode(data.toJson());
class Auth {
Auth({
this.token,
this.user,
});
final String? token;
final User? user;
POST request to API to get accesss token which I got succesfully:
Future<Auth> getToken() async {
String _email = "admin";
String _password = "admin";
Map<String, String> headers = {
'Content-Type': 'application/json',
'accept': ' */*'
};
final body = {
'username': _email,
'password': _password,
};
var response = await http.post(
Uri.parse("http://******/login"),
headers: headers,
body: jsonEncode(body),
);
print(response.body);
print(response.statusCode);
var jsonResponse = jsonDecode(response.body);
return Auth.fromJson(jsonResponse);
}
What I have in print in my console:
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA","user":{}}
After all this stuff I opened docs and found out how SignalR package handle token auth and did the same thing:
Future<List> fetchLists() async {
final httpConnectionOptions = HttpConnectionOptions(
accessTokenFactory: () => getToken().then((value) => value.token ?? ''),
);
final hubConnection = HubConnectionBuilder()
.withUrl('http://*****/hub',
options: httpConnectionOptions)
.build();
await hubConnection.start();
So after all of this I got this error [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: 302: Found
it means what I should add access token to each of requests and I do, but still get this error. How can i solve it or may be there is anoher way to add token in HubConnectionBuild?
There is parameter in accessTokenFactory which accept a function and have return type String so make a function which return token .
below attached code for your reference-
_hubConnection = HubConnectionBuilder()
.withUrl(chaturl,
options: HttpConnectionOptions(
headers: defaultHeaders,
accessTokenFactory: () async => await getToken() //define a function which return token
))
withAutomaticReconnect(retryDelays: [
20000,
]
).build();
//get token method
Future<dynamic> getToken() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
if (sharedPreferences.containsKey("token")) {
print(sharedPreferences.getString("token"));
return sharedPreferences.getString("token");
} else {
return null;
}
}

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),
);
}
}

How to mock http request in flutter integration test?

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
}
}
}

Http POST request with json content-type in dart:io

How to perform HTTP POST using the console dart application (using dart:io or may be package:http library. I do something like that:
import 'package:http/http.dart' as http;
import 'dart:io';
http.post(
url,
headers: {HttpHeaders.CONTENT_TYPE: "application/json"},
body: {"foo": "bar"})
.then((response) {
print("Response status: ${response.statusCode}");
print("Response body: ${response.body}");
}).catchError((err) {
print(err);
});
but get the following error:
Bad state: Cannot set the body fields of a Request with content-type "application/json".
This is a complete example. You have to use json.encode(...) to convert the body of your request to JSON.
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
var url = "https://someurl/here";
var body = json.encode({"foo": "bar"});
Map<String,String> headers = {
'Content-type' : 'application/json',
'Accept': 'application/json',
};
final response =
http.post(url, body: body, headers: headers);
final responseJson = json.decode(response.body);
print(responseJson);
Generally it is advisable to use a Future for your requests so you can try something like
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
Future<http.Response> requestMethod() async {
var url = "https://someurl/here";
var body = json.encode({"foo": "bar"});
Map<String,String> headers = {
'Content-type' : 'application/json',
'Accept': 'application/json',
};
final response =
await http.post(url, body: body, headers: headers);
final responseJson = json.decode(response.body);
print(responseJson);
return response;
}
The only difference in syntax being the async and await keywords.
From http.dart:
/// [body] sets the body of the request. It can be a [String], a [List<int>] or
/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
/// used as the body of the request. The content-type of the request will
/// default to "text/plain".
So generate the JSON body yourself (with JSON.encode from dart:convert).

Resources