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

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

Related

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

Using JSON Files for localization in GetX

I am trying to read translation files eg. en.json to use with GetX localization. I tried the following code but no success. Is there any better way of doing it? Somebody suggested using auto_localize with GetX, but I'll really appreciate if I can proceed without an extra package
Map<String, Map<String, String>> get keys => {
"ar": readJson("ar"),
"en": readJson("en"),
}
I tried loading the localization information using the following function
// Fetch content from the json file
Map<String, String> readJson(String languageCode) {
Map data = {};
rootBundle
.loadString('assets/translations/$languageCode.json')
.then((response) {
data = json.decode(response);
});
return data.map((key, value) {
return MapEntry(key, value.toString());
});
}
DebugPrint() gets shows that the files were successfully loaded. However, trying to display the loaded Maps doesn't work
The readJson should be Future and because of that the empty Map returned at the end.
The way i did
create languages.json file and put all texts to it.
[
{
"name": "English",
"code": "en_US",
"texts": {
"hello": "Hello World"
}
},
{
"name": "Arabic",
"code": "ar_AE",
"texts": {
"hello": "مرحبا بالعالم"
}
}
]
implement localization class like this
class LocalizationService extends Translations {
#override
Map<String, Map<String, String>> get keys => {};
static Future<void> initLanguages() async {
final _keys = await readJson();
Get.clearTranslations();
Get.addTranslations(_keys);
}
static Future<Map<String, Map<String, String>>> readJson() async {
final res = await rootBundle.loadString('assets/languages.json');
List<dynamic> data = jsonDecode(res);
final listData = data.map((j) => I18nModel.fromJson(j)).toList();
final keys = Map<String, Map<String, String>>();
listData.forEach((value) {
final String translationKey = value.code!;
keys.addAll({translationKey: value.texts!});
});
return keys;
}
}
As you see after getting the Map data, i used Get.addTranslations(); to set language keys.
this is a GetX function to add languages on air!
create model class to parse json data
class I18nModel {
String? name;
String? code;
Map<String, String>? texts;
I18nModel(
{this.name, this.code, this.texts});
I18nModel.fromJson(Map<String, dynamic> json) {
name = json['name'];
code = json['code'];
if (json['texts'] != null) {
texts = Map<String, String>.from(json['texts']);
}
}
}
And finally call await LocalizationService.initLanguages(); before going to your first Route.

How to fix strange dart error on http call

I am getting the following error at run time I am doing an http call and getting json back.
"_TypeError (type 'List' is not a subtype of type '() => void')"
Here is my code
class _ForumPostsState extends State<ForumPosts> {
List data;
String categoryID = 'D64D0737-746D-4562-8C10-6445F4069A92';
Future<String> getPostsByCategory() async {
var response = await http.post(
Uri.encodeFull("http://api/ForumPostsByCategory"),
headers: {"Content-Type": "application/json",
'Accept': 'application/json',},
body: json.encode({'categoryID' : categoryID }));
this.setState(
data = json.decode(response.body)
);
print(data[1]["title"]);
return "Success!";
}
The error is thrown on this line
data = json.decode(response.body)
While debugging I noticed that the JSON was there it just errors on the data = json.decode call.
Change this:
this.setState(
data = json.decode(response.body)
);
To this:
this.setState(() {
data = json.decode(response.body)
}
);
More info here: https://docs.flutter.io/flutter/widgets/State/setState.html

Post request with Cookies in Flutter

I'm trying to add Cookies to my request:
Here i get csrftoken with a GET request:
Future<String> getCsrftoken() async{
var response = await http.get(Uri.encodeFull('http://test/accounts/login/'));
var csrftoken = response.headers.remove('set-cookie').substring(10,74); //csrf
64 chars
return csrftoken;
}
Here i'm trying to perform the POST (application/x-www-form-urlencoded) request using the package Dio.
getSessionId() async {
var csrf = await getCsrftoken();
var cj = new CookieJar();
List<Cookie> cookies = [new Cookie("csrftoken", csrf)];
cj.saveFromResponse(Uri.parse("http://test/accounts/login/"), cookies);
List<Cookie> results = cj.loadForRequest(Uri.parse("http://test/accounts/login/"));
var dio = new Dio(new Options(
baseUrl: "http://test/accounts/login/",
connectTimeout: 5000,
receiveTimeout: 100000,
// 5s
headers: {
},
contentType: ContentType.JSON,
// Transform the response data to a String encoded with UTF8.
// The default value is [ResponseType.JSON].
responseType: ResponseType.PLAIN
));
Response<String> response;
response = await dio.post("",
data: {
"username": "username",
"password": "password",
"csrfmiddlewaretoken" : getCsrftoken()
},
// Send data with "application/x-www-form-urlencoded" format
options: new Options(
contentType: ContentType.parse("application/x-www-form-urlencoded")),
);
print(response.statusCode);
}
I get 403 status code, because i need to add as a cookie csrftoken.
How should I proceed?
From the Dio Dart API Docs:
Cookie Manager
You can manage the request/response cookies using cookieJar .
The dio cookie manage API is based on the withdrawn cookie_jar.
You can create a CookieJar or PersistCookieJar to manage cookies automatically, and dio use the CookieJar by default, which saves the cookies in RAM. If you want to persists cookies, you can use the PersistCookieJar class, the example codes as follows:
var dio = new Dio();
dio.cookieJar=new PersistCookieJar("./cookies");
PersistCookieJar is a cookie manager which implements the standard cookie policy declared in RFC. PersistCookieJar persists the cookies in files, so if the application exit, the cookies always exist unless call delete explicitly.
More details about cookie_jar see : https://github.com/flutterchina/cookie_jar .
Check if the csrftoken needs to be passed in the header and the cookie or just one of them. It sometimes needs to be included as a header, which is shown in the example below, but the header name varies. To persist cookies, use a PersistCookieJar. Other options are persisted through BaseOptions (previously named Options).
Add to pubspec.yaml the latest versions of these plugins
path_provider: ^1.1.0
dio: ^2.1.6
cookie_jar: ^1.0.0
In a new class named webFunctions:
import 'dart:io';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
class webFunctions {
final Dio _dio = Dio();
PersistCookieJar persistentCookies;
final String URL = "http://test/";
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<Directory> get _localCoookieDirectory async {
final path = await _localPath;
final Directory dir = new Directory('$path/cookies');
await dir.create();
return dir;
}
Future<String> getCsrftoken() async{
try {
String csrfTokenValue;
final Directory dir = await _localCoookieDirectory;
final cookiePath = dir.path;
persistentCookies = new PersistCookieJar(dir: '$cookiePath');
persistentCookies.deleteAll(); //clearing any existing cookies for a fresh start
_dio.interceptors.add(
CookieManager(persistentCookies) //this sets up _dio to persist cookies throughout subsequent requests
);
_dio.options = new BaseOptions(
baseUrl: URL,
contentType: ContentType.json,
responseType: ResponseType.plain,
connectTimeout: 5000,
receiveTimeout: 100000,
headers: {
HttpHeaders.userAgentHeader: "dio",
"Connection": "keep-alive",
},
); //BaseOptions will be persisted throughout subsequent requests made with _dio
_dio.interceptors.add(
InterceptorsWrapper(
onResponse:(Response response) {
List<Cookie> cookies = persistentCookies.loadForRequest(Uri.parse(URL));
csrfTokenValue = cookies.firstWhere((c) => c.name == 'csrftoken', orElse: () => null)?.value;
if (csrfTokenValue != null) {
_dio.options.headers['X-CSRF-TOKEN'] = csrfTokenValue; //setting the csrftoken from the response in the headers
}
return response;
}
)
);
await _dio.get("/accounts/login/");
return csrfTokenValue;
} catch (error, stacktrace) {
print("Exception occured: $error stackTrace: $stacktrace");
return null;
}
}
getSessionId() async {
try {
final csrf = await getCsrftoken();
FormData formData = new FormData.from({
"username": "username",
"password": 'A *passphrase* is stronger than a password.',
"csrfmiddlewaretoken" : '$csrf'
});
Options optionData = new Options(
contentType: ContentType.parse("application/x-www-form-urlencoded"),
);
Response response = await _dio.post("/accounts/login/", data: formData, options: optionData);
print(response.statusCode);
} on DioError catch(e) {
if(e.response != null) {
print( e.response.statusCode.toString() + " " + e.response.statusMessage);
print(e.response.data);
print(e.response.headers);
print(e.response.request);
} else{
print(e.request);
print(e.message);
}
}
catch (error, stacktrace) {
print("Exception occured: $error stackTrace: $stacktrace");
return null;
}
}
}

Resources