mocking Dio is not working to test my app in Flutter - flutter

I am trying to write unit tests for my request with Dio but I keep getting this error:
type 'Null' is not a subtype of type 'BaseOptions'
I tried adding the base options on DioMock in a lot of different ways but the test remains the same.
How can I fix it?
Bellow are my Network class and the test class.
class NetworkService {
final Dio dio;
NetworkService(this.dio){
dio.options.baseUrl = "https://food2fork.ca/api/recipe/search";
dio.options.headers['Authorization'] = 'Token 9c8b06d329136da358c2d00e76946b0111ce2c48';
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler){
print('base ${options.baseUrl}');
print("PATH: ${options.path}");
return handler.next(options);
},
onResponse: (response, handler){
print("RESPONSE: ${response.statusCode} PATH: ${response.requestOptions.path}");
return handler.next(response);
},
onError: (DioError e, handler){
print("ERROR: ${e.response?.statusCode} => PATH: ${e.requestOptions.path}");
return handler.next(e);
}
));
}
Future<List<Recipe>> getRecipe() async {
var response = await dio.get('/?page=1&query=beef');
print("response ${response.data}");
if(response.statusCode == 200){
final List<Recipe> recipeList = [];
for(Map<String, dynamic> recipe in response.data['results']){
recipeList.add(Recipe.fromJson(recipe));
}
return recipeList;
} else {
throw Exception('sss');
}
// ONBOARDING
}
}
class DioMock extends Mock implements DioForNative {}
class RecipeMock extends Mock implements Recipe {}
main() {
final dio = DioMock();
final service = NetworkService(dio);
dio.options.baseUrl = "https://food2fork.ca/api/recipe/search";
dio.options.headers = { 'Content-type': 'application/json', 'Accept': 'application/json' };
test("should return Onboarding Model", () async {
final response = Response(
requestOptions: RequestOptions(
path: 'gfh',
baseUrl: "fgh"
),
data: RecipeMock()
);
when(dio.get(
"https://food2fork.ca/api/recipe/search"))
.thenAnswer((_) async => response);
final result = await service.getRecipe();
expect(result, isA<Recipe>());
});
}

You should use https://pub.dev/packages/http_mock_adapter package to help you mock your DIO requests
See its example https://github.com/lomsa-dev/http-mock-adapter/blob/main/example/main.dart :
void main() async {
late Dio dio;
late DioAdapter dioAdapter;
Response<dynamic> response;
group('Accounts', () {
const baseUrl = 'https://example.com';
const userCredentials = <String, dynamic>{
'email': 'test#example.com',
'password': 'password',
};
setUp(() {
dio = Dio(BaseOptions(baseUrl: baseUrl));
dioAdapter = DioAdapter(dio: dio);
});
test('signs up user', () async {
const route = '/signup';
dioAdapter.onPost(
route,
(server) => server.reply(201, null),
data: userCredentials,
);
// Returns a response with 201 Created success status response code.
response = await dio.post(route, data: userCredentials);
expect(response.statusCode, 201);
});
...
final dioError = DioError(
error: {'message': 'Some beautiful error!'},
requestOptions: RequestOptions(path: path),
response: Response(
statusCode: 500,
requestOptions: RequestOptions(path: path),
),
type: DioErrorType.response,
);
test("should return a DioError", () async {
dioAdapter.onGet(
path,
(server) {
server.throws(404, dioError );
});
final result = await service.getOnboardingAnswer("lastAnswerId");
expect(result, throwsA(isA<DioError>()));
});
}
});

Related

client.post login timeout not working in flutter

I need to use timeout if post request not working so, I write below code:
class APIService {
static var client = http.Client();
static Future<bool> login(LoginRequestModel model) async {
Map<String, String> requestHeaders = {
'Content-Type': 'application/json',
};
var url = Uri.http(Config.apiURL, Config.loginAPI);
try {
final response = await client
.post(
url,
headers: requestHeaders,
body: jsonEncode(model.toJson()),
)
.timeout(const Duration(seconds: 5));
print("response:");
print(response);
if (response.statusCode == 200) {
//SHARED
await SharedService.setLoginDetails(loginResponseJson(response.body));
return true;
} else {
return false;
}
} on TimeoutException catch (e) {
// handle timeout
return false;
}
}
But never end await client.post method waiting althouth I add timeout. How can I solve this ?
You can try this:
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http;
final body = { 'email': email, 'password': password };
final client = http.Client();
http.Response res;
try {
res = await client
.post(
url,
headers: requestHeaders,
body: jsonEncode(model.toJson()),
.catchError((e) {
// SocketException would show up here, potentially after the timeout.
})
.timeout(const Duration(seconds: 5));
} on TimeoutException catch (e) {
// Display an alert, no internet
} catch (err) {
print(err);
return null;
}

how to resend multipart request

I am retrying my api call if get 401 response but when Retrying I am ending with an following exception
following is my code for retrying multipart I had used http_interceptor package for retrying Api Calls
interceptor.dart
class AuthorizationInterceptor extends InterceptorContract {
#override
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
final prefs = await SharedPreferences.getInstance();
final extractData =
json.decode(prefs.getString('userData')!) as Map<String, dynamic>;
final Map<String, String> headers = Map.from(request.headers);
headers['Authorization'] = await extractData['accessToken'];
print(
'this is from AuthorizationInterceptor: ${extractData['accessToken']}');
// TODO: implement interceptRequest
return request.copyWith(
headers: headers,
);
}
retry.dart
class ExpiredTokenRetryPolicy extends RetryPolicy {
BuildContext context;
ExpiredTokenRetryPolicy(this.context);
#override
// TODO: implement maxRetryAttempts
int get maxRetryAttempts => 2;
#override
Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
if (response.statusCode == 401) {
print('retry token started');
//perform token refresh,get the new token and update it in the secure storage
await Provider.of<Auth>(context, listen: false).restoreAccessToken();
return true;
}
return false;
}
I am using interceptors in my widget following is my code where I am using interceptors and using retry policy
#override
Widget build(BuildContext context) {
var flutterFunctions = Provider.of<FlutterFunctions>(context);
// print('this is from insert package${token.token}');
ApiCalls repository = ApiCalls(
client: InterceptedClient.build(
retryPolicy: ExpiredTokenRetryPolicy(context),
interceptors: [
AuthorizationInterceptor(),
],
),
);
following is my restore access token method
Future<void> restoreAccessToken() async {
print('restoreAccessToken started');
//print(token);
final url = '${Ninecabsapi().urlHost}${Ninecabsapi().login}/$sessionId';
var response = await http.patch(
Uri.parse(url),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': accessToken!
},
body: json.encode(
{"refresh_token": refreshtoken},
),
);
var userDetails = json.decode(response.body);
if (response.statusCode == 401) {
print(userDetails['messages']);
}
sessionId = userDetails['data']['session_id'];
accessToken = userDetails['data']['access_token'];
accessTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['access_token_expiry']),
);
refreshToken = userDetails['data']['refresh_token'];
refreshTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['refresh_token_expiry']),
);
final userData = json.encode({
'sessionId': sessionId,
'refreshToken': refreshToken,
'refreshExpiry': refreshTokenExpiryDate!.toIso8601String(),
'accessToken': accessToken,
'accessTokenExpiry': accessTokenExpiryDate!.toIso8601String()
});
//print(userDetails);
notifyListeners();
final prefs = await SharedPreferences.getInstance();
prefs.setString('userData', userData);
print("this is from restoreAcessToken :$userDetails");
final extractData =
json.decode(prefs.getString('userData')!) as Map<String, dynamic>;
print('restore access token: ${extractData['accessToken']}');
reset();
}
As a rule. You must NOT write using the same Stream/MultipartFile more than once. If you need to retry sending to the same destination, you have to use a new MultipartFile each time you retry.

No stub was found which matches the arguments of this method call

I'm writing a Unit test for my data source and face the problem No stub was found which matches the arguments of this method call
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:true_food/core/constant.dart';
import 'package:true_food/core/network_manager.dart';
import 'package:true_food/features/home/data/datasources/home_data_source.dart';
import 'package:true_food/features/home/data/models/shelve.dart';
import 'package:true_food/features/home/data/models/shelve_banner_list.dart';
import 'package:true_food/features/home/domain/entities/banner_list_request_model.dart';
import '../../../../core/utils/fixture_reader.dart';
import 'home_data_source_test.mocks.dart';
#GenerateMocks([NetworkManager])
main() {
late HomeDataSourceImpl dataSource;
late MockNetworkManager mockNetworkManager;
final successCode = 201;
final shelveUrl = Constant.baseUrl + APIRoute.shelveList;
final bannerUrl = Constant.baseUrl + APIRoute.bannerList;
setUp(() {
mockNetworkManager = MockNetworkManager();
dataSource = HomeDataSourceImpl(mockNetworkManager);
});
void onCallSuccess(Function body) {
group('Get list successfully', () {
body();
});
}
void onCallFailed(Function body) {
group('Get list unsuccessfully', () {
body();
});
}
BannerListRequestModel request = const BannerListRequestModel(
url:
"/weshop/api/TRUEID_TRUEFOOD/shelfContent?type=item&sort=sequence&itemGroup=6183b134b6d93f000180bf76&page={sys.page}&size={sys.size}&publishStatus=true",
location: BannerListRequestLocationModel(latitude: 0.0, longitude: 0.0),
page: 0,
size: 4);
Future<void> makeResponseSuccessfull(dynamic data) async {
final Response response =
Response(requestOptions: RequestOptions(path: 'path'), statusCode: 200);
when(mockNetworkManager.request(
path: shelveUrl,
method: Method.get,
param: {
"publishOnApp": true,
},
headers: null,
isFormData: false,
contentType: 'application/json',
isFullPath: false,
language: "th",
date: null,
file: null,
check: false,
)).thenAnswer((_) async => Right(response));
}
void makeBannerListResponseSuccessfull(dynamic data) async {
final Response response = Response(
requestOptions: RequestOptions(path: 'path'),
statusCode: 200,
data: data);
when(mockNetworkManager.request(
path: bannerUrl,
method: Method.post,
param: request.toMap(),
headers: null,
isFormData: false,
contentType: 'application/json',
isFullPath: false,
language: "th",
date: null,
file: null,
check: false,
)).thenAnswer((_) async => Right(response));
}
onCallSuccess(() {
test('should perform GET for get shelve list successfully', () async {
final json = jsonDecode(fixture("shelve_list_data.json"));
final List<ShelveModel> list =
List<ShelveModel>.from(json.map((e) => ShelveModel.fromJson(e)));
// arrange
await makeResponseSuccessfull(json);
// act
final rs = await dataSource.getList(publishOnApp: true);
// assert
expect(rs, list);
});
test('should perform POST for get banner list successfully', () async {
final json = jsonDecode(fixture("banner_list_data.json"));
final List<ShelveBannerListModel> list = List<ShelveBannerListModel>.from(
json.map((e) => ShelveBannerListModel.fromJson(e)));
// arrange
makeBannerListResponseSuccessfull(json);
// act
final rs = await dataSource.getBannerList(request: request);
// assert
expect(rs, list);
});
});
}
My NetworkManager class:
import 'dart:convert';
import 'dart:io';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:flutter_flavor/flutter_flavor.dart';
import 'package:injectable/injectable.dart';
import 'package:true_food/core/constant.dart';
import 'package:true_food/core/error/error.dart';
import 'package:true_food/core/error/exceptions.dart';
import 'package:true_food/core/local_storage.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:true_food/gen/locale_keys.g.dart';
import 'package:dio/adapter.dart';
#lazySingleton
class NetworkManager {
final dio = Dio();
Future<Either<ServerException, Response>> request({
required String path,
required Method method,
Map<String, dynamic>? param,
Map<String, dynamic>? headers,
bool isFormData = false,
String contentType = 'application/json',
bool isFullPath = false,
String language = 'vi',
// ignore: avoid_init_to_null
DateTime? date = null,
// ignore: avoid_init_to_null
FormData? file = null,
bool check = false,
}) async {
final String? _token = LocalStorage().getString(Constant.accessToken);
print('_token: $_token');
String domain = FlavorConfig.instance.variables["baseUrl"] ?? '';
String _finalPath = domain + path;
Map<String, dynamic> _finalParam = {};
if (param != null) {
print(param);
_finalParam.addAll(param);
}
try {
Map<String, dynamic>? _header =
_token != null ? {"Authorization": "Bearer " + _token} : null;
Response response = Response(requestOptions: RequestOptions(path: path));
Options optionsCommon = Options(
headers: _header,
sendTimeout: 30 * 1000,
receiveTimeout: 30 * 1000,
contentType: contentType,
);
//handle old printic with the request without content type
switch (method) {
case Method.get:
response = await dio.get(
_finalPath,
queryParameters: _finalParam,
options: optionsCommon,
cancelToken: null,
onReceiveProgress: null,
);
break;
case Method.post:
var data = isFormData ? file : _finalParam;
response = await dio.post(
_finalPath,
options: optionsCommon,
data: data,
cancelToken: null,
onReceiveProgress: null,
);
break;
case Method.put:
var data = isFormData ? file : _finalParam;
response = await dio.put(
_finalPath,
options: optionsCommon,
data: data,
cancelToken: null,
onReceiveProgress: null,
);
break;
case Method.delete:
var data = isFormData ? file : _finalParam;
response = await dio.delete(
_finalPath,
options: optionsCommon,
data: data,
cancelToken: null,
);
break;
}
print('==> DIO SUCCESS <$path> RESPONSE :\n'
'${response.data}');
// onSuccess(response.data, response.statusCode ?? 200);
return Right(response);
} on DioError catch (e) {
if (e.response != null) {
final Response rp = e.response!;
if (rp.statusCode == 502) {
// showToast("Server is maintaining, please login later".tr());
// UserManager.instance.logout();
// EasyLoading.dismiss();
}
print('~~> e: ${e.error}');
print('==> DIO FAILED <$path> RESPONSE :\n'
'${e.response?.data ?? ""}');
final data = rp.data;
try {
final json = jsonDecode(data);
return Left(ServerException(appError: AppError.fromJson(json)));
} on Exception {
return Left(ServerException(
appError: AppError(
status: 500,
title: LocaleKeys.something_went_wrong.tr(),
message: LocaleKeys.something_went_wrong.tr())));
}
} else {
return Left(ServerException(
appError: AppError(
status: 500,
title: LocaleKeys.something_went_wrong.tr(),
message: LocaleKeys.something_went_wrong.tr(),
),
));
}
} on SocketException {
return Left(ServerException(
appError: AppError(
status: 500,
title: LocaleKeys.something_went_wrong.tr(),
message: LocaleKeys.something_went_wrong.tr())));
}
}
}
enum Method { post, get, put, delete }
Pubspec:
Mockito version: "5.1.0"
Dio version "4.0.5"
MissingStubError: 'request'
No stub was found which matches the arguments of this method call:
request({path: api/shelves, method: Method.get, param: {publishOnApp: true}, headers: null, isFormData: false, contentType: application/json, isFullPath: false, language: vi, date: null, file: null, check: false})
Add a stub for this method using Mockito's 'when' API, or generate the MockNetworkManager mock with a MockSpec with 'returnNullOnMissingStub: true' (see https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).

Flutter Test for API Helper class using Dio

I am using a helper class to make request to server for that I am using Dio.
I want to test the helper class
this is my helper class
import 'package:dio/dio.dart';
import '../error/exceptions.dart';
enum HttpMethod { GET, POST, PUT, DELETE }
abstract class ApiClient {
Future<Response> request(
HttpMethod method,
String path, {
Map<String, dynamic>? headers,
Map<String, dynamic>? queryParams,
dynamic body,
});
}
/// This class provides http calls using dio package
class ApiClientImpl implements ApiClient {
final Dio dio;
ApiClientImpl({required this.dio});
#override
Future<Response> request(
HttpMethod method,
String path, {
Map<String, dynamic>? headers,
Map<String, dynamic>? queryParams,
dynamic body,
}) async {
// we can add headers here which are common for every restapi call
// headers = {'content-type': 'application/json'};
try {
final _response = await dio.request(
path,
options: Options(
method: _getApiMethodString(method),
headers: headers,
),
queryParameters: queryParams,
data: body,
);
return _response;
} on DioError {
throw FetchDataException('Dio Error Occurred');
}
}
String _getApiMethodString(HttpMethod method) {
switch (method) {
case HttpMethod.GET:
return 'GET';
case HttpMethod.POST:
return 'POST';
case HttpMethod.PUT:
return 'PUT';
case HttpMethod.DELETE:
return 'DELETE';
}
}
}
The test I wrote for it is
#GenerateMocks([Dio])
void main() {
late MockDio dio;
late ApiClientImpl apiClient;
setUp(() {
dio = MockDio();
apiClient = ApiClientImpl(dio: dio);
});
group('ApiService class methods test', () {
test('Should return response when request to server is made', () async {
// arrange
var successMessage = {'message': 'Success'};
const baseUrl = 'https://example.com/';
final options = Options(method: 'GET', headers: null);
when(dio.request(baseUrl,
options: options, queryParameters: anyNamed('queryParameters')))
.thenAnswer((_) async => Response(
requestOptions: RequestOptions(path: baseUrl),
data: successMessage,
statusCode: 200));
// act
final response = await apiClient.request(HttpMethod.GET, baseUrl);
// assert
expect(response.data, successMessage);
});
});
}
I am getting an error
MissingStubError: 'request'
No stub was found which matches the arguments of this method call:
request('https://example.com/', {data: null, queryParameters: null, cancelToken: null, options: Instance of 'Options', onSendProgress: null, onReceiveProgress: null})
How Can I write a correct test for the helper class.

How to set headers for POST with flutter and Dio

this is my code below, i'm stuck please help.
void getProducts() async {
String htoken = Utils.prefs.getString("token");
print(htoken);
try {
var dio = Dio(BaseOptions(headers: {"appusertoken": "$htoken"}));
//dio.options.headers["appusertoken"] = "$htoken";
Response response = await dio.post(
'APIURL',
);
print("data coming");
print(response.data);
} on DioError catch (e) {
print(e.response.data);
print(e.response.headers);
print(e.response.request);
}
}
it was throwing an error of data null.
I was able to fix the issues.
Add optional parameter options for dio.post method and define headers with Options class:
void getProducts() async {
String htoken = Utils.prefs.getString("token");
try {
Dio dio = Dio();
Response response = await dio.post("http://URL",
data: {},
options: Options(
headers: {"appusertoken": "$htoken"},
));
print("data coming");
print(response);
} on DioError catch (e) {
print(e.response.data);
print(e.response.headers);
print(e.response.request);
}
}
For example :
Dio _dio = new Dio();
_dio.options.contentType = Headers.formUrlEncodedContentType;
_dio.options.headers['Authorization'] = 'bearer $authToken';
or use :
final Map<String, dynamic> header = {'Authorization': 'bearer $authToken'};
enter code here
final responseData = await _dio.get(
Apis.account_profit,
options: RequestOptions(
method: 'GET', headers: header, baseUrl: Apis.apiBaseUrl),
);