Модульное тестирование GetxController
Я новичок в tdd, так что простите меня, если это глупый вопрос.
У меня проблемы с модульным тестированием GetxControllers. Кто-нибудь знает простой способ сделать это? Всякий раз, когда я это делаю, я получаю ошибки, поскольку Get вызывает onStart, и ему не нравится результат, который дает ему Mockito. Я пробовал использовать автоматически сгенерированный код Mockito 5.0.1, а также старый синтаксис, класс MockController расширяет Mock, реализует Controller{}, а также расширяет Fake.
В автоматически сгенерированном коде есть ошибки сборки, поскольку Mockito пытается использовать _InternalFinalCallback, но он не импортируется как частный. Я попытался просто скопировать, вставив эту часть кода в свой сгенерированный файл (и выключив наблюдение за сборкой паба), но сначала это краткосрочное решение с его собственными проблемами, во-вторых, оно все еще не работает, так как функции onStart и onDelete теперь говорят мне они не являются действительными переопределениями.
Кроме того, я вижу пакет get_test, но его документация в основном равна 0, а в примерах контроллер просто используется напрямую - никогда не бывает имитируемого контроллера.
Я пробовал установить Get.testMode = true; но опять же, похоже, это ничего не дает. И хотя я нашел это свойство в документации, я не нашел, как его правильно использовать.
Любая помощь будет оценена по достоинству,
Вот мой код, но проблема, похоже, связана с GetxControllers, поэтому я не думаю, что это важно:
class FakeAuthController extends Fake implements AuthController {}
@GenerateMocks([AuthController])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late MockAuthController mockAuthController;
late FakeAuthController fakeAuthController;
late SessionController sessionController;
setUp(() {
Get.testMode = true;
mockAuthController = MockAuthController();
fakeAuthController = FakeAuthController();
Get.put<AuthController>(mockAuthController);
sessionController = SessionController();
});
tearDown(() {
Get.delete<AuthController>();
});
group('getSessionInfo', () {
test('Calls authFacade getSignedInUserId', () async {
await sessionController.getSessionInfo();
when(Get.find<AuthController>()).thenReturn(fakeAuthController);
verify(mockAuthController.getSignedInUserId());
});
});
}
В моем AuthController и контроллере сеанса действительно ничего нет, но код выглядит следующим образом:
import 'package:get/get.dart';
class AuthController extends GetxController {
String getSignedInUserId() {
// await Future.delayed(Duration(milliseconds: 1));
return '1';
}
}
import 'package:get/get.dart';
import '../../auth/controllers/auth_controller.dart';
import '../models/session_info.dart';
class SessionController extends GetxController {
final AuthController authController = Get.find<AuthController>();
Rx<SessionInfo> sessionInfo = Rx<SessionInfo>();
Future<void> getSessionInfo() async {
// authController.getSignedInUserId();
// sessionInfo.value = SessionInfo(userId: userId);
}
}
И автоматически сгенерированный макет контроллера с ошибками:
// Mocks generated by Mockito 5.0.1 from annotations
// in smart_locker_controller/test/shared/controllers/session_controller_test.dart.
// Do not manually edit this file.
import 'dart:ui' as _i4;
import 'package:get/get_instance/src/lifecycle.dart' as _i2;
import 'package:get/get_state_manager/src/simple/list_notifier.dart' as _i5;
import 'package:mockito/mockito.dart' as _i1;
import 'package:smart_locker_controller/auth/controllers/auth_controller.dart'
as _i3;
// ignore_for_file: comment_references
// ignore_for_file: unnecessary_parenthesis
class _Fake_InternalFinalCallback<T> extends _i1.Fake
implements _i2._InternalFinalCallback<T> {}
/// A class which mocks [AuthController].
///
/// See the documentation for Mockito's code generation for more information.
class MockAuthController extends _i1.Mock implements _i3.AuthController {
MockAuthController() {
_i1.throwOnMissingStub(this);
}
@override
int get notifierVersion =>
(super.noSuchMethod(Invocation.getter(#notifierVersion), returnValue: 0)
as int);
@override
int get notifierMicrotask =>
(super.noSuchMethod(Invocation.getter(#notifierMicrotask), returnValue: 0)
as int);
@override
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
@override
int get listeners =>
(super.noSuchMethod(Invocation.getter(#listeners), returnValue: 0)
as int);
@override
_i2._InternalFinalCallback<void> get onStart =>
(super.noSuchMethod(Invocation.getter(#onStart),
returnValue: _Fake_InternalFinalCallback<void>())
as _i2._InternalFinalCallback<void>);
@override
_i2._InternalFinalCallback<void> get onDelete =>
(super.noSuchMethod(Invocation.getter(#onDelete),
returnValue: _Fake_InternalFinalCallback<void>())
as _i2._InternalFinalCallback<void>);
@override
bool get initialized =>
(super.noSuchMethod(Invocation.getter(#initialized), returnValue: false)
as bool);
@override
bool get isClosed =>
(super.noSuchMethod(Invocation.getter(#isClosed), returnValue: false)
as bool);
@override
String getSignedInUserId() =>
(super.noSuchMethod(Invocation.method(#getSignedInUserId, []),
returnValue: '') as String);
@override
void update([List<Object>? ids, bool? condition = true]) =>
super.noSuchMethod(Invocation.method(#update, [ids, condition]),
returnValueForMissingStub: null);
@override
void refreshGroup(Object? id) =>
super.noSuchMethod(Invocation.method(#refreshGroup, [id]),
returnValueForMissingStub: null);
@override
void removeListener(_i4.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#removeListener, [listener]),
returnValueForMissingStub: null);
@override
void removeListenerId(Object? id, _i4.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#removeListenerId, [id, listener]),
returnValueForMissingStub: null);
@override
_i5.Disposer addListener(_i5.GetStateUpdate? listener) =>
(super.noSuchMethod(Invocation.method(#addListener, [listener]),
returnValue: () {}) as _i5.Disposer);
@override
_i5.Disposer addListenerId(Object? key, _i5.GetStateUpdate? listener) =>
(super.noSuchMethod(Invocation.method(#addListenerId, [key, listener]),
returnValue: () {}) as _i5.Disposer);
@override
void disposeId(Object? id) =>
super.noSuchMethod(Invocation.method(#disposeId, [id]),
returnValueForMissingStub: null);
}
2 ответа
Ответ на этот вопрос теперь дан в документации GetX.
Вставил из документов:
Тесты
Вы можете тестировать свои контроллеры, как и любой другой класс, включая их жизненные циклы:
class Controller extends GetxController {
@override
void onInit() {
super.onInit();
//Change value to name2
name.value = 'name2';
}
@override
void onClose() {
name.value = '';
super.onClose();
}
final name = 'name1'.obs;
void changeName() => name.value = 'name3';
}
void main() {
test('''
Test the state of the reactive variable "name" across all of its lifecycles''',
() {
/// You can test the controller without the lifecycle,
/// but it's not recommended unless you're not using
/// GetX dependency injection
final controller = Controller();
expect(controller.name.value, 'name1');
/// If you are using it, you can test everything,
/// including the state of the application after each lifecycle.
Get.put(controller); // onInit was called
expect(controller.name.value, 'name2');
/// Test your functions
controller.changeName();
expect(controller.name.value, 'name3');
/// onClose was called
Get.delete<Controller>();
expect(controller.name.value, '');
});
}
Мокито или моктейль
Если вам нужно издеваться над вашим GetxController/GetxService, вы должны расширить GetxController и таким образом смешать его с Mock
class NotificationServiceMock extends GetxService with Mock implements NotificationService {}
Не совсем интуитивно, не так ли?
Попробуйте изменить определение поддельного контроллера на:
class FakeAuthController extends GetxController with Fake implements AuthController {}
Не уверен, что это работает для
Fake
, но я только что исправил аналогичную проблему с
Mock
с этим.