Open macik1423 opened 1 year ago
Hi, have you tried sending a http.Client
instance to your interceptor builder? You can use that to mock the internal client and thus have your requests do whatever you want. The library is designed to wrap a Client
and provide proper support of interceptors.
InterceptedClient.build(
interceptors: [AuthorizationInterceptor(storage: storage)],
retryPolicy: ExpiredTokenRetryPolicy(
authRepository: authRepository,
storage: storage,
),
client: MockClient(),
);
Let me know if that helps. Cheers from CR 🇨🇷
Hi, thanks for your reply! Thanks to your hint I have created something like this:
warehouse_api_client.dart
import 'dart:convert';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
import 'package:http_interceptor/http/interceptor_contract.dart';
import 'package:http_interceptor/http_interceptor.dart';
import 'package:testing_app/authentication_repository.dart';
import 'package:testing_app/material.dart';
import 'package:testing_app/storage_service.dart';
import 'package:testing_app/token.dart';
class AuthorizationInterceptor implements InterceptorContract {
final IStorage storage;
AuthorizationInterceptor({required this.storage});
@override
Future<RequestData> interceptRequest({required RequestData data}) async {
try {
final Token? token =
Token.deserialize(await storage.readSecureData('token'));
data.headers.clear();
data.headers['authorization'] = 'Bearer ' + token!.accessToken;
data.headers['content-type'] = 'application/json';
} catch (e) {
print(e);
}
return data;
}
@override
Future<ResponseData> interceptResponse({required ResponseData data}) async {
return data;
}
}
class ExpiredTokenRetryPolicy extends RetryPolicy {
final IStorage storage;
final AuthenticationRepository authRepository;
@override
int maxRetryAttempts = 1;
ExpiredTokenRetryPolicy(
{required this.authRepository, required this.storage});
@override
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
if (response.statusCode == 401) {
await regenerateToken();
return true;
}
return false;
}
Future<void> regenerateToken() async {
final token = Token.deserialize(await storage.readSecureData('token'));
await authRepository.getRefreshToken(token: token);
final Token? newToken =
Token.deserialize(await storage.readSecureData('token'));
if (newToken == null) {
authRepository.sessionExpired();
throw Exception();
}
}
}
class WarehouseApiClient {
final IStorage storage;
final API_URL = dotenv.env['API_URL']!;
final http.Client _client;
WarehouseApiClient(this.storage, this.authRepository, client)
: _client = client ??
InterceptedClient.build(
interceptors: [AuthorizationInterceptor(storage: storage)],
retryPolicy: ExpiredTokenRetryPolicy(
authRepository: authRepository, storage: storage),
);
final AuthenticationRepository authRepository;
Future<List<Material>> getMaterial() async {
final request = Uri.http(API_URL, '/api/material');
final token = await storage.readSecureData('token');
final deserialized = Token.deserialize(token);
final accessToken = deserialized.accessToken;
http.Response response = await _client.get(
request,
headers: {
'Authorization': 'Bearer $accessToken',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
);
// .timeout(Duration(seconds: 15));
switch (response.statusCode) {
case 200:
final materialJson = jsonDecode(response.body) as List;
final list = materialJson
.map((item) => Material.fromJson(item as Map<String, dynamic>))
.toList();
return list;
default:
throw Exception();
}
}
}
my_test.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http_interceptor/http/http.dart';
import 'package:mocktail/mocktail.dart';
import 'package:testing_app/authentication_repository.dart';
import 'package:testing_app/storage_service.dart';
import 'package:testing_app/warehouse_api_client.dart';
class MockHttpClient extends Mock implements http.Client {}
class MockResponse extends Mock implements http.Response {}
class FakeUri extends Fake implements Uri {}
class MockAuthenticationRepository extends Mock
implements AuthenticationRepository {}
class MockStorage extends Mock implements IStorage {}
Future<void> main() async {
await dotenv.load(fileName: '.env.development');
group('WarehouseApiClient', () {
late http.Client httpClient;
late WarehouseApiClient apiClient;
late MockAuthenticationRepository authenticationRepository;
late MockStorage storage;
final apiUrl = dotenv.env['API_URL']!;
setUpAll(() {
registerFallbackValue(FakeUri());
});
setUp(() {
httpClient = MockHttpClient();
authenticationRepository = MockAuthenticationRepository();
storage = MockStorage();
apiClient = WarehouseApiClient(
storage,
authenticationRepository,
InterceptedClient.build(
interceptors: [AuthorizationInterceptor(storage: storage)],
retryPolicy: ExpiredTokenRetryPolicy(
authRepository: authenticationRepository,
storage: storage,
),
client: httpClient,
),
);
when(() => authenticationRepository.status)
.thenAnswer((_) => const Stream.empty());
});
group('getMaterial', () {
test('makes correct http request', () async {
final response = MockResponse();
when(() => response.statusCode).thenReturn(200);
when(() => response.body).thenReturn('{}');
when(() => httpClient.get(any(), headers: any(named: 'headers')))
.thenAnswer((_) async => response);
when(() => storage.readSecureData(any())).thenAnswer(
(_) async => """{
"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoibSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWVpZGVudGlmaWVyIjoiOSIsImVtYWlsIjoibSIsInN1YiI6Im0iLCJqdGkiOiI2YjdlZWRlNy1jMDJjLTRiNzMtYTMxYS01ZTYwNTA1NTkzMWYiLCJleHAiOjE2NjkxMDI2OTEsImlzcyI6Imh0dHA6Ly8xMjcuMC4wLjE6NTAwMSIsImF1ZCI6InVzZXIifQ.q14peCD-pfEhu9zm1JeAVps-WxHhriruGLadu3QNzeY",
"refreshToken":"24e57093-ccac-489c-bc83-27cf4ca36285-29c13587-88e9-4f25-87f9-9d2134d845e2",
"expiresAt":"2022-11-22T07:38:11Z"
}""",
);
try {
await apiClient.getMaterial();
} catch (e) {
print(e);
}
final uri = Uri.https(
apiUrl,
'/api/material',
);
verify(
() => httpClient.get(uri),
).called(1);
});
});
});
}
previously errors with timeout is disapear, but now I have a
type 'Null' is not a subtype of type 'Future<StreamedResponse>'
package:test_api fail
package:mocktail/src/mocktail.dart 722:7 _VerifyCall._checkWith
package:mocktail/src/mocktail.dart 515:18 _makeVerify.<fn>
test\my_test.dart 77:15 main.<fn>.<fn>.<fn>
No matching calls. All calls: MockHttpClient.send(GET http://10.0.2.2:5001/api/material)
(If you called `verify(...).called(0);`, please instead use `verifyNever(...);`.)
error. Maybe I should mock http server somehow?
I will take a closer look with mocktail
. Will get back to you as soon as I can 😄
Any updates? :)
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
mocktail
Hi sorry, hectic months. I. haven't gotten around to trying tests with mocktail. But it's getting to the top of my list too so I'm hoping to dive into it soon.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Describe the bug I wonder how to test http client with intercepor. I get every time
TimeoutException after 0:00:15.000000: Future not completed
orPrzekroczono limit czasu semafora.
if I remove timeout in client.get(...). Below there are my classes, I hope you can easly reproduce issue.To Reproduce Steps to reproduce the behavior:
lib\
authentication_repository.dart
material.dart
material.g.dart
storage_service.dart
token.dart
warehouse_api_client.dart
test\my_test.dart
.env.development
pubspec.yaml
Expected behavior I want to test my api where I use http client with interceptor but when I run test the async method takes few seconds and return error that I have mentioned above.
Please complete the following information): [√] Flutter (Channel stable, 3.3.8, on Microsoft Windows [Version 10.0.19042.2194], locale pl-PL) [!] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc4) ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses [√] Chrome - develop for the web [!] Visual Studio - develop for Windows (Visual Studio Community 2022 17.4.1) X Visual Studio is missing necessary components. Please re-run the Visual Studio installer for the "Desktop development with C++" workload, and include these components: MSVC v142 - VS 2019 C++ x64/x86 build tools
! Doctor found issues in 2 categories.