From a03d15bbb7e473a4926aabf0bab67726bd2d9303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=BCchler?= <42775578+sebastianbuechler@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:38:50 +0200 Subject: [PATCH] feature/add-callback-and-async-callback (#157) * feature/add-callback-and-async-callback * Fix unit tests and add new cases --- lib/src/adapters/dio_adapter.dart | 2 +- lib/src/handlers/request_handler.dart | 91 +++++++- lib/src/interceptors/dio_interceptor.dart | 2 +- lib/src/types.dart | 6 +- test/handlers/request_handler_test.dart | 249 +++++++++++++--------- 5 files changed, 244 insertions(+), 106 deletions(-) diff --git a/lib/src/adapters/dio_adapter.dart b/lib/src/adapters/dio_adapter.dart index ddc7785..0f09e95 100644 --- a/lib/src/adapters/dio_adapter.dart +++ b/lib/src/adapters/dio_adapter.dart @@ -40,7 +40,7 @@ class DioAdapter with Recording, RequestHandling implements HttpClientAdapter { } await setDefaultRequestHeaders(dio, requestOptions); - final response = mockResponse(requestOptions); + final response = await mockResponse(requestOptions); // Waits for defined duration. if (response.delay != null) await Future.delayed(response.delay!); diff --git a/lib/src/handlers/request_handler.dart b/lib/src/handlers/request_handler.dart index 993f698..63c987b 100644 --- a/lib/src/handlers/request_handler.dart +++ b/lib/src/handlers/request_handler.dart @@ -20,6 +20,28 @@ abstract class MockServer { Duration? delay, }); + void replyCallback( + int statusCode, + MockDataCallback data, { + Map> headers = const { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + String? statusMessage, + bool isRedirect = false, + Duration? delay, + }); + + void replyCallbackAsync( + int statusCode, + MockDataCallbackAsync data, { + Map> headers = const { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + String? statusMessage, + bool isRedirect = false, + Duration? delay, + }); + void throws( int statusCode, DioException dioError, { @@ -31,7 +53,7 @@ abstract class MockServer { /// constructs the configured [MockResponse]. class RequestHandler implements MockServer { /// This is the response. - late MockResponse Function(RequestOptions options) mockResponse; + late Future Function(RequestOptions options) mockResponse; /// Stores [MockResponse] in [mockResponse]. @override @@ -50,7 +72,7 @@ class RequestHandler implements MockServer { ) ?? false; - mockResponse = (requestOptions) { + mockResponse = (requestOptions) async { if (data is Uint8List) { return MockResponseBody.fromBytes( data, @@ -77,9 +99,72 @@ class RequestHandler implements MockServer { }; } + /// Stores [MockResponse] in [mockResponse]. + @override + void replyCallback( + int statusCode, + MockDataCallback data, { + Map> headers = const { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + String? statusMessage, + bool isRedirect = false, + Duration? delay, + }) { + final isJsonContentType = headers[Headers.contentTypeHeader]?.contains( + Headers.jsonContentType, + ) ?? + false; + + mockResponse = (requestOptions) async { + final rawData = data(requestOptions); + + return MockResponseBody.from( + isJsonContentType ? jsonEncode(rawData) : rawData, + statusCode, + headers: headers, + statusMessage: statusMessage, + isRedirect: isRedirect, + delay: delay, + ); + }; + } + + /// Stores [MockResponse] in [mockResponse]. + @override + void replyCallbackAsync( + int statusCode, + MockDataCallbackAsync data, { + Map> headers = const { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + String? statusMessage, + bool isRedirect = false, + Duration? delay, + }) { + final isJsonContentType = headers[Headers.contentTypeHeader]?.contains( + Headers.jsonContentType, + ) ?? + false; + + mockResponse = (requestOptions) async { + final rawData = await data(requestOptions); + + return MockResponseBody.from( + isJsonContentType ? jsonEncode(rawData) : rawData, + statusCode, + headers: headers, + statusMessage: statusMessage, + isRedirect: isRedirect, + delay: delay, + ); + }; + } + /// Stores the [DioException] inside the [mockResponse]. @override void throws(int statusCode, DioException dioError, {Duration? delay}) { - mockResponse = (requestOptions) => MockDioException.from(dioError, delay); + mockResponse = + (requestOptions) async => MockDioException.from(dioError, delay); } } diff --git a/lib/src/interceptors/dio_interceptor.dart b/lib/src/interceptors/dio_interceptor.dart index cd17151..11ad9f0 100644 --- a/lib/src/interceptors/dio_interceptor.dart +++ b/lib/src/interceptors/dio_interceptor.dart @@ -24,7 +24,7 @@ class DioInterceptor extends Interceptor with Recording, RequestHandling { @override void onRequest(requestOptions, requestInterceptorHandler) async { await setDefaultRequestHeaders(dio, requestOptions); - final response = mockResponse(requestOptions); + final response = await mockResponse(requestOptions); // Reject the response if type is MockDioException. if (isMockDioException(response)) { diff --git a/lib/src/types.dart b/lib/src/types.dart index 8939998..ee3d372 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -9,9 +9,13 @@ typedef MockServerCallback = void Function(MockServer server); /// Type for [Recording]'s [ResponseBody], which takes [RequestOptions] as a parameter /// and compares its signature to saved [Request]'s signature and chooses right response. -typedef MockResponseBodyCallback = MockResponse Function( +typedef MockResponseBodyCallback = Future Function( RequestOptions options, ); /// Type for expect data as function typedef MockDataCallback = dynamic Function(RequestOptions options); + +/// Type for async expect data as function +typedef MockDataCallbackAsync = Future Function( + RequestOptions options); diff --git a/test/handlers/request_handler_test.dart b/test/handlers/request_handler_test.dart index 029a205..7d6e477 100644 --- a/test/handlers/request_handler_test.dart +++ b/test/handlers/request_handler_test.dart @@ -14,137 +14,186 @@ void main() { setUp(() { requestHandler = RequestHandler(); }); - test('sets response data for a status with Uint8List', () async { - const statusCode = HttpStatus.ok; - final inputData = Uint8List.fromList([1, 2, 3, 4, 5]); - requestHandler.reply( - statusCode, - inputData, - ); + group('reply', () { + test('sets response data for a status with Uint8List', () async { + const statusCode = HttpStatus.ok; + final inputData = Uint8List.fromList([1, 2, 3, 4, 5]); - final statusHandler = requestHandler.mockResponse; + requestHandler.reply( + statusCode, + inputData, + ); - expect(statusHandler, isNotNull); + final statusHandler = requestHandler.mockResponse; - final mockResponseBody = - statusHandler(RequestOptions(path: '')) as MockResponseBody; - final resolvedData = await BackgroundTransformer().transformResponse( - RequestOptions(path: '', responseType: ResponseType.bytes), - mockResponseBody, - ); + expect(statusHandler, isNotNull); - expect(resolvedData, inputData); - }); + final mockResponseBody = + await statusHandler(RequestOptions(path: '')) as MockResponseBody; + final resolvedData = await BackgroundTransformer().transformResponse( + RequestOptions(path: '', responseType: ResponseType.bytes), + mockResponseBody, + ); + + expect(resolvedData, inputData); + }); + + test('sets response data for a status with JSON by default', () async { + const statusCode = HttpStatus.ok; + const inputData = {'data': 'OK'}; + + requestHandler.reply( + statusCode, + inputData, + ); + + final statusHandler = requestHandler.mockResponse; + + expect(statusHandler, isNotNull); + + final mockResponseBody = + await statusHandler(RequestOptions(path: '')) as MockResponseBody; + + final resolvedData = await BackgroundTransformer().transformResponse( + RequestOptions(path: ''), + mockResponseBody, + ); + + expect(resolvedData, inputData); + expect( + mockResponseBody.headers, + { + Headers.contentTypeHeader: [ + Headers.jsonContentType, + ], + }, + ); + }); + + test('sets response data MockDataCallback ', () async { + const statusCode = HttpStatus.ok; + var data = {'data': 'OK'}; - test('sets response data for a status with JSON by default', () async { - const statusCode = HttpStatus.ok; - const inputData = {'data': 'OK'}; + inputData(RequestOptions options) => data; - requestHandler.reply( - statusCode, - inputData, - ); + requestHandler.reply(statusCode, inputData); - final statusHandler = requestHandler.mockResponse; + final statusHandler = requestHandler.mockResponse; - expect(statusHandler, isNotNull); + expect(statusHandler, isNotNull); - final mockResponseBody = - statusHandler(RequestOptions(path: '')) as MockResponseBody; + final mockResponseBody = + await statusHandler(RequestOptions(path: '')) as MockResponseBody; - final resolvedData = await BackgroundTransformer().transformResponse( - RequestOptions(path: ''), - mockResponseBody, - ); + final resolvedData = await BackgroundTransformer() + .transformResponse(RequestOptions(path: ''), mockResponseBody); - expect(resolvedData, inputData); - expect( - mockResponseBody.headers, - { + expect(resolvedData, data); + }); + + test( + 'sets response data for a status without JSON if content type header is set', + () async { + const statusCode = HttpStatus.created; + const inputData = 'Plain text response'; + const headers = { Headers.contentTypeHeader: [ - Headers.jsonContentType, + Headers.textPlainContentType, ], - }, - ); + }; + + requestHandler.reply( + statusCode, + inputData, + headers: headers, + ); + + final statusHandler = requestHandler.mockResponse; + + expect(statusHandler, isNotNull); + + final mockResponseBody = + await statusHandler(RequestOptions(path: '')) as MockResponseBody; + + final resolvedData = await BackgroundTransformer().transformResponse( + RequestOptions(path: ''), + mockResponseBody, + ); + + expect(resolvedData, inputData); + expect(mockResponseBody.headers, headers); + }); }); - test('sets response data MockDataCallback ', () async { - const statusCode = HttpStatus.ok; - var data = {'data': 'OK'}; + group('replyCallback', () { + test('sets response data MockDataCallback ', () async { + const statusCode = HttpStatus.ok; + var data = {'data': 'OK'}; - inputData(RequestOptions options) => data; + inputData(RequestOptions options) => data; - requestHandler.reply(statusCode, inputData); + requestHandler.replyCallback(statusCode, inputData); - final statusHandler = requestHandler.mockResponse; + final statusHandler = requestHandler.mockResponse; - expect(statusHandler, isNotNull); + expect(statusHandler, isNotNull); - final mockResponseBody = - statusHandler(RequestOptions(path: '')) as MockResponseBody; + final mockResponseBody = + await statusHandler(RequestOptions(path: '')) as MockResponseBody; - final resolvedData = await BackgroundTransformer() - .transformResponse(RequestOptions(path: ''), mockResponseBody); + final resolvedData = await BackgroundTransformer() + .transformResponse(RequestOptions(path: ''), mockResponseBody); - expect(resolvedData, data); + expect(resolvedData, data); + }); }); + group('replyCallbackAsync', () { + test('sets response data MockDataCallbackAsync ', () async { + const statusCode = HttpStatus.ok; + var data = {'data': 'OK'}; - test( - 'sets response data for a status without JSON if content type header is set', - () async { - const statusCode = HttpStatus.created; - const inputData = 'Plain text response'; - const headers = { - Headers.contentTypeHeader: [ - Headers.textPlainContentType, - ], - }; - - requestHandler.reply( - statusCode, - inputData, - headers: headers, - ); - - final statusHandler = requestHandler.mockResponse; - - expect(statusHandler, isNotNull); - - final mockResponseBody = - statusHandler(RequestOptions(path: '')) as MockResponseBody; - - final resolvedData = await BackgroundTransformer().transformResponse( - RequestOptions(path: ''), - mockResponseBody, - ); - - expect(resolvedData, inputData); - expect(mockResponseBody.headers, headers); - }); + inputData(RequestOptions options) => Future.value(data); - test('sets DioException for a status code', () async { - const statusCode = HttpStatus.badRequest; - final dioError = DioException( - requestOptions: RequestOptions( - path: 'path', - ), - type: DioExceptionType.badResponse, - ); + requestHandler.replyCallbackAsync(statusCode, inputData); - requestHandler.throws( - statusCode, - dioError, - ); + final statusHandler = requestHandler.mockResponse; - final statusHandler = requestHandler.mockResponse; + expect(statusHandler, isNotNull); - expect(statusHandler, isNotNull); + final mockResponseBody = + await statusHandler(RequestOptions(path: '')) as MockResponseBody; - final mockDioException = - statusHandler(RequestOptions(path: '')) as MockDioException; + final resolvedData = await BackgroundTransformer() + .transformResponse(RequestOptions(path: ''), mockResponseBody); - expect(mockDioException.type, dioError.type); + expect(resolvedData, data); + }); + }); + group('throws', () { + test('sets DioException for a status code', () async { + const statusCode = HttpStatus.badRequest; + final dioError = DioException( + requestOptions: RequestOptions( + path: 'path', + ), + type: DioExceptionType.badResponse, + ); + + requestHandler.throws( + statusCode, + dioError, + ); + + final statusHandler = requestHandler.mockResponse; + + expect(statusHandler, isNotNull); + + final mockDioException = + await statusHandler(RequestOptions(path: '')) as MockDioException; + + expect(mockDioException.type, dioError.type); + }); }); }); }