Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default @GenerateMocks and custom @GenerateMocks has different returns (Generics to dynamic) #422

Closed
EhiltonKazuo opened this issue Jun 2, 2021 · 11 comments
Labels
P3 A lower priority bug or feature request type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@EhiltonKazuo
Copy link

Hi, i'm using the custom mocks, but when i run the build_runner the vscode found 4 problems in generated code. But when i use the default mocks the error doesn't occur.

Here is the custom generate code:

class BoxSpy<E> extends _i1.Mock implements _i2.Box<E> {
  BoxSpy() {
    _i1.throwOnMissingStub(this);
  }
  @override
  Iterable<dynamic> get values =>
      (super.noSuchMethod(Invocation.getter(#values), returnValue: [])
          as Iterable<dynamic>);
  ...
  @override
  Iterable<dynamic> valuesBetween({dynamic startKey, dynamic endKey}) =>
      (super.noSuchMethod(
          Invocation.method(
              #valuesBetween, [], {#startKey: startKey, #endKey: endKey}),
          returnValue: []) as Iterable<dynamic>);
  @override
  dynamic getAt(int? index) =>
      super.noSuchMethod(Invocation.method(#getAt, [index]));
  @override
  Map<dynamic, dynamic> toMap() =>
      (super.noSuchMethod(Invocation.method(#toMap, []),
          returnValue: <dynamic, dynamic>{}) as Map<dynamic, dynamic>);
  ...
}

The default generate code:

class MockBox<E> extends _i1.Mock implements _i2.Box<E> {
  MockBox() {
    _i1.throwOnMissingStub(this);
  }
  @override
  Iterable<E> get values =>
      (super.noSuchMethod(Invocation.getter(#values), returnValue: [])
          as Iterable<E>);
  ...
  @override
  Iterable<E> valuesBetween({dynamic startKey, dynamic endKey}) =>
      (super.noSuchMethod(
          Invocation.method(
              #valuesBetween, [], {#startKey: startKey, #endKey: endKey}),
          returnValue: []) as Iterable<E>);
  @override
  E? getAt(int? index) =>
      (super.noSuchMethod(Invocation.method(#getAt, [index])) as E?);
  @override
  Map<dynamic, E> toMap() => (super.noSuchMethod(Invocation.method(#toMap, []),
      returnValue: <dynamic, E>{}) as Map<dynamic, E>);
  ...
}
@EhiltonKazuo
Copy link
Author

I'm using hive ^2.0.4 dependency

@EhiltonKazuo EhiltonKazuo changed the title Default @GenerateMocks and custom @GenerateMocks has different return (Iterable<E> to Iterable<dynamic> , E to dynamic and Map<dynamic, E> to Map<dynamic, dynamic>) Default @GenerateMocks and custom @GenerateMocks has different returns (Generics to dynamic) Jun 2, 2021
@srawlins
Copy link
Member

srawlins commented Jun 2, 2021

Thanks for the bug report. Please include the four errors reported.

@EhiltonKazuo
Copy link
Author

EhiltonKazuo commented Jun 3, 2021

test/infra/cache/hive_adapter_test.mocks.dart:141:25: Error: The return type of the method 'BoxSpy.values' is 'Iterable<dynamic>', which does not match the return type, 'Iterable<E>', of the overridden method, 'Box.values'.
 - 'Iterable' is from 'dart:core'.
Change to a subtype of 'Iterable<E>'.
  Iterable<dynamic> get values =>
                        ^
/C:/Flutter/flutter/.pub-cache/hosted/pub.dartlang.org/hive-2.0.4/lib/src/box/box.dart:17:19: Context: This is the overridden method ('values').

  Iterable<E> get values;
                  ^
test/infra/cache/hive_adapter_test.mocks.dart:171:21: Error: The return type of the method 'BoxSpy.valuesBetween' is 'Iterable<dynamic>', which does not match the return type, 'Iterable<E>', of the overridden method, 'Box.valuesBetween'.
 - 'Iterable' is from 'dart:core'.
Change to a subtype of 'Iterable<E>'.
  Iterable<dynamic> valuesBetween({dynamic startKey, dynamic endKey}) =>
                    ^
/C:/Flutter/flutter/.pub-cache/hosted/pub.dartlang.org/hive-2.0.4/lib/src/box/box.dart:27:15: Context: This is the overridden method ('valuesBetween').
  Iterable<E> valuesBetween({dynamic startKey, dynamic endKey});
              ^
test/infra/cache/hive_adapter_test.mocks.dart:177:11: Error: The return type of the method 'BoxSpy.getAt' is 'dynamic', which does not match the return type, 'E?', of the overridden method, 'Box.getAt'.
Change to a subtype of 'E?'.
  dynamic getAt(int? index) =>
          ^
/C:/Flutter/flutter/.pub-cache/hosted/pub.dartlang.org/hive-2.0.4/lib/src/box/box.dart:37:6: Context: This is the overridden method ('getAt').
  E? getAt(int index);
     ^
test/infra/cache/hive_adapter_test.mocks.dart:180:25: Error: The return type of the method 'BoxSpy.toMap' is 'Map<dynamic, dynamic>', which does not match the return type, 'Map<dynamic, E>', of the overridden method, 'Box.toMap'.
 - 'Map' is from 'dart:core'.
Change to a subtype of 'Map<dynamic, E>'.
  Map<dynamic, dynamic> toMap() =>
                        ^
/C:/Flutter/flutter/.pub-cache/hosted/pub.dartlang.org/hive-2.0.4/lib/src/box/box.dart:40:19: Context: This is the overridden method ('toMap').
  Map<dynamic, E> toMap();
                  ^
Failed to load "E:\Development\Flutter\Autônomo\calendar\test\infra\cache\hive_adapter_test.dart": Compilation failed for testPath=E:\Development\Flutter\Autônomo\calendar\test\infra\cache\hive_adapter_test.dart
dart:async/stream_controller.dart 561:44                       _StreamController.addError
dart:async/stream_controller.dart 830:13                       _StreamSinkWrapper.addError
_GuaranteeSink._addError
_GuaranteeSink.addError
package:flutter_tools/src/test/flutter_platform.dart 466:37    FlutterPlatform._startTest
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19                                   _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 45:22                        _asyncThenWrapperHelper
package:flutter_tools/src/test/flutter_platform.dart 378:36    FlutterPlatform.loadChannel
package:flutter_tools/src/test/flutter_platform.dart 342:44    FlutterPlatform.load
Loader.loadFile.<fn>
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19                                   _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 45:22                        _asyncThenWrapperHelper
package:test_core/src/runner/loader.dart                       Loader.loadFile.<fn>
new LoadSuite.<fn>.<fn>
new LoadSuite.<fn>.<fn>
new LoadSuite.<fn>
Invoker.waitForOutstandingCallbacks.<fn>
Invoker.waitForOutstandingCallbacks.<fn>
dart:async/zone.dart 1354:13                                   _rootRun
dart:async/zone.dart 1258:19                                   _CustomZone.run
dart:async/zone.dart 1789:10                                   _runZoned
dart:async/zone.dart 1711:10                                   runZoned
Invoker.waitForOutstandingCallbacks
Invoker._onRun.<fn>.<fn>.<fn>
===== asynchronous gap ===========================
dart:async/zone.dart 1286:19                                   _CustomZone.registerUnaryCallback
dart:async-patch/async_patch.dart 45:22                        _asyncThenWrapperHelper
package:test_api/src/backend/invoker.dart                      Invoker._onRun.<fn>.<fn>.<fn>
dart:async/zone.dart 1354:13                                   _rootRun
dart:async/zone.dart 1258:19                                   _CustomZone.run
dart:async/zone.dart 1789:10                                   _runZoned
dart:async/zone.dart 1711:10                                   runZoned
Invoker._onRun.<fn>.<fn>
dart:async/zone.dart 1354:13                                   _rootRun
dart:async/zone.dart 1258:19                                   _CustomZone.run
dart:async/zone.dart 1789:10                                   _runZoned
dart:async/zone.dart 1711:10                                   runZoned
Invoker.guard
Invoker._guardIfGuarded
Invoker._onRun.<fn>
Chain.capture.<fn>
dart:async/zone.dart 1354:13                                   _rootRun
dart:async/zone.dart 1258:19                                   _CustomZone.run
dart:async/zone.dart 1789:10                                   _runZoned
dart:async/zone.dart 1711:10                                   runZoned
Chain.capture
Invoker._onRun
LiveTestController.run
dart:async/future.dart 198:37                                  new Future.microtask.<fn>
dart:async/zone.dart 1346:47                                   _rootRun
dart:async/zone.dart 1258:19                                   _CustomZone.run
dart:async/zone.dart 1162:7                                    _CustomZone.runGuarded
dart:async/zone.dart 1202:23                                   _CustomZone.bindCallbackGuarded.<fn>
dart:async/zone.dart 1354:13                                   _rootRun
dart:async/zone.dart 1258:19                                   _CustomZone.run
dart:async/zone.dart 1162:7                                    _CustomZone.runGuarded
dart:async/zone.dart 1202:23                                   _CustomZone.bindCallbackGuarded.<fn>
dart:async/schedule_microtask.dart 40:21                       _microtaskLoop
dart:async/schedule_microtask.dart 49:5                        _startMicrotaskLoop
dart:isolate-patch/isolate_patch.dart 120:13                   _runPendingImmediateCallback
dart:isolate-patch/isolate_patch.dart 185:5                    _RawReceivePortImpl._handleMessage

✖ loading E:\Development\Flutter\Autônomo\calendar\test\infra\cache\hive_adapter_test.dart
Exited (1)

@srawlins
Copy link
Member

srawlins commented Jun 3, 2021

Curious, can you send me the code for class Box? I'd like to look at the signature for values and getAt.

@srawlins
Copy link
Member

srawlins commented Jun 3, 2021

Also can you include your @GenerateMocks code? I'm curious to see how you specify Box, like MockSpec<Box>() or MockSpec<Box<dynamic>>(), ...

@srawlins
Copy link
Member

srawlins commented Jun 3, 2021

I can reproduce this bug with a little example.

@srawlins srawlins added the type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) label Jun 3, 2021
@EhiltonKazuo
Copy link
Author

abstract class Box<E> implements BoxBase<E> {
  /// All the values in the box.
  ///
  /// The values are in the same order as their keys.
  Iterable<E> get values;

  /// Returns an iterable which contains all values starting with the value
  /// associated with [startKey] (inclusive) to the value associated with
  /// [endKey] (inclusive).
  ///
  /// If [startKey] does not exist, an empty iterable is returned. If [endKey]
  /// does not exist or is before [startKey], it is ignored.
  ///
  /// The values are in the same order as their keys.
  Iterable<E> valuesBetween({dynamic startKey, dynamic endKey});

  /// Returns the value associated with the given [key]. If the key does not
  /// exist, `null` is returned.
  ///
  /// If [defaultValue] is specified, it is returned in case the key does not
  /// exist.
  E? get(dynamic key, {E? defaultValue});

  /// Returns the value associated with the n-th key.
  E? getAt(int index);

  /// Returns a map which contains all key - value pairs of the box.
  Map<dynamic, E> toMap();
}

@EhiltonKazuo
Copy link
Author

EhiltonKazuo commented Jun 3, 2021

I don't know if this test works, here is the example that i'm using for my application:

import 'package:mockito/annotations.dart';
import 'package:hive/hive.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

import 'hive_adapter_test.mocks.dart';

class HiveAdapter {
  final HiveInterface hive;

  HiveAdapter(this.hive);

  Future<void> save(String key, dynamic value) async {
    final box = await hive.openBox(key);
    box.add(value);
  }
}

@GenerateMocks([], customMocks: [
  MockSpec<HiveInterface>(as: #HiveSpy),
  MockSpec<Box>(as: #BoxSpy),
])
void main() {
  late HiveAdapter sut;
  late HiveSpy hive;
  late BoxSpy hiveBox;
  late Map event;

  PostExpectation mockHiveCall() => when(hive.openBox(any));

  void mockHive() => mockHiveCall().thenAnswer((_) async => hiveBox);

  PostExpectation mockHiveBoxCall() => when(hiveBox.add(any));

  void mockBoxHive() => mockHiveBoxCall().thenAnswer((_) async => 1);

  setUp(() {
    hive = HiveSpy();
    sut = HiveAdapter(hive);
    hiveBox = BoxSpy();
    event = {'any_key1': 'any_value1', 'any_key2': 'any_value2'};
    mockHive();
    mockBoxHive();
  });

  test('Should Hive calls with correct values', () async {
    //act
    await sut.save('events', event);
    //assert
    verify(hive.openBox('events'));
    verify(hiveBox.add(event));
  });
}

@srawlins
Copy link
Member

srawlins commented Jun 3, 2021

The problem here is that this line: MockSpec<Box>(as: #BoxSpy), includes an implicit dynamic. To make it explicit, it is MockSpec<Box<dynamic>>(as: #BoxSpy),. This is not mockito's doing, but is how the language works.

Mockito might be able to distinguish between an implicit type instantiation here (i.e. missing type argument(s)) and an explicit type instantiation.

But to solve your problem today, you may need to declare a custom mock for each type of Box. For example:

MockSpec<Box<int>>(as: #BoxSpyOfInt),
MockSpec<Box<String>>(as: #BoxSpyOfString),

@srawlins srawlins added the P3 A lower priority bug or feature request label Jun 3, 2021
@EhiltonKazuo
Copy link
Author

EhiltonKazuo commented Jun 3, 2021

The strange is when i put the same Box in default code generator doesn't occur the problem like this code:

import 'package:mockito/annotations.dart';
import 'package:hive/hive.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

import 'hive_adapter_test.mocks.dart';

class HiveAdapter {
  final HiveInterface hive;

  HiveAdapter(this.hive);

  Future<void> save(String key, dynamic value) async {
    final box = await hive.openBox(key);
    box.add(value);
  }
}

@GenerateMocks([
  HiveInterface,
  Box,
])
void main() {
  late HiveAdapter sut;
  late MockHiveInterface hive;
  late MockBox hiveBox;
  late Map event;

  PostExpectation mockHiveCall() => when(hive.openBox(any));

  void mockHive() => mockHiveCall().thenAnswer((_) async => hiveBox);

  PostExpectation mockHiveBoxCall() => when(hiveBox.add(any));

  void mockBoxHive() => mockHiveBoxCall().thenAnswer((_) async => 1);

  setUp(() {
    hive = MockHiveInterface();
    sut = HiveAdapter(hive);
    hiveBox = MockBox();
    event = {'any_key1': 'any_value1', 'any_key2': 'any_value2'};
    mockHive();
    mockBoxHive();
  });

  test('Should Hive calls with correct values', () async {
    //act
    await sut.save('events', event);
    //assert
    verify(hive.openBox('events'));
    verify(hiveBox.add(event));
  });
}

The test runs succesfully.
✓ Should Hive calls with correct values

Well, thanks for reply. =)

@srawlins
Copy link
Member

srawlins commented Jun 3, 2021

Yeah this is an ugly (IMO) wrinkle in the language. List foo; declares a List<dynamic>, but in other cases List is a Type. So Type foo = List references the Type, and Types know nothing about generics. ☹️

srawlins added a commit that referenced this issue Jun 4, 2021
… "custom mock" annotation with implicit type arguments.

Given a method which references
type variables defined on their enclosing class (for example, `T` in
`class Foo<T>`), mockito will now correctly reference `T` in generated code.

Fixes #422

PiperOrigin-RevId: 377571931
srawlins added a commit that referenced this issue Jun 4, 2021
… "custom mock" annotation with implicit type arguments.

Given a method which references
type variables defined on their enclosing class (for example, `T` in
`class Foo<T>`), mockito will now correctly reference `T` in generated code.

Fixes #422

PiperOrigin-RevId: 377571931
srawlins added a commit that referenced this issue Jun 4, 2021
… "custom mock" annotation with implicit type arguments.

Given a method which references
type variables defined on their enclosing class (for example, `T` in
`class Foo<T>`), mockito will now correctly reference `T` in generated code.

Fixes #422

PiperOrigin-RevId: 377571931
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P3 A lower priority bug or feature request type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

2 participants