From 99ea9f8fafd9303078b4001e20ede8a95bb97467 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sun, 7 Mar 2021 09:38:45 -0600 Subject: [PATCH] fix(mocktail)!: verifyInOrder --- packages/mocktail/CHANGELOG.md | 6 ++ packages/mocktail/lib/mocktail.dart | 3 +- packages/mocktail/lib/src/fake.dart | 50 --------------- packages/mocktail/lib/src/mocktail.dart | 61 ++++++++++--------- packages/mocktail/pubspec.yaml | 2 +- .../mockito_compat/nnbd_support_test.dart | 54 ++++++++++++++++ .../test/mockito_compat/verify_test.dart | 38 +++++++----- 7 files changed, 120 insertions(+), 94 deletions(-) delete mode 100644 packages/mocktail/lib/src/fake.dart create mode 100644 packages/mocktail/test/mockito_compat/nnbd_support_test.dart diff --git a/packages/mocktail/CHANGELOG.md b/packages/mocktail/CHANGELOG.md index 1cd916a..ef190d6 100644 --- a/packages/mocktail/CHANGELOG.md +++ b/packages/mocktail/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.0.2-dev.3 + +- **BREAKING** fix: `verifyInOrder` function signature +- docs: update inline API documentation +- test: add nnbd compat tests + # 0.0.2-dev.2 - chore: documentation updates diff --git a/packages/mocktail/lib/mocktail.dart b/packages/mocktail/lib/mocktail.dart index 8719fd0..f6e6abd 100644 --- a/packages/mocktail/lib/mocktail.dart +++ b/packages/mocktail/lib/mocktail.dart @@ -1,6 +1,7 @@ library mocktail; -export 'src/fake.dart'; +// ignore: deprecated_member_use +export 'package:test_api/fake.dart' show Fake; export 'src/mocktail.dart' show Mock, diff --git a/packages/mocktail/lib/src/fake.dart b/packages/mocktail/lib/src/fake.dart deleted file mode 100644 index d530f8b..0000000 --- a/packages/mocktail/lib/src/fake.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:mocktail/mocktail.dart'; - -/// A stand-in for another object which cannot be used except for specifically -/// overridden methods. -/// -/// A fake has a default behavior for every field and method of throwing -/// [UnimplementedError]. Fields and methods that are excersized by the code -/// under test should be manually overridden in the implementing class. -/// -/// A fake does not have any support for verification or defining behavior from -/// the test, it cannot be used as a [Mock]. -/// -/// In most cases a shared full fake implementation without a `noSuchMethod` is -/// preferable to `extends Fake`, however `extends Fake` is preferred against -/// `extends Mock` mixed with manual `@override` implementations. -/// -/// __Example use__: -/// -/// // Real class. -/// class Cat { -/// String meow(String suffix) => 'Meow$suffix'; -/// String hiss(String suffix) => 'Hiss$suffix'; -/// } -/// -/// // Fake class. -/// class FakeCat extends Fake implements Cat { -/// @override -/// String meow(String suffix) => 'FakeMeow$suffix'; -/// } -/// -/// void main() { -/// // Create a new fake Cat at runtime. -/// var cat = new FakeCat(); -/// -/// // Try making a Cat sound... -/// print(cat.meow('foo')); // Prints 'FakeMeowfoo' -/// print(cat.hiss('foo')); // Throws -/// } -/// -/// **WARNING**: [Fake] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of -/// runtime reflection, and causes sub-standard code to be generated. As such, -/// [Fake] should strictly _not_ be used in any production code, especially if -/// used within the context of Dart for Web (dart2js, DDC) and Dart for Mobile -/// (Flutter). -abstract class Fake { - @override - dynamic noSuchMethod(Invocation invocation) { - throw UnimplementedError(invocation.memberName.toString().split('"')[1]); - } -} diff --git a/packages/mocktail/lib/src/mocktail.dart b/packages/mocktail/lib/src/mocktail.dart index 5b9eb5c..0022b20 100644 --- a/packages/mocktail/lib/src/mocktail.dart +++ b/packages/mocktail/lib/src/mocktail.dart @@ -2,11 +2,10 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:matcher/matcher.dart'; +import 'package:mocktail/mocktail.dart'; // ignore: deprecated_member_use import 'package:test_api/test_api.dart'; -import 'fake.dart'; - part '_arg_matcher.dart'; part '_invocation_matcher.dart'; part '_is_invocation.dart'; @@ -47,26 +46,26 @@ void throwOnMissingStub( /// customized at runtime to define how it may behave using [when]. /// /// __Example use__: +/// ```dart +/// // Real class. +/// class Cat { +/// String getSound(String suffix) => 'Meow$suffix'; +/// } /// -/// // Real class. -/// class Cat { -/// String getSound(String suffix) => 'Meow$suffix'; -/// } -/// -/// // Mock class. -/// class MockCat extends Mock implements Cat {} -/// -/// void main() { -/// // Create a new mocked Cat at runtime. -/// final cat = MockCat(); +/// // Mock class. +/// class MockCat extends Mock implements Cat {} /// -/// // When 'getSound' is called, return 'Woof' -/// when(() => cat.getSound(any())).thenReturn('Woof'); +/// void main() { +/// // Create a new mocked Cat at runtime. +/// final cat = MockCat(); /// -/// // Try making a Cat sound... -/// print(cat.getSound('foo')); // Prints 'Woof' -/// } +/// // When 'getSound' is called, return 'Woof' +/// when(() => cat.getSound(any())).thenReturn('Woof'); /// +/// // Try making a Cat sound... +/// print(cat.getSound('foo')); // Prints 'Woof' +/// } +/// ``` /// A class which `extends Mock` should not have any directly implemented /// overridden fields or methods. These fields would not be usable as a [Mock] /// with [verify] or [when]. To implement a subset of an interface manually use @@ -173,7 +172,7 @@ typedef _ReturnsCannedResponse = Expectation Function(); /// canned response method on the result. For example: /// /// ```dart -/// when(cat.eatFood("fish")).thenReturn(true); +/// when(() => cat.eatFood("fish")).thenReturn(true); /// ``` /// /// Mocktail will store the fake call to `cat.eatFood`, and pair the exact @@ -295,7 +294,7 @@ class Expectation { /// /// ```dart /// cat.eatFood("chicken"); -/// verify(cat.eatFood("fish")); +/// verify(() => cat.eatFood("fish")); /// ``` /// /// Mocktail will fail the current test case if `cat.eatFood` hasn't been called @@ -303,8 +302,8 @@ class Expectation { /// method was called a certain number of times. For example: /// /// ```dart -/// verify(cat.eatFood("fish")).called(2); -/// verify(cat.eatFood("fish")).called(greaterThan(3)); +/// verify(() => cat.eatFood("fish")).called(2); +/// verify(() => cat.eatFood("fish")).called(greaterThan(3)); /// ``` /// /// Note: When mocktail verifies a method call, said call is then excluded from @@ -325,7 +324,7 @@ _Verify get verify => _makeVerify(false); /// /// ```dart /// cat.eatFood("chicken"); -/// verifyNever(cat.eatFood("fish")); +/// verifyNever(() => cat.eatFood("fish")); /// ``` /// /// Mocktail will pass the current test case, as `cat.eatFood` has not been @@ -336,7 +335,7 @@ _Verify get verifyNever => _makeVerify(true); /// given arguments. For example: /// /// ```dart -/// verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood(any)]); +/// verifyInOrder(() => [cat.eatFood("Milk"), cat.sound(), cat.eatFood(any)]); /// ``` /// /// This verifies that `eatFood` was called with `"Milk"`, `sound` was called @@ -348,8 +347,9 @@ _Verify get verifyNever => _makeVerify(true); /// For example, if [verifyInOrder] is given these calls to verify: /// /// ```dart -/// var verification = verifyInOrder( -/// [cat.eatFood(captureAny), cat.chew(), cat.eatFood(captureAny)]); +/// final verification = verifyInOrder( +/// () => [cat.eatFood(captureAny), cat.chew(), cat.eatFood(captureAny)], +/// ); /// ``` /// /// then `verification` is a list which contains a `captured` getter which @@ -366,13 +366,18 @@ _Verify get verifyNever => _makeVerify(true); /// other calls were made to `eatFood` or `sound` between the three given /// calls, or before or after them, the verification will still succeed. List Function( - List recordedInvocations, + List Function() recordedInvocations, ) get verifyInOrder { if (_verifyCalls.isNotEmpty) { throw StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (List _) { + return (List Function() _) { + try { + _(); + } catch (_) { + if (_ is! TypeError) rethrow; + } _verificationInProgress = false; var verificationResults = []; var time = DateTime.fromMillisecondsSinceEpoch(0); diff --git a/packages/mocktail/pubspec.yaml b/packages/mocktail/pubspec.yaml index 8da08bc..91acb99 100644 --- a/packages/mocktail/pubspec.yaml +++ b/packages/mocktail/pubspec.yaml @@ -1,6 +1,6 @@ name: mocktail description: A Dart mocking library which simplifies mocking with null safety support and no manual mocks or code generation. -version: 0.0.2-dev.2 +version: 0.0.2-dev.3 repository: https://github.com/felangel/mocktail homepage: https://github.com/felangel/mocktail/tree/main/packages/mocktail diff --git a/packages/mocktail/test/mockito_compat/nnbd_support_test.dart b/packages/mocktail/test/mockito_compat/nnbd_support_test.dart new file mode 100644 index 0000000..613b84e --- /dev/null +++ b/packages/mocktail/test/mockito_compat/nnbd_support_test.dart @@ -0,0 +1,54 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class Foo { + String? returnsNullableString() => 'Hello'; + + String returnsNonNullableString() => 'Hello'; +} + +class MockFoo extends Mock implements Foo {} + +void main() { + late MockFoo mock; + + setUp(() { + mock = MockFoo(); + }); + + tearDown(resetMocktailState); + + group('Using nSM out of the box,', () { + test('nSM returns the dummy value during method stubbing', () { + // Trigger method stubbing. + final whenCall = when; + final stubbedResponse = mock.returnsNullableString(); + expect(stubbedResponse, equals(null)); + whenCall(() => stubbedResponse).thenReturn('A'); + }); + + test('nSM returns the dummy value during method call verification', () { + when(() => mock.returnsNullableString()).thenReturn('A'); + + // Make a real call. + final realResponse = mock.returnsNullableString(); + expect(realResponse, equals('A')); + + // Trigger method call verification. + final verifyCall = verify; + final verificationResponse = mock.returnsNullableString(); + expect(verificationResponse, equals(null)); + verifyCall(() => verificationResponse); + }); + + test( + 'nSM returns the dummy value during method call verification, using ' + 'verifyNever', () { + // Trigger method call verification. + final verifyNeverCall = verifyNever; + final verificationResponse = mock.returnsNullableString(); + expect(verificationResponse, equals(null)); + verifyNeverCall(() => verificationResponse); + }); + }); +} diff --git a/packages/mocktail/test/mockito_compat/verify_test.dart b/packages/mocktail/test/mockito_compat/verify_test.dart index e8c7970..76fbe4d 100644 --- a/packages/mocktail/test/mockito_compat/verify_test.dart +++ b/packages/mocktail/test/mockito_compat/verify_test.dart @@ -13,6 +13,7 @@ class _RealClass { String? methodWithNamedArgs(int x, {int? y}) => 'Real'; String? methodWithOnlyNamedArgs({int? y = 0, int? z}) => 'Real'; String? get getter => 'Real'; + String get nsGetter => 'Real'; set setter(String arg) { throw StateError('I must be mocked'); } @@ -436,7 +437,15 @@ void main() { mock ..methodWithoutArgs() ..getter; - verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + verifyInOrder(() => [mock.methodWithoutArgs(), mock.getter]); + }); + + test('right order passes (ns)', () { + when(() => mock.nsGetter).thenReturn('mock'); + mock + ..methodWithoutArgs() + ..nsGetter; + verifyInOrder(() => [mock.methodWithoutArgs(), mock.nsGetter]); }); test('wrong order fails', () { @@ -446,7 +455,7 @@ void main() { expectFail( 'Matching call #1 not found. All calls: ' '_MockedClass.methodWithoutArgs(), _MockedClass.getter', () { - verifyInOrder([mock.getter, mock.methodWithoutArgs()]); + verifyInOrder(() => [mock.getter, mock.methodWithoutArgs()]); }); }); @@ -457,10 +466,10 @@ void main() { expectFail( 'There is already a verification in progress, ' 'check if it was not called with a verify argument(s)', () { - verifyInOrder([ - verify(() => mock.getter), - verify(() => mock.methodWithoutArgs()), - ]); + verifyInOrder(() => [ + verify(() => mock.getter), + verify(() => mock.methodWithoutArgs()), + ]); }); }); @@ -469,7 +478,7 @@ void main() { expectFail( 'Matching call #1 not found. All calls: ' '_MockedClass.methodWithoutArgs()', () { - verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + verifyInOrder(() => [mock.methodWithoutArgs(), mock.getter]); }); }); @@ -478,7 +487,7 @@ void main() { ..methodWithoutArgs() ..getter ..methodWithoutArgs(); - verifyInOrder( + verifyInOrder(() => [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); }); @@ -487,11 +496,11 @@ void main() { ..methodWithNormalArgs(1) ..methodWithoutArgs() ..methodWithNormalArgs(2); - final captured = verifyInOrder([ - mock.methodWithNormalArgs(captureAny()), - mock.methodWithoutArgs(), - mock.methodWithNormalArgs(captureAny()) - ]).captured; + final captured = verifyInOrder(() => [ + mock.methodWithNormalArgs(captureAny()), + mock.methodWithoutArgs(), + mock.methodWithNormalArgs(captureAny()) + ]).captured; expect(captured, hasLength(3)); expect(captured[0], equals([1])); expect(captured[1], equals([])); @@ -508,7 +517,8 @@ void main() { '_MockedClass.methodWithoutArgs(), _MockedClass.methodWithoutArgs(), ' '_MockedClass.getter', () { verifyInOrder( - [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()], + () => + [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()], ); }); });