Hot questions for Using Mockito in dart

Question:

I am writing a unit test in which I need to mock a JsObject so that I don't need to do actual javascript interop in my test. However, I'm using the indexing operator [] to access a field in my JsObject. I am using the dart mockito library https://github.com/fibulwinter/dart-mockito for mocking and I can't seem to find how I go about mocking the behavior of operators on the mocked object.


Answer:

Mockito makes stubbing very easy, stubbing the index operator works like stubbing any other method. Imaging that you want to stub the index operator of the following class:

class IndexTest {
  operator[] (String value);
}

In the first step we create a mock for that class:

class MockIndexTest extends Mock implements IndexTest {
  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

Now in you test you can set the return values that you expect for invocations using the index operator:

  test('Test', () {
    final t = new MockIndexTest();

    // Set return values
    when(t[any]).thenReturn(0); // 1
    when(t['one']).thenReturn(1); // 2
    when(t['two']).thenReturn(2); // 3

    // Check return values
    expect(t['one'], equals(1));
    expect(t['two'], equals(2));
    expect(t['something else'], equals(0));
  });

Without stubbing a call always returns null. With the any value provided by mockito you can set the default return value for calls with any argument (see 1). You can also set the return value for a specific set of parameters (see 2 and 3). You have to set the default value before setting the specific ones.

Question:

I use Mockito for writing tests on Flutter. I have a mocked class and method with arguments that are functions - Function() and this method returns StreamSubscription. I need to pass these arguments to the call of listen() function but can't find a way to do it. (See example) Could somebody help me, please?

I tried to pass them with argThat(anyNamed('nameOfArgument') like in the example, but tests trows error - The "anyNamed" argument matcher is used outside of method stubbing (via when)

class MockPhotoLibraryService extends Mock implements PhotoLibraryService {}

PhotoLibraryService setupMockPhotoLibraryService() {
  final photoLibraryService = MockPhotoLibraryService();

  when(
    photoLibraryService.getPhotosForPeriod(
        onData: anyNamed('onData'),
        onDone: anyNamed('onDone'),
        onError: anyNamed('onError')),
  ).thenAnswer((_) => Stream<Photo>.fromFuture(
          Future<Photo>.delayed(Duration(milliseconds: 50), () => Photo()))
      .listen(argThat(anyNamed('onData')), //need to pass argument named onData
          onDone: argThat(anyNamed('onDone')), //need to pass argument named onDone
          onError: argThat(anyNamed('onError')), //need to pass argument named onError
          cancelOnError: true));

  return photoLibraryService;
}

I need these arguments functions to be called by the Future for the correct work of my testable widget.


Answer:

You can get access to the original call parameters through Invocation object. It is passed as a parameter to the thenAnswer callback function.

when(photoLibraryService.getPhotosForPeriod(
  onData: anyNamed('onData'),
  onDone: anyNamed('onDone'),
  onError: anyNamed('onError'),
)).thenAnswer((Invocation invocation) {
  final namedArgs = invocation.namedArguments;
  final onData = namedArgs[Symbol('onData')] as Function(Photo);
  final onDone = namedArgs[Symbol('onDone')] as Function();
  final onError = namedArgs[Symbol('onError')] as Function(dynamic);

  return Stream<Photo>.fromFuture(
    Future<Photo>.delayed(Duration(milliseconds: 50), () => Photo()),
  ).listen(onData, onDone: onDone, onError: onError, cancelOnError: true);
});