I am trying to create a test for timeout using Dio, I expect get DioError with type CONNECT_TIMEOUT then throw a custom exception
My test I mock Dio with Mockito and try throw DioError
test(
'Should throw [ConnectionTimeOutException] when reach timeout',
() async {
//arange
when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
when(mockDio.post(paths.login, data: tParams.toJson())).thenThrow(
(_) async => DioError(type: DioErrorType.CONNECT_TIMEOUT));
//act
final call = loginDataSource.login;
//assert
expect(() => call(params: tParams),
throwsA(TypeMatcher<ConnectTimeOutException>()));
},
);
My data source class:
class LoginDataSourceImpl implements LoginDataSource {
final Dio dio;
final NetworkInfo networkInfo;
LoginDataSourceImpl({#required this.dio, #required this.networkInfo});
#override
Future<CredencialModel> login({#required Params params}) async {
if (!await networkInfo.isConnected) {
throw NoNetworkException();
}
try {
final response = await dio.post(paths.login, data: params.toJson());
if (response.statusCode == 200) {
return CredencialModel.fromJson(response.data);
} else if (response.statusCode == 400) {
final error = ResponseError.fromJson(response.data);
switch (error.error) {
case 'invalid_request':
throw InvalidRequestException();
break;
case 'invalid_device':
throw InvalidDeviceException();
break;
case 'invalid_user_credentials':
throw InvalidUserCredentialException();
break;
case 'user_disabled':
throw UserDisableException();
default:
throw UnknowException();
}
} else if (response.statusCode == 500) {
throw ServerException();
} else {
throw UnknowException();
}
} on DioError catch (e) {
if (e.type == DioErrorType.CONNECT_TIMEOUT) {
throw ConnectTimeOutException();
} else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
} else {
throw UnknowException();
}
}
}
}
The result of the test is:
Expected: throws <Instance of 'ConnectTimeOutException'>
Actual: <Closure: () => Future<CredencialModel>>
Which: threw <Closure: (dynamic) => DioError>
stack package:mockito/src/mock.dart 385:7
How can i solve this issue and create a Timeout test with Dio?
There are a couple of problems with your approach.
First, you are testing an async method but you are not awaiting it. This is going to cause the raw Future object to be returned to the expect function which is going to consider it a successful call, even if the future ends up throwing an error. You will need to await your call, although doing so as a closure passed to expect is awkward. I would suggest wrapping the asynchronous call in a try/catch instead.
Second, you are providing a closure to Mockito's thenThrow method. This method takes whatever you give to it and uses it as the actual thrown value, so it isn't going to call the closure you passed to it - it will just throw it as-is.
Fixing these both, you end up with this:
test(
'Should throw [ConnectionTimeOutException] when reach timeout',
() async {
// arrange
when(mockNetworkInfo.isConnected)
.thenAnswer(true);
when(mockDio.post(paths.login, data: tParams.toJson()))
.thenThrow(DioError(type: DioErrorType.CONNECT_TIMEOUT));
// act
final call = loginDataSource.login;
// assert
try {
await call(params: tParams);
} catch(e) {
expect(e, isInstanceOf<ConnectTimeOutException>());
}
},
);
Related
I have a weird error with my Flutter code involving a Future<T> return type. I have a fairly simple piece of code that makes a get request to the backend, and a .then clause to handle the return. Everything's fine, and as soon as I add onError to handle possible back error (namely 403/404 errors), I have an issue regarding the return type, quoting that Future<dynamic> can't be returned when I expect a Future<String?>, and that's in spite of onError always returning null.
Any idea how I can fix that behavior? Thanks in advance !
Code:
Future<String?> getUserStatus(String id) async {
return requestManager.get("/users/$id/status")
.then((response) {
final dynamic userStatus =
(response as Map<String, dynamic>)["status"];
if (unsubStatus == null) {
return Future.value();
}
return Future.value(userStatus.toString());
}, onError: (error) {
print("An error occured when reading response : $error");
return null;
}).onError((error, stackTrace) => Future.value("NoStatus")); // I also tried to return null
}
Error:
A value of type 'Future<dynamic>' can't be returned from an async function with return type 'Future<String?>'.
- 'Future' is from 'dart:async'.
}).onError((error, stackTrace) => Future.value("NoStatus"));
I recommended using try bloc and await instead of using then and onError:
Future<String?> getUserStatus(String id) async {
try {
var response = await requestManager.get("/users/$id/status");
final dynamic unsubStatus = (response as Map<String, dynamic>)["status"];
if (unsubStatus == null) {
return null;
} else {
return unsubStatus.toString();
}
} catch (e) {
print("An error occured when reading response : $e");
return null;
}
}
Future<String?> getUserStatus(String id) async {
final result =await requestManager.get("/users/$id/status");
final dynamic userStatus = (response as Map<String, dynamic>)["status"];
if (unsubStatus == null) {
return Future.value();
}
return Future.value(userStatus.toString());
}
I'm a beginner developer and I have problem with implementation of BloC framework. Let's assume that I have this code (Model, NetworkService, Repository, Cubit, State, Widget):
class NetworkService {
Future getData(Uri uri) async {
try {
http.Response httpsResponse = await http.get(
uri,
headers: {
// some headers //
},
);
if (httpsResponse.statusCode == 200) {
return httpsResponse.body;
} else {
throw 'Request failed with status: ${httpsResponse.statusCode}';
}
} catch (e) {
// What I shloud return here?
return e.toString();
}
}
Future<List<dynamic>> fetchData() async {
final uri = Uri.parse('some url');
var data = await getData(uri);
return = jsonDecode(data) as List;
}
}
class Repository {
final NetworkService networkService = NetworkService();
Future<List<SomeObject>> fetchDataList() async {
final dataRaw =
await networkService.fetchDataList();
return dataRaw.map((e) => SomeObject.fromJson(e)).toList();
}
}
class SomeCubit extends Cubit<CubitState> {
final Repository repository;
SomeCubit(this.repository) : super(LoadingState()) {
fetchDataList();
}
void fetchDataList() {
try {
repository
.fetchDataList()
.then((dataList) => emit(LoadedState(dataList)));
} catch (e) {
// What I shloud return here?
emit(ErrorState(e.toString()));
}
}
}
How to make this code "bullet proof" because I don't know how to "pass" error from NetworkService to Cubit? It works fine till I have dynamic responses in functions but in Repository class I want to return List of specific objects and when function fail I will return null. If I write try/catch I have to provide return statement in catch block - and I can't return List. I want to return some kind of Error...
I suggest that you use the excellent class named Either from the dartz package. It will allow you to return X if things went bad, and return Y if all is well, as such: Future<Either<X, Y>>
Then you can check on your variable (e.g. result) as follows: result.isLeft() for error, or do result.fold( ... ) to easily handle the return type (error or success).
In your particular case you could do as follows when returning from the repository to the cubit:
Future<Either<RepositoryError, List<SomeObject>>> fetchDataList() async { ... }
Where RepositoryError could be a class containing information about the type of error.
So in the cubit you do:
final result = await repository.fetchDataList();
emit(
result.fold(
(error) => ErrorState(error),
(dataList) => LoadedState(dataList)
)
);
Then you continue with this pattern all the way to NetworkService getData(). Either with the same common "error class" in the Repository and the NetworkService, or separate ones in the different layers and you "translate" between different "error classes". Perhaps it makes sense to have a NetworkServiceError that is returned there..
In your NetworkService you could do as follows:
Future<Either<NetworkServiceError, String>> getData(Uri uri) async { ... }
Future<Either<NetworkServiceError, List<dynamic>>> fetchData() async { ... }
This will give you great flexibility and passing of information from the service, to the repository and to the cubit.
You can let exceptions propagate through Futures from NetworkService up to the cubit, by removing the try/catch from getData.
I upgraded Flutter from version 2.0.2 to version 2.2.2 and now the custom exceptions that are thrown from a Future function are not being catch.
For example, I got this Future function, where I call another Future that does a server request and returns back the response or throws a custom exception (ApiException) in case of error:
static Future<bool> signUpCustomerRequest(Map<String, dynamic> params) async {
try {
// Here we call this Future function that will do a request to server API.
dynamic _response = await _provider.signUpCustomer(params);
if (_response != null) {
updateUserData(_response);
return true;
}
return false;
} on ApiException catch(ae) {
// This custom exception is not being catch
ae.printDetails();
rethrow;
} catch(e) {
// This catch is working and the print below shows that e is Instance of 'ApiException'
print("ERROR signUpCustomerRequest: $e");
rethrow;
} finally {
}
}
And this is the Future function that does the request to server and throws the ApiException:
Future<User?> signUpCustomer(Map<String, dynamic> params) async {
// POST request to server
var _response = await _requestPOST(
needsAuth: false,
path: routes["signup_client"],
formData: params,
);
// Here we check the response...
var _rc = _response["rc"];
switch(_rc) {
case 0:
if (_response["data"] != null) {
User user = User.fromJson(_response["data"]["user"]);
return user;
}
return null;
default:
print("here default: $_rc");
// And here we have the throw of the custom exception (ApiException)
throw ApiException(getRCMessage(_rc), _rc);
}
}
Before upgrading to Flutter 2.2.2 the catch of custom exceptions worked perfectly. Did something change on this Flutter version? Am I doing something wrong?
Thanks!
I was able to reproduce your bug with the following code:
class ApiException implements Exception {
void printDetails() {
print("ApiException was caught");
}
}
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 1));
throw ApiException();
}
void main() async {
try {
await doSomething();
} on ApiException catch (ae) {
ae.printDetails();
} catch (e) {
print("Uncaught error: $e"); // This line is printed
}
}
There's an open issue on the dart sdk, which I think might be related, though I'm not sure: https://github.com/dart-lang/sdk/issues/45952.
In any case, I was able to correct the error by returning a Future.error, instead of throwing the error directly:
class ApiException implements Exception {
void printDetails() {
print("ApiException was caught"); // This line is printed
}
}
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 1));
return Future.error(ApiException());
}
void main() async {
try {
await doSomething();
} on ApiException catch (ae) {
ae.printDetails();
} catch (e) {
print("Uncaught error: $e");
}
}
In my flutter app, I have a future that handles http requests and returns the decoded data. But I want to be able to send an error if the status code != 200 that can be gotten with the .catchError() handler.
Heres the future:
Future<List> getEvents(String customerID) async {
var response = await http.get(
Uri.encodeFull(...)
);
if (response.statusCode == 200){
return jsonDecode(response.body);
}else{
// I want to return error here
}
}
and when I call this function, I want to be able to get the error like:
getEvents(customerID)
.then(
...
).catchError(
(error) => print(error)
);
Throwing an error/exception:
You can use either return or throw to throw an error or an exception.
Using return:
Future<void> foo() async {
if (someCondition) {
return Future.error('FooError');
}
}
Using throw:
Future<void> bar() async {
if (someCondition) {
throw Exception('BarException');
}
}
Catching the error/exception:
You can use either catchError or try-catch block to catch the error or the exception.
Using catchError:
foo().catchError(print);
Using try-catch:
try {
await bar();
} catch (e) {
print(e);
}
You can use throw :
Future<List> getEvents(String customerID) async {
var response = await http.get(
Uri.encodeFull(...)
);
if (response.statusCode == 200){
return jsonDecode(response.body);
}else{
// I want to return error here
throw("some arbitrary error"); // error thrown
}
}
Another way to solve this is by using the dartz package.
An example of how to use it would look something similar like this
import 'package:dartz/dartz.dart';
abstract class Failure {}
class ServerFailure extends Failure {}
class ResultFailure extends Failure {
final int statusCode;
const ResultFailure({required this.statusCode});
}
FutureOr<Either<Failure, List>> getEvents(String customerID) async {
try {
final response = await http.get(
Uri.encodeFull(...)
);
if (response.statusCode == 200) {
return Right(jsonDecode(response.body));
} else {
return Left(ResultFailure(statusCode: response.statusCode));
}
}
catch (e) {
return Left(ServerFailure());
}
}
main() async {
final result = await getEvents('customerId');
result.fold(
(l) => print('Some failure occurred'),
(r) => print('Success')
);
}
You can return the error data like this if you want to read the error object:
response = await dio.post(endPoint, data: data).catchError((error) {
return error.response;
});
return response;
//POST
Future<String> post_firebase_async({String? path , required Product product}) async {
final Uri _url = path == null ? currentUrl: Uri.https(_baseUrl, '/$path');
print('Sending a POST request at $_url');
final response = await http.post(_url, body: jsonEncode(product.toJson()));
if(response.statusCode == 200){
final result = jsonDecode(response.body) as Map<String,dynamic>;
return result['name'];
}
else{
//throw HttpException(message: 'Failed with ${response.statusCode}');
return Future.error("This is the error", StackTrace.fromString("This is its trace"));
}
}
Here is how to call:
final result = await _firebase.post_firebase_async(product: dummyProduct).
catchError((err){
print('huhu $err');
});
This is my exception class. Exception class has been implemented by the abstract exception class of flutter. Am I missing something?
class FetchDataException implements Exception {
final _message;
FetchDataException([this._message]);
String toString() {
if (_message == null) return "Exception";
return "Exception: $_message";
}
}
void loginUser(String email, String password) {
_data
.userLogin(email, password)
.then((user) => _view.onLoginComplete(user))
.catchError((onError) => {
print('error caught');
_view.onLoginError();
});
}
Future < User > userLogin(email, password) async {
Map body = {
'username': email,
'password': password
};
http.Response response = await http.post(apiUrl, body: body);
final responseBody = json.decode(response.body);
final statusCode = response.statusCode;
if (statusCode != HTTP_200_OK || responseBody == null) {
throw new FetchDataException(
"An error occured : [Status Code : $statusCode]");
}
return new User.fromMap(responseBody);
}
CatchError doesn't catch the error when the status is not 200. In short error caught is not printed.
Try
void loginUser(String email, String password) async {
try {
var user = await _data
.userLogin(email, password);
_view.onLoginComplete(user);
});
} on FetchDataException catch(e) {
print('error caught: $e');
_view.onLoginError();
}
}
catchError is sometimes a bit tricky to get right.
With async/await you can use try/catch like with sync code and it is usually much easier to get right.
Let's say this is your function which throws an exception:
Future<void> foo() async {
throw Exception('FooException');
}
You can either use try-catch block or catchError on the Future since both do the same thing.
Using try-catch
try {
await foo();
} on Exception catch (e) {
print(e); // Only catches an exception of type `Exception`.
} catch (e) {
print(e); // Catches all types of `Exception` and `Error`.
}
Use catchError
await foo().catchError(print);
I was trying to find this answer when got to this page, hope it helps: https://stackoverflow.com/a/57736915/12647239
Basicly i was just trying to catch an error message from a method, but i was calling
throw Exception("message")
And in "catchError" i was getting "Exception: message" instead of "message".
catchError(
(error) => print(error)
);
fixed with the return in the above reference
Future < User > userLogin(email, password) async { try {
Map body = {
'username': email,
'password': password
};
http.Response response = await http.post(apiUrl, body: body);
final responseBody = json.decode(response.body);
final statusCode = response.statusCode;
if (statusCode != HTTP_200_OK || responseBody == null) {
throw new FetchDataException(
"An error occured : [Status Code : $statusCode]");
}
return new User.fromMap(responseBody); }
catch (e){
print(e.toString());
}