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.
Related
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>()));
});
}
});
I'm trying to get my token from the API but it's empty
This is dio functions
import 'package:dio/dio.dart';
class DioHelper
{
static Dio dio;
static init()
{
print('dioHelper Initialized');
dio = Dio(
BaseOptions(
baseUrl:'my api link',
receiveDataWhenStatusError: true,
));
}
static Future<Response> getData ({
String url,
Map<String,dynamic> query,
String lang = 'en',
String token,
Map<String,dynamic> data,
})async
{
dio.options.headers =
{
'lang':'$lang',
'Content-Type':'application/json',
'Authorization' : '$token'
};
return await dio.get(
url,
queryParameters: query
);
}
static Future<Response> postData ({
String url,
Map<String,dynamic> query,
Map<String,dynamic> data,
String lang = 'ar',
String token
})async
{
dio.options.headers =
{
'lang':'$lang',
'Content-Type':'application/json',
'Authorization' : '$token'
};
return await dio.post(
url,
queryParameters: query,
data: data,
);
}
static Future<Response> putData ({
String url,
Map<String,dynamic> query,
Map<String,dynamic> data,
String lang = 'ar',
String token
})async
{
dio.options.headers =
{
'lang':'$lang',
'Content-Type':'application/json',
'Authorization' : '$token'
};
return await dio.put(
url,
queryParameters: query,
data: data,
);
}
static Future<Response> deleteData ({
String url,
String lang = 'ar',
String token
})async
{
dio.options.headers =
{
'lang':'$lang',
'Content-Type':'application/json',
'Authorization' : '$token'
};
return await dio.delete(url);
}
}
and pass value in this variable
String token = '';
and fuctions sharedPreferences
import 'package:shared_preferences/shared_preferences.dart';
class CacheHelper
{
static SharedPreferences sharedPreferences;
static init () async {
sharedPreferences = await SharedPreferences.getInstance();
}
static dynamic getData(String key, ){
return sharedPreferences.get(key);
}
static Future<bool> saveData({ String key, dynamic value})async{
if(value is String) return await sharedPreferences.setString(key, value);
if(value is int) return await sharedPreferences.setInt(key, value);
if(value is bool) return await sharedPreferences.setBool(key, value);
return await sharedPreferences.setDouble(key, value);
}
static Future<bool> removeData (String key)async{
return await sharedPreferences.remove(key);
}
}
*CALL THE TOKEN*
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
Bloc.observer = MyBlocObserver();
await DioHelper.init();
await CacheHelper.init();
token = CacheHelper.getData('token');
var IsLogin = CacheHelper.getData( 'IsLogin');
var IsBoarding = CacheHelper.getData( 'IsBoarding');
print(IsLogin);
print(IsBoarding);
print(token);
var widget;
if (IsBoarding != null) {
if (token == null) {
widget = ShopingScreen();
} else {
widget = Login_Screen();
}
} else {
widget = BordScreen();
}
THANKS
Is there any solution
I described my problem a few days ago, but without details, there was no useful solution
Tried a lot but it didn't solve this problem. I tried wiping the phone data or reformatting the codes and restarting the device several times.
I am using this library https://pub.dev/packages/graphql_flutter for graphql in a flutter web application. Below code can be used to get authentication token:
import 'package:graphql_flutter/graphql_flutter.dart';
final HttpLink httpLink = HttpLink(
'https://api.github.com/graphql',
);
final AuthLink authLink = AuthLink(
getToken: () async => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
// OR
// getToken: () => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
);
but how can I put the token in the http header like x-api-key: xxxx when sending requests?
I have tried:
HttpLink link = HttpLink(
uri: 'https://api.github.com/graphql',
headers: <String, String>{
'x-api-key': 'xxxx',
},
);
but it gives me the error: The named parameter 'uri' isn't defined. Try correcting the name to an existing named parameter's name, or defining a named parameter with the name 'uri'.
Update: base on the answer from #Moaid
import 'package:graphql_flutter/graphql_flutter.dart';
typedef GetHeaders = FutureOr<Map<String, String>> Function();
class CustomAuthLink extends Link {
CustomAuthLink({
this.getHeaders,
});
final GetHeaders getHeaders;
#override
Stream<Response> request(Request request, [NextLink forward]) {
StreamController<Response> controller;
Future<void> onListen() async {
try {
final Map<String, String> headers = await getHeaders();
return request.updateContextEntry<HttpLinkHeaders>(
(_headers) => HttpLinkHeaders(
headers: <String, String>{
...headers,
},
),
);
} catch (error) {
controller.addError(error);
}
await controller.addStream(forward(request));
await controller.close();
}
controller = StreamController<Response>(onListen: onListen);
return controller.stream;
}
}
Base on the answer from #moaid-alrazhy and after checking how AuthLink is working
class CustomAuthLink extends Link {
CustomAuthLink();
#override
Stream<Response> request(Request request, [NextLink? forward]) async* {
// Some logic here
final AuthService authService = GetIt.I.get<AuthService>();
final String? token = authService.token;
final String deviceID = await DeviceInformation.deviceIMEINumber;
// TIP: do not forget getting new Request instance!
final Request req = request.updateContextEntry<HttpLinkHeaders>(
(HttpLinkHeaders? headers) => HttpLinkHeaders(
headers: <String, String>{
// put oldest headers
...headers?.headers ?? <String, String>{},
// and add a new headers
'Authorization': 'Bearer $token',
'x-device-id': deviceID,
},
),
);
// and "return" new Request with updated headers
yield* forward!(req);
}
}
Probably if you need to change only the name of the Authentication value you can edit the headerKey param
otherwise other parameters can be insert in the "defaultHeaders" fields of the HttpLink object. but I don't know if they can be use for authentication
you can add it to your HttpLink like this
HttpLink link = HttpLink(
'https://api.github.com/graphql',
headers: <String, String>{
'x-api-key': 'xxxx',
},
);
however this was in old versions .. now for more headers your have to write your own CustomAuthLink like
typedef GetHeaders = Future<Map<String, String>> Function();
class CustomAuthLink extends Link {
CustomAuthLink({
this.getHeaders,
}) : super(
request: (Operation operation, [NextLink forward]) {
StreamController<FetchResult> controller;
Future<void> onListen() async {
try {
final Map<String, String> headers = await getHeaders();
operation.setContext(<String, Map<String, String>>{
'headers': headers
});
} catch (error) {
controller.addError(error);
}
await controller.addStream(forward(operation));
await controller.close();
}
controller = StreamController<FetchResult>(onListen: onListen);
return controller.stream;
},
);
GetHeaders getHeaders;
}
I ma not able to set header with dio.I am tryng to set my access token to the header.I ma trying to set header so that every request doesnt required to call it.Here is my network class where i am trying to call header with dio
My network Class:
class NetworkUtil {
Dio _dio;
String token;
getToken() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
String getToken = preferences.getString(AppPrefernces.LOGIN_PREF);
return getToken;
}
NetworkUtil() {
///Create Dio Object using baseOptions set receiveTimeout,connectTimeout
BaseOptions options = BaseOptions(receiveTimeout: 5000, connectTimeout: 5000);
options.baseUrl = ApiConstants.BASE_URL;
_dio = Dio(options);
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (Options option) async{
//my function to recovery token
await getToken().then((result) {
LoginResponse loginResponse = LoginResponse.fromJson(jsonDecode(result));
token = loginResponse.accessToken;
});
option.headers = {
"Authorization": "Bearer $token"
};
}
));
}
///used for calling Get Request
Future<Response> get(String url, Map<String, String> params) async {
Response response = await _dio.get(url,
queryParameters: params,
options: Options(responseType: ResponseType.json));
return response;
}
///used for calling post Request
Future<Response> post(String url, Map<String, String> params) async {
Response response = await _dio.post(url,
data: params, options: Options(responseType: ResponseType.json));
return response;
}
}
I use this setup and it works fine for me.
Future<Dio> createDioWithHeader() async {
if (_dioWithHeader != null) return _dioWithHeader;
String token = await appSharedPreferences.getToken();
String userAgent = await getUserAgent();
print('User-Agent: $userAgent');
// base config
_dioWithHeader = Dio(BaseOptions(
connectTimeout: 10000,
receiveTimeout: 10000,
baseUrl: Config.apiBaseUrl,
contentType: 'application/json',
headers: {
'Authorization': token,
'User-Agent': userAgent
}));
// setup interceptors
return addInterceptors(_dioWithHeader);
}```
i would like to create a campaign using marketing API, here is the Curl code, i want to transform into an http post request:
AND MY HTTP REQUEST with the model class
Future<Campaign> createCampaign(String name,String objective,String
status) async {
final http.Response response = await http.post(
'https://graph.facebook.com/v7.0/act_<AD_ACCOUNT_ID>/campaigns',
headers: {HttpHeaders.authorizationHeader: "Basic },
body: jsonEncode(<String, String>{
'name': name,
'objective': objective,
'status': status
}),
);
if (response.statusCode == 201) {
return Campaign.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create Campaign.');
}
}
class Campaign {
final String name;
final String objective;
final String status;
final Map <String,dynamic> special_ad_categories;
final Map<String,dynamic> access_token;
Campaign({this.name,this.objective,this.status,this.special_ad_categories,
this.access_token});
factory Campaign.fromJson(Map<String, dynamic> json) {
return Campaign(
name: json['name'],
objective: json['objective'],
status: json['status'],
special_ad_categories: json['special_ad_categories'],
access_token: json['access_token'],
);
}
}
Try using dio package. It has API to send form data.
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart' as dio;
const AD_ACCOUNT_ID = '...';
const ACCESS_TOKEN = '...';
Future<Campaign> createCampaign(String name, String objective, String status, String categories) async {
try {
final formData = dio.FormData.fromMap({
'name': name,
'objective': objective,
'status': status,
'special_ad_categories': categories,
'access_token': ACCESS_TOKEN
});
final response = await dio.Dio().post(
'https://graph.facebook.com/v7.0/act_$AD_ACCOUNT_ID/campaigns',
data: formData,
);
if (response.statusCode == HttpStatus.created) {
return Campaign.fromJson(jsonDecode(response.data));
} else {
throw Exception('Failed to create Campaign.');
}
} on dio.DioError {
throw Exception('Failed to create Campaign.');
}
}
// example of calling: createCampaign('test', 'LINK_CLICKS', 'PAUSED', 'NONE');
Don't forget to replace AD_ACCOUNT_ID and ACCESS_TOKEN.
All parameters are in body. Try like this:
Future<http.Response> fetchAlbum() {
return http.post(
'https://your-url',
body: {
'name': name,
'objective': objective,
'status': status,
'special_ad_categories': [],
'access_token': accessToken,
},
);
}
I think you missed the account id in
'https://graph.facebook.com/v7.0/act_<AD_ACCOUNT_ID>/campaigns'