Unit Testing GetxController

3,304

This question has now been answered in the GetX docs.

Pasted from the docs:

Tests

You can test your controllers like any other class, including their lifecycles:

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, '');
  });
}

Mockito or mocktail

If you need to mock your GetxController/GetxService, you should extend GetxController, and mixin it with Mock, that way

class NotificationServiceMock extends GetxService with Mock implements NotificationService {}

Not exactly intuitive, is it?

Share:
3,304
Ovidius Mazuru
Author by

Ovidius Mazuru

Updated on December 28, 2022

Comments

  • Ovidius Mazuru
    Ovidius Mazuru over 1 year

    I'm a beginner with tdd so please forgive me if it's a dumb question.

    I'm having difficulty unit testing GetxControllers. Does anyone know a simple way of doing this? Whenever I do I get errors since Get is calling onStart and it doesn't like the result Mockito's giving it. I've tried using Mockito 5.0.1's auto generated code as well as the older syntax, class MockController extends Mock implements Controller{}, as well as extends Fake.

    The auto generated code has build errors, since Mockito is trying to use _InternalFinalCallback, but it's not being imported as it's private. I tried just copy pasting that part of the code into my generated file (and switching off pub build watch) but first that's a short term solution with it's own issues, 2nd it still doesn't work since the onStart and onDelete functions now tell me they're not valid overrides.

    Also, I can see the get_test package but it's documentation is basically 0, and in the examples the controller is just used directly -- there's never a mocked controller.

    I tried setting Get.testMode = true; but again that doesn't seem to do anything. And while I found that property in the docs, I didn't find how to use it correctly.

    Any help would be appreciated,

    Here's my code but the issue seems to be with the GetxControllers, so I don't think it's relevant much:

    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());
        });
      });
    }
    

    There really isn't anything in my AuthController and session controller, but code is as follows:

    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);
      }
    }
    
    

    And the auto-generated, buggy mock controller:

    // 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);
    }
    
  • King Leon
    King Leon over 2 years
    How would one go about this using the annotation for code generation?