lomsa-dev / http-mock-adapter Goto Github PK
View Code? Open in Web Editor NEWA simple to use mocking package for Dio intended to be used in tests.
Home Page: https://pub.dev/packages/http_mock_adapter
License: MIT License
A simple to use mocking package for Dio intended to be used in tests.
Home Page: https://pub.dev/packages/http_mock_adapter
License: MIT License
First thank you for creating this package,and maintaining it.
Because every version of http_mock_adapter depends on mockito ^4.1.3 and tdd_null_safe depends on mockito ^5.0.3, http_mock_adapter is forbidden.
I'm trying to provide a chain of results for a given path, so the first request would fail with a 401, and the second request would succeed. Currently, the Dio interceptor is being called repeatedly with the same response. With the expectations below, 401 is repeatedly received
the test expectations:
_dioAdapter
..onGet(
'/Test',
(request) => request.throws(401, error(401)),
headers: {
'accept-language': 'en',
'accept': 'application/json',
'authorization': 'bearer ${accessToken.token}',
},
)
..onGet(
'/Test',
(request) => request.reply(200, ''),
headers: {
'accept-language': 'en',
'accept': 'application/json',
'authorization': 'bearer ${refreshedAccessToken.token}',
},
);
I would expect subsequent calls to the path to use the responses in the order given:
The first response would be the 401, followed by the 200.
The code being tested is for refreshing auth tokens.
http_mock_adapter: ^0.2.1
dio: ^4.0.0
Git hooks is a mechanism to automate work whenever certain important events occur, such as commits and pull requests.
It is notable that there are numerous Git hooks managers that make working with Git hooks noticeably easier, such as lefthook.
Example configuration for lefthook
(keep in mind that you will have to set up lefthook
locally on your system):
pre-push:
parallel: true
commands:
tests:
run: flutter test
pre-commit:
commands:
pretty:
glob: "*.dart"
run: flutter format {staged_files} && git add {staged_files}
linter:
run: flutter analyze
Using the DioInterceptor, should a request throw a DioError, other interceptors are not informed.
dio_interceptor.dart
contains the method void onRequest(options, handler) async
, and the following is the issue:
if (isError(response)) {
handler.reject(response as DioError);
return;
}
reject
should be passing callFollowingErrorInterceptor = true
void reject(DioError error, [bool callFollowingErrorInterceptor = false])
package:dio/src/interceptor.dart
Complete the request with an error! Other request/response interceptor(s) will not be executed, but error interceptor(s) may be executed, which depends on whether the value of parameter [callFollowingErrorInterceptor] is true.
[error]: Error info to reject. [callFollowingErrorInterceptor]: Whether to call the error interceptor(s).
Stacktrace:
DioError [DioErrorType.response]: {message: Some beautiful error!}
dart:async/future_impl.dart 25:44 _Completer.completeError
package:dio/src/interceptor.dart 120:16 RequestInterceptorHandler.reject
package:http_mock_adapter/src/interceptors/dio_interceptor.dart 67:15 DioInterceptor.onRequest
package:dio/src/dio_mixin.dart 502:28 DioMixin.fetch._requestInterceptorWrapper.<fn>.<fn>.<fn>
package:dio/src/dio_mixin.dart 795:22 DioMixin.checkIfNeedEnqueue
package:dio/src/dio_mixin.dart 500:22 DioMixin.fetch._requestInterceptorWrapper.<fn>.<fn>
dart:async/future.dart 174:37 new Future.<fn>
package:stack_trace/src/stack_zone_specification.dart 208:15 StackZoneSpecification._run
package:stack_trace/src/stack_zone_specification.dart 116:48 StackZoneSpecification._registerCallback.<fn>
dart:async/zone.dart 1346:47 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1162:7 _CustomZone.runGuarded
dart:async/zone.dart 1202:23 _CustomZone.bindCallbackGuarded.<fn>
package:stack_trace/src/stack_zone_specification.dart 208:15 StackZoneSpecification._run
package:stack_trace/src/stack_zone_specification.dart 116:48 StackZoneSpecification._registerCallback.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1186:23 _CustomZone.bindCallback.<fn>
dart:async-patch/timer_patch.dart 18:15 Timer._createTimer.<fn>
dart:isolate-patch/timer_impl.dart 395:19 _Timer._runTimers
dart:isolate-patch/timer_impl.dart 426:5 _Timer._handleMessage
dart:isolate-patch/isolate_patch.dart 184:12 _RawReceivePortImpl._handleMessage
===== asynchronous gap ===========================
dart:async/zone.dart 1279:19 _CustomZone.registerCallback
dart:async/zone.dart 1201:22 _CustomZone.bindCallbackGuarded
dart:async/timer.dart 48:45 new Timer
dart:async/timer.dart 81:9 Timer.run
dart:async/future.dart 172:11 new Future
package:dio/src/dio_mixin.dart 499:13 DioMixin.fetch._requestInterceptorWrapper.<fn>
package:dio/src/dio_mixin.dart 494:14 DioMixin.fetch._requestInterceptorWrapper.<fn>
package:stack_trace/src/stack_zone_specification.dart 126:26 StackZoneSpecification._registerUnaryCallback.<fn>.<fn>
package:stack_trace/src/stack_zone_specification.dart 208:15 StackZoneSpecification._run
package:stack_trace/src/stack_zone_specification.dart 126:14 StackZoneSpecification._registerUnaryCallback.<fn>
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19 _CustomZone.registerUnaryCallback
dart:async/future_impl.dart 293:23 Future.then
package:dio/src/dio_mixin.dart 576:23 DioMixin.fetch.<fn>
dart:collection/list.dart 86:13 ListMixin.forEach
package:dio/src/dio_mixin.dart 575:18 DioMixin.fetch
package:dio/src/dio_mixin.dart 466:12 DioMixin.request
package:dio/src/dio_mixin.dart 53:12 DioMixin.get
test\util\dio_service_test.dart 505:39 main.<fn>.<fn>.<fn>.<fn>
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19 _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 45:22 _asyncThenWrapperHelper
test\util\dio_service_test.dart main.<fn>.<fn>.<fn>.<fn>
package:test_api/src/backend/declarer.dart 200:19 Declarer.test.<fn>.<fn>
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19 _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 45:22 _asyncThenWrapperHelper
package:test_api/src/backend/declarer.dart Declarer.test.<fn>.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1789:10 _runZoned
dart:async/zone.dart 1711:10 runZoned
package:test_api/src/backend/declarer.dart 198:13 Declarer.test.<fn>
package:test_api/src/backend/declarer.dart 181:48 Declarer.test.<fn>
package:test_api/src/backend/invoker.dart 231:15 Invoker.waitForOutstandingCallbacks.<fn>
package:test_api/src/backend/invoker.dart 228:14 Invoker.waitForOutstandingCallbacks.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1789:10 _runZoned
dart:async/zone.dart 1711:10 runZoned
package:test_api/src/backend/invoker.dart 228:5 Invoker.waitForOutstandingCallbacks
package:test_api/src/backend/invoker.dart 383:17 Invoker._onRun.<fn>.<fn>.<fn>
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19 _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 45:22 _asyncThenWrapperHelper
package:test_api/src/backend/invoker.dart Invoker._onRun.<fn>.<fn>.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1789:10 _runZoned
dart:async/zone.dart 1711:10 runZoned
package:test_api/src/backend/invoker.dart 370:9 Invoker._onRun.<fn>.<fn>
package:test_api/src/backend/invoker.dart 415:15 Invoker._guardIfGuarded
package:test_api/src/backend/invoker.dart 369:7 Invoker._onRun.<fn>
package:stack_trace/src/chain.dart 94:24 Chain.capture.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1789:10 _runZoned
dart:async/zone.dart 1711:10 runZoned
package:stack_trace/src/chain.dart 92:12 Chain.capture
package:test_api/src/backend/invoker.dart 368:11 Invoker._onRun
package:test_api/src/backend/live_test_controller.dart 153:11 LiveTestController.run
package:test_api/src/remote_listener.dart 255:16 RemoteListener._runLiveTest.<fn>
dart:async/zone.dart 1354:13 _rootRun
dart:async/zone.dart 1258:19 _CustomZone.run
dart:async/zone.dart 1789:10 _runZoned
dart:async/zone.dart 1711:10 runZoned
package:test_api/src/remote_listener.dart 254:5 RemoteListener._runLiveTest
package:test_api/src/remote_listener.dart 207:7 RemoteListener._serializeTest.<fn>
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_controller.dart 733:19 _SyncStreamControllerDispatch._sendData
dart:async/stream_controller.dart 607:7 _StreamController._add
dart:async/stream_controller.dart 554:5 _StreamController.add
dart:async/zone.dart 1370:13 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_controller.dart 733:19 _SyncStreamControllerDispatch._sendData
dart:async/stream_controller.dart 607:7 _StreamController._add
dart:async/stream_controller.dart 554:5 _StreamController.add
dart:async/stream_controller.dart 826:13 _StreamSinkWrapper.add
package:stream_channel/src/guarantee_channel.dart 125:12 _GuaranteeSink.add
package:stream_channel/src/multi_channel.dart 159:31 new _MultiChannel.<fn>
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:_internal/async_cast.dart 85:11 CastStreamSubscription._onData
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_controller.dart 733:19 _SyncStreamControllerDispatch._sendData
dart:async/stream_controller.dart 607:7 _StreamController._add
dart:async/stream_controller.dart 554:5 _StreamController.add
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_controller.dart 733:19 _SyncStreamControllerDispatch._sendData
dart:async/stream_controller.dart 607:7 _StreamController._add
dart:async/stream_controller.dart 554:5 _StreamController.add
dart:async/stream_controller.dart 826:13 _StreamSinkWrapper.add
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_pipe.dart 123:11 _ForwardingStreamSubscription._add
dart:async/stream_pipe.dart 218:10 _MapStream._handleData
dart:async/stream_pipe.dart 153:13 _ForwardingStreamSubscription._handleData
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_controller.dart 733:19 _SyncStreamControllerDispatch._sendData
dart:async/stream_controller.dart 607:7 _StreamController._add
dart:async/stream_controller.dart 554:5 _StreamController.add
dart:_http/websocket_impl.dart 1145:21 new _WebSocketImpl._fromSocket.<fn>
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_transformers.dart 63:11 _SinkTransformerStreamSubscription._add
dart:async/stream_transformers.dart 13:11 _EventSinkWrapper.add
dart:_http/websocket_impl.dart 338:23 _WebSocketProtocolTransformer._messageFrameEnd
dart:_http/websocket_impl.dart 232:46 _WebSocketProtocolTransformer.add
dart:async/stream_transformers.dart 111:24 _SinkTransformerStreamSubscription._handleData
dart:async/zone.dart 1362:47 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_controller.dart 733:19 _SyncStreamControllerDispatch._sendData
dart:async/stream_controller.dart 607:7 _StreamController._add
dart:async/stream_controller.dart 554:5 _StreamController.add
dart:io-patch/socket_patch.dart 2160:41 _Socket._onData
dart:async/zone.dart 1370:13 _rootRunUnary
dart:async/zone.dart 1265:19 _CustomZone.runUnary
dart:async/zone.dart 1170:7 _CustomZone.runUnaryGuarded
dart:async/stream_impl.dart 341:11 _BufferingStreamSubscription._sendData
dart:async/stream_impl.dart 271:7 _BufferingStreamSubscription._add
dart:async/stream_controller.dart 733:19 _SyncStreamControllerDispatch._sendData
dart:async/stream_controller.dart 607:7 _StreamController._add
dart:async/stream_controller.dart 554:5 _StreamController.add
dart:io-patch/socket_patch.dart 1696:33 new _RawSocket.<fn>
dart:io-patch/socket_patch.dart 1208:14 _NativeSocket.issueReadEvent.issue
dart:async/schedule_microtask.dart 40:21 _microtaskLoop
dart:async/schedule_microtask.dart 49:5 _startMicrotaskLoop
package:dio/src/dio_mixin.dart 618:7 DioMixin.fetch.<fn>
===== asynchronous gap ===========================
dart:async Future.catchError
package:dio/src/dio_mixin.dart 609:8 DioMixin.fetch
package:dio/src/dio_mixin.dart 466:12 DioMixin.request
package:dio/src/dio_mixin.dart 53:12 DioMixin.get
test\util\dio_service_test.dart 505:39 main.<fn>.<fn>.<fn>.<fn>
===== asynchronous gap ===========================
dart:async _asyncThenWrapperHelper
test\util\dio_service_test.dart main.<fn>.<fn>.<fn>.<fn>
Testing code:
//ARRANGE
final _dioAdapter = DioAdapter();
final _dio = Dio()..httpClientAdapter = _dioAdapter;
final _interceptor = DioInterceptor();
_dio.interceptors.add(_interceptor);
final firstRequestHeaders = {
'accept-language': 'en',
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'bearer ${accessToken.token}',
};
final error = getError(401, '/Test', firstRequestHeaders);
_interceptor
..onGet(
'/Test',
(request) => request.throws(
401,
error,
),
headers: firstRequestHeaders,
)
..onGet(
'/Test',
(request) => request.reply(200, ''),
headers: {
'accept-language': 'en',
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'bearer ${refreshedAccessToken.token}',
},
)
..onError(error, ErrorInterceptorHandler());
//ACT
final response = await _dio.get('/Test');
Code being tested:
dio.interceptors.add(
InterceptorsWrapper(onRequest: (requestOptions, handler) async {
if (tenantId != null && accessToken != null) {
requestOptions.headers['authorization'] = 'bearer ${accessToken!.token}';
}
handler.next(requestOptions);
}, onError: (err, handler) async {
if (tenantId != null && accessToken != null && err.response?.statusCode == HttpStatus.unauthorized) {
dio.interceptors.requestLock.lock();
dio.interceptors.responseLock.lock();
final options = err.requestOptions;
final refreshDio = getInstanceNoAuth();
final newAccessToken = await _tokenRefresher.refresh(
refreshDio: refreshDio,
tenantId: tenantId,
accessToken: accessToken!,
);
dio.interceptors.requestLock.unlock();
dio.interceptors.responseLock.unlock();
if (newAccessToken != null) {
await _tenantLocalRepo.setAccessToken(newAccessToken);
accessToken = newAccessToken;
handler.resolve(await dio.fetch(options));
} else {
handler.next(err);
}
} else {
handler.next(err);
}
}),
);
I expect any error interceptor to be called if there's an error thrown in the onGet request
http_mock_adapter: ^0.2.1
dio: ^4.0.0
I've been unable to mock a Post that sends FormData. Try as I might, I can't setup the route correctly:
Assertion failed: "Could not find mocked route matching request for https://example.com/POST/Instance of 'FormData'/{}/{content-type: multipart/form-data; boundary=--dio-boundary-4160721626, content-length: 173}"
Simplified, the method I'm trying to test looks like something this:
Future<void> postSomething(
{required String url, required FormData formData}) async {
try {
final response = await _httpClient.post(url, data: formData);
if (response.statusCode != 200) {
print('Request failed ${response.statusCode}');
}
} on DioError catch (e) {
print(e.message);
print(e.type);
if (e.type == DioErrorType.connectTimeout ||
e.type == DioErrorType.receiveTimeout) {
print('Doing something cool with a timeout error');
throw Exception('TIMEOUT');
} else {
print('Doing something cool with unexepcted error');
}
}
}
The current iteration of my test looks like:
test('postSomething', () {
final errorToThrow = DioError(
requestOptions: RequestOptions(path: path),
type: DioErrorType.receiveTimeout,
response: Response(
statusCode: 408,
requestOptions: RequestOptions(path: path),
),
error: {'message': 'Timeout'});
print('Form Length: ${formData.length}');
print('Form Boundary: ${formData.boundary}');
dioAdapter.onPost(path, (request) => request.throws(408, errorToThrow),
data: formData);
final postService = PostService(httpClient: dio);
expect(
() async =>
await postService.postSomething(url: path, formData: formData),
throwsA(
predicate(
(e) => e is Exception && e.toString().startsWith('TIMEOUT'),
),
),
);
});
Working example here: https://github.com/ComplexCarbos/dio_mock_example
Can Mock a Post request that takes FormData as a parameter.
EDIT: Edited to be set aside due to having little involvement with the non-sequential workflow and the method chaining itself.
One of the implementers of the core package functionality is ought to be HttpInterceptor (Dio's interceptor).
See the usage scenario for the guidelines on how to use Dio's interceptors.
Implementation ideas are welcome!
For my current use case I need to mock a response for an application/xml
content response. But since the mocked response body is always passed to jsonEncode()
in RequesHandler
the body will be surrounded with "
s, while existing ones will be escaped to \"
.
String
data.A simple test to reproduce:
void main() {
final dioAdapter = DioAdapter();
final dio = Dio()..httpClientAdapter = dioAdapter;
test('should not encode body with jsonEncode', () async {
const expected = '<xml xmlns="http://www.w3.org/2005/Atom">Test</xml>';
dioAdapter.onGet(
'/',
(request) => request.reply(
200,
expected,
headers: {
Headers.contentTypeHeader: ['application/xml'],
},
),
);
final response = await dio.get<String>(
'/',
options: Options(
responseType: ResponseType.plain,
),
);
expect(response.data, expected);
});
}
Response body is not encoded, remains as plain string.
Screenshot of the above test result.
OS: Windows 10
Flutter 2.2.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision b22742018b (12 days ago) • 2021-05-14 19:12:57 -0700
Engine • revision a9d88a4d18
Tools • Dart 2.13.0
The fix should probably be to conditionally apply jsonEncode()
depending on the header type given. Current implementation has a default header that contains Headers.jsonContentType
.
Maybe something similar to this in request_handler.dart
?
final isJson =
headers[Headers.contentTypeHeader]?.contains(Headers.jsonContentType) ??
false;
requestMap[this.statusCode] = () => AdapterResponse.from(
isJson ? jsonEncode(data) : data,
this.statusCode,
headers: headers,
statusMessage: statusMessage,
isRedirect: isRedirect,
);
This will make the above test pass.
I also noticed the request_handler.dart
doesn't have any tests. so if something like my proposal would be okay I could also open a PR for it. Please let me know what you think/any changes could be applied.
Bad state: Stream has already been listened to.
How can I requset dio more than once?
when I add queryParameters in Dio, in that case URL is added unexpected "GET/null/{test: true}/{}"
DioError [DioErrorType.DEFAULT]: Assertion failed: "Could not find mocked route matching request for https://example.com/GET/null/{test: true}/{}"
below one is my test code.
test("query parameters API test", () async {
final dio = Dio();
final dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
const path = 'https://example.com';
dioAdapter
.onGet(
path,
(request) => request.reply(200,
{'message': 'Successfully mocked GET!'}));
// Making dio.get request on the path an expecting mocked response
final getResponse = await dio.get(path, queryParameters: {"test": true});
expect({'message': 'Successfully mocked GET!'},
getResponse.data);
});
expected URL is just below
https://example.com/
I am trying to mocking Dio . So this is my test to test exception according your example:
void main() {
group("Splash init", () {
DioAdapterMockito dioAdapterMockito;
Dio dio;
const path = 'https://example/mobileFlutter/1.0/';
setUpAll(() {
registerServices();
dioAdapterMockito = DioAdapterMockito();
dio = Dio()..httpClientAdapter = dioAdapterMockito;
});
tearDownAll(() {
print("Called teardown");
});
test(
"call getMainConfigs -> getMainConfig web service called 404 exception",
() async {
final dioError = DioError(
error: {'message': 'Some beautiful error!'},
request: RequestOptions(path: path),
response: Response(
statusCode: 500,
request: RequestOptions(path: path),
),
type: DioErrorType.RESPONSE,
);
dioAdapterMockito.onGet(
path,
(request) => request.throws(500, dioError),
);
expect(() async => await dio.get(path), throwsA(isA<AdapterError>()));
expect(() async => await dio.get(path), throwsA(isA<DioError>()));
expect(
() async => await dio.get(path),
throwsA(
predicate(
(DioError error) =>
error is DioError &&
error is AdapterError &&
error.message == dioError.error.toString(),
),
),
);
});
});
But I got this error:
Expected: throws satisfies function
Actual: <Closure: () => Future<Response<dynamic>>>
Which: threw DioError:<DioError [DioErrorType.DEFAULT]: NoSuchMethodError: The getter 'headers' was called on null.
Receiver: null
Tried calling: headers
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 DioMixin._dispatchRequest
package:dio/src/dio.dart:927
<asynchronous suspension>
#2 StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
package:stack_trace/src/stack_zone_specification.dart:1
<asynchronous suspension>
>
stack package:dio/src/dio.dart 966:7 DioMixin._dispatchRequest
package:dio/src/dio.dart:966....
I am using dio: ^3.0.9
and this is doctor:
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.0.5, on Linux, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Chrome - develop for the web
[!] Android Studio (not installed)
[✓] VS Code (version 1.55.2)
[✓] Connected device (1 available)
! Doctor found issues in 1 category.
One of the implementers of the core package functionality is ought to be DioAdapter.
It will extend HttpClientAdapter
from dio package and be exported in the http_mock_adapter.dart file.
One of the implementers of the core package functionality is ought to be DioAdapterMockito.
Implementation ideas are welcome!
GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.
It would be beneficial to automate the testing workflow while making sure that no broken code gets merged into releases, alongside other benefits of Continuous Integration.
A Code of Conduct is a set of rules outlining the norms, rules, and responsibilities or proper practices of an individual party or an organization.
I will write test code like this:
dioAdapter.onPost("some_route", (server) => server.reply(...), data: Matchers.any);
I am very much bugged that I do not know the type of 'server'. Moreover, I don't really know what to pass to server.reply()!
What I've tried:
I've looked through the relevant docs, but I see no page on MockServerCallback. I've looked into the code and tried declaring the type of the callback that I pass into onPost(), but my linter won't allow me.
I am trying to make unit test with mockitto to test dio.
void main() {
group("Splash init", () {
DioAdapterMockito dioAdapterMockito;
Dio dio;
const path = 'https://..../mobile/';
setUpAll(() {
dioAdapterMockito = DioAdapterMockito();
dio = Dio()..httpClientAdapter = dioAdapterMockito;
});
test(
"call getMainConfigs -> getMainConfig web service called 404 exception",
() async {
final dioError = DioError(
error: {'message': 'Some beautiful error!'},
request: RequestOptions(path: path),
response: Response(
statusCode: 500,
request: RequestOptions(path: path),
),
type: DioErrorType.RESPONSE,
);
dioInterceptor.onPost(path, (request) => request.throws(500, dioError),
headers: {'Content-Type': 'application/json; charset=utf-8'});
expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));
// expect(() async => await dio.get(path), throwsA(isA<DioError>()));
// expect(
// () async => await dio.get(path),
// throwsA(
// predicate(
// (DioError error) =>
// error is DioError &&
// error is AdapterError &&
// error.message == dioError.error.toString(),
// ),
// ),
// );
});
});
}
but after running this test I got this error:
Expected: throws <Instance of 'AdapterError'>
Actual: <Closure: () => Future<Response<dynamic>>>
Which: threw DioError:<DioError [DioErrorType.DEFAULT]: Assertion failed: "Could not find mocked route matching request for https://..../mobile//POST/null/{}/{content-type: application/json; charset=utf-8}"
#0 History.responseBody.<anonymous closure>
package:http_mock_adapter/src/history.dart:32
#1 DioInterceptor.onRequest
package:http_mock_adapter/…/interceptors/dio_interceptor.dart:63
#2 DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>.<anonymous closure>
package:dio/src/dio.dart:849
#3 DioMixin.checkIfNeedEnqueue
package:dio/src/dio.dart:1121
#4 DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>
package:dio/src/dio.dart:846
#5 new Future.<anonymous closure> (dart:async/future.dart:174:37)
#6 StackZoneSpecification._run
package:stack_trace/src/stack_zone_specification.dart:208
#7 StackZoneSpecification._registerCallback.<anonymous closure>
package:stack_trace/src/stack_zone_specification.dart:116
I am trying to test exception.
When doing widget testing, if we are testing Screen 1 and need to mock a response that happens in Screen 2, mock won't be available when Screen 2 is pushed and will get Pending Timer error.
After debugging, I see that the client is trying to make the actual API call because there are no mocked responses.
In the same test, if I mock an API call that happens in that screen, mocking works fine.
Response is mocked properly.
Flutter 2.10.4
We are using AutoRouter for navigation, but I don't think this affects anything.
Is your feature request related to a problem? Please describe.
How to throw exceptions from Dio to test error handling?
Describe the solution you'd like
I think pretty useful for doing something like
dioAdapter.onGet('/test').throw(DioError(type: DioErrorType.RESPONSE, response: Response(statusCode: 500)));
thanks for the package
The matched headers are iterated over the actual
headers instead of the expected
headers here: https://github.com/lomsa-dev/http-mock-adapter/blob/main/lib/src/extensions/matches_request.dart#L75
This means, that expectations are just ignored if they are not actually present. This seems wrong. Same goes for query parameters and others things that are matched via Map
.
There doesn't seem to be an option to match against query parameters while using request handlers.
I tried adding the param to the URL string,
dioAdapter.onGet('/search?param=$query')
However, this doesn't seem to match requests correctly.
Using the onRoute
API does work for this use case but it's a lot more verbose.
dioAdapter
.onRoute(
'/search',
request: Request(
route: '/search',
method: RequestMethods.GET,
headers: {'authorization': 'Bearer $token'},
queryParameters: {
'param': 'query',
},
),
);
It would be nice to expose an API for this on the factory methods.
Hi, could you please update your dependencies to the newest ones, because the projects migrated to flutter 2 are quite affected?
The score of the package is not maximum due to an issue regarding multi-platform support.
The issue is described as:
package:http_mock_adapter/http_mock_adapter.dart
that imports:package:http_mock_adapter/src/adapters/dio_adapter.dart
that imports:package:dio/adapter.dart
that imports:package:dio/src/adapters/io_adapter.dart
that imports:dart:io
The traced information depicts that Dio uses dart:io
package in its source code, which is not compatible with JavaScript runtime (Meaning that Dio
is not completely compatible for web usage).
The problem may be solved by conditional importing, universal_io or other method.
The issue in the context of http-mock-adapter
will be pertinent until Dio
fixes the issue.
Tip: Use pana to calculate package's score locally.
final dio = Dio(BaseOptions());
final dioAdapter = DioAdapter(dio: dio);
Here the adapter is forced to be responsible for setting itself as the httpclientadapter. This creates a few issues while using DI libraries one of which is Dio needs to be instantiated before the adapter despite the adapter not depending of the Dio instance.
it would be great if instead the task of setting the adapter is externalized i.e.
final dio = Dio(BaseOptions());
final dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
path
and not the entire request including headers etc.matching
We can have an external interface like this
abstract class HttpRequestMatcher{
bool matches(RequestOptions ongoingRequest, Request matcher);
}
and supplemented by passing an instance to the adapter like this
class DioAdapter extends HttpClientAdapter with Recording, RequestHandling {
@override
final HttpRequestMatcher matcher;
DioAdapter({
required this.matcher,
});
This would allow us to have a few matchers like this prebaked into the library and let the users create their own custom matchers as well.
class UrlPathMatcher implements HttpRequestMatcher{
@override
bool matches(RequestOptions ongoingRequest, Request matcher) {
return doesRouteMatch(ongoingRequest.path, matcher.route);
}
}
Happy to hear your thoughts on this.
Exception is not working properly.
Sometimes when i PUT PATCH or POST data I need to receive different responses over time, so I guess it will be great to make response builder function instead of direct static response in reply method. This may be trigged on iteration, as instance: I send first request and receive RESP#1 and on the next requests i will Receive RESP#2 only. (Otherwise if i will try to place my own returnable method with proposed logic will it work though?...)
The second feature is to include the path patterns for the route, that will allow to put modifiable data e.g. uuids etc. in the path according to REST API patterns. The further enhancement might also include passing those modifiable path parameters to response builder I proposed above so developers could build their own custom response every time.
Hope my thoughts were useful enough, thank you.
When changing POST request content in a test I always have to update the content-length header to match the new length.
To make this easier, I would like to ignore some headers when requests are matched.
This could be solved with a complex request matching implementation (like #68) which I would really love to see.
Hi! Those tests should show what's happening. The last test fails and it (IMO) shouldn't:
http_mock_adapter
- 0.2.1
dio
- 4.0.0
Flutter 2.0.5 • channel unknown • unknown source
Framework • revision adc687823a (11 days ago) • 2021-04-16 09:40:20 -0700
Engine • revision b09f014e96
Tools • Dart 2.12.3
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
void main() {
late Dio dio;
late DioAdapter adapter;
setUp(() {
dio = Dio();
adapter = DioAdapter();
dio.httpClientAdapter = adapter;
adapter.onGet('/test', (request) {
request.reply(200, 'ok');
});
});
test('correctly throws when does not match first request', () async {
expectLater(
dio.get<void>('/wont-find'),
throwsA(isA<DioError>()),
);
});
test('correctly not throws when matches first request', () async {
expectLater(
dio.get<void>('/test'),
completes,
);
});
test(
'INCORRECTLY not throws when matches '
'first request but not the next ones', () async {
expectLater(
dio.get<void>('/test'),
completes,
);
expectLater(
dio.get<void>('/wont-find'),
throwsA(isA<DioError>()),
);
});
}
Create ability to read mock response from file.
Current interface vision is following:
dio..onGet(
"path",
(request) => request.reply(200,
MockFile("./some.json"),
);```
There are 38~ warnings and errors in total found by linter thus far.
They should be fixed and pushed as soon as possible.
I have a test written like this:
void main() {
test("Function should return a Genre", () async {
final Dio dio = Dio();
final DioAdapter dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
dioAdapter.onGet('This is a test endpoint',
(request) => request.reply(200, Genre('Romance', '/romance', 100, 50)));
ApiClientProvider apiClientProvider = ApiClientProvider(dio);
expect(
await apiClientProvider.getGenreInfo(
endpoint: 'This is a test endpoint'),
isInstanceOf<Genre>());
});
}
This is the getGenreInfo()
method from ApiClientProvider
class:
Future<Genre> getGenreInfo({required String endpoint}) async {
Response response = await _dio.get(endpoint);
return response.data;
}
I tried to mock the get()
function to return the Genre('Romance', '/romance', 100, 50)
but I end up getting DioError [DioErrorType.other]: Converting object to an encodable object failed: Instance of 'Genre'
I am using retrofit
package. When I run tests, the following error keeps showing:
Could not find mocked route matching request for /api/users/GET/{}/{page: 1}/{}
When I debug, I see the client sends perfectly normal requests, however, for some reason the adapter cannot resolve it properly.
retrofit: ^2.0.0-beta1
DioAdapter()
as the httpAdapter
of the Dio client.The code is null-safe.
Pull Request templates are used as structured guidelines for making Pull Requests.
getMockFileName() located in utils.dart file is currently bugged.
Its functionality lies in modifying uriPath
strings from slashed versions to dashed versions, such as: /accounts
=> -accounts
This is due to the reason that filePath
string is formatted like so: ${options.method}$mockFileName.json
It fails when we pass in something such as https://example.com
due to its implementation details.
It would be very beneficial to test numerous different uriPath
cases on this function to assess its effectiveness.
Github action should publish package after adding release tag to the main
branch
develop
branchmain
<- develop
Github action (publish package to pub.dev) should start.
Nothing happens
Github.com
analysis_options is Dart's official static analysis tool.
There are numerous well-defined and pre-configured analysis options available, such as pedantic, effective_dart, and maybe on the stricter side, - http.
It is critical for a package that is intended to be used as a library to be less prone to fail under both defined and undefined circumstances, therefore I advise to implement numerous strict static analysis rules.
Dart has recently obtained the ability to enable sound null safety in Dart code. Here is the overview of the feature. It is currently in beta.
From the migration guide we see the following notice:
Migrating an app is technically the same as migrating a package. Before migrating an app, consider waiting until null safety is in a stable release and all your dependencies are ready.
This means that it's generally recommended to migrate if developing a Dart package. Thus, it would seem beneficial to implement null safety features in http_mock_adapter
.
changes need for increasing pub points.
The documentation for the project could use major improvement.
EditorConfig is a working environment agnostic intended to maintain consistent coding style while working on the project.
It would be useful to implement Dart/Flutter friendly EditorConfig implementation, such as:
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
Cited from keep a changelog:
What is a changelog?
A changelog is a file which contains a curated, chronologically ordered list of notable changes for each version of a project.
Why keep a changelog?
To make it easier for users and contributors to see precisely what notable changes have been made between each release (or version) of the project.
Who needs a changelog?
People do. Whether consumers or developers, the end users of software are human beings who care about what's in the software. When the software changes, people want to know why and how.
The content could be similar to this (inside CHANGELOG.md
located in the project directory):
# Changelog
All notable changes to this project will be documented in this file.
Minor versions will be bundled with more important versions.
The format is influenced by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
Subsequently, the date entry follows **YYYY-MM-DD** format in accordance with the [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) standard.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [Unreleased]
- **Added**
- ...
- ...
- ...
---
## [v0.0.1] (2020-11-09)
- **Added**
- The MIT License
[Unreleased]: https://github.com/lomsa-dev/http-mock-adapter/compare/447829b2969300e0ff7e9d6a7c6697cd5744b632...HEAD
[v0.0.1]: https://github.com/lomsa-dev/http-mock-adapter/commit/447829b2969300e0ff7e9d6a7c6697cd5744b632
Guidelines on how to contribute to http-mock-adapter.
Provide a way to clear previous call from adapter history to be called on every tearDown during tests.
Supplying headers to dioAdapter.onGet
works as expected. But does not work for POST, PUT and DELETE (what I tested).
Matching headers/options along with a matching path result in an Assertion error: "Could not find mocked route matching request for..."
Code to reproduce:
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
void main() async {
late Dio dio;
late DioAdapter dioAdapter;
const path = 'https://example.com';
final headers = <String, String>{'Authorization': 'Bearer authToken'};
setUp(() {
dio = Dio();
dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
dioAdapter
..onGet(
path,
(request) =>
request.reply(200, {'message': 'Successfully mocked GET!'}),
headers: headers,
)
..onPost(
path,
(request) =>
request.reply(200, {'message': 'Successfully mocked POST!'}),
headers: headers, // NOT WORKING
)
..onDelete(
path,
(request) =>
request.reply(200, {'message': 'Successfully mocked DELETE!'}),
headers: headers, // NOT WORKING
)
..onPut(
path,
(request) =>
request.reply(200, {'message': 'Successfully mocked PUT!'}),
headers: headers, // NOT WORKING
);
});
// WORKING
test('on get with headers', () async {
final onGetResponse = await dio.get<dynamic>(
path,
options: Options(
headers: headers,
),
);
print(onGetResponse.data);
});
// NOT WORKING
test('on post with headers', () async {
final onPostResponse = await dio.post<dynamic>(
path,
options: Options(
headers: headers,
),
);
print(onPostResponse.data);
});
// NOT WORKING
test('on delete with headers', () async {
final onPostResponse = await dio.post<dynamic>(
path,
options: Options(
headers: headers,
),
);
print(onPostResponse.data);
});
// NOT WORKING
test('on put with headers', () async {
final onPostResponse = await dio.put<dynamic>(
path,
options: Options(
headers: headers,
),
);
print(onPostResponse.data);
});
}
Example error for onPost test:
DioError [DioErrorType.other]: Assertion failed: "Could not find mocked route matching request for https://example.com/POST/null/{}/{Authorization: Bearer authToken, content-type: application/json; charset=utf-8}"
#0 History.responseBody.<anonymous closure>
package:http_mock_adapter/src/history.dart:32
#1 DioAdapter.fetch
package:http_mock_adapter/…/adapters/dio_adapter.dart:47
#2 DioMixin._dispatchRequest
package:dio/src/dio_mixin.dart:632
<asynchronous suspension>
#3 StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
package:stack_trace/src/stack_zone_specification.dart:1
<asynchronous suspension>
2
DioMixin.fetch.<fn>
package:dio/src/dio_mixin.dart:618
===== asynchronous gap ===========================
dart:async Future.catchError
DioMixin.fetch
package:dio/src/dio_mixin.dart:609
DioMixin.request
package:dio/src/dio_mixin.dart:466
DioMixin.post
package:dio/src/dio_mixin.dart:89
main.<fn>
test/…/api/dio_test.dart:56
main.<fn>
test/…/api/dio_test.dart:55
2
✖ on post with headers
Versions:
http_mock_adapter : 0.2.1
did: 4.0.0
Thanks for the package and help!
Create ability for delayed response:
Current vision for interface:
dio..onGet("path",
(request){},
delay: Duration(seconds:1),
)
In DioAdapter
or DiopInterceptor
delay param will be checked for null
if it is null adapter will respond normally, if not it will respond with specified delay
I am using Dio with retrofit plugin . I have called my web services like this:
var configs = await restClient.getMainConfig({
"versionName": "1.0",
"currentMenuVersion": "2.0",
"test": "test"
});
And This is restClient.getMainConfig content:
@POST("/loadConfig")
Future<MainModel> getMainConfig(@Body() request);
How can I test restClient.getMainConfig
?
Right now it is necessary to expose them (make them part of the public API) in order to make onRoute()
calls by providing an optional request instance.
Problem begins from the, RequestHandler's reply function:
RequestHandler
's reply method was returning only DioAdapter
which caused chaining problems for DioInterceptor
, because DioInterceptor
itself needs DioInterceptor
to be returned from reply
to chain methods and track the History
.
So we needed to distinguish returns
inside the reply
function, this problem was going to be solved with creating generic type parameter for RequestHandler
class, but while doing this another problem occurred: reply function was not able to understand what type it was returning(DioAdapter
or DioInterceptor
).
For the above mentioned problem two ways of the solution were suggested to solve it.
First, define an abstract interface AdapterInterface
for both, DioInterceptor and DioAdapter, also we need to define all the methods(onGet
, onPost
......) inside the interface and those methods will be overriden
from mixin RequestRouted
.
In this way reply function return type is AdapterInterface
and also reply function maintains it's code completion ability when you are creating chains, But there is big problem of code management because interface defines all of the methods from ReqeustRouted
with their parameters , if we have to change parameters' types or quantities of all of the methods(onGet
,onPost
......) inside the RequestRouted
mixin we will have to change everything inside interface too.
Second, reply functions return type will be T and it will return DioAdapter or DioInterceptor with following manner: return DioAdapter() as T
or return DioInterceptor() as T
, In this case there is no need for separate interface but this type of solution destroyed auto completion ability for reply()
method while creating chains.
For now, wile prioritizing auto completion, First method was left for use, But we are looking for other ways of solution and open to suggestions.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.