From 0402d6817a35e50761e877c90a8ba8b65084e83a Mon Sep 17 00:00:00 2001 From: fibulwinter Date: Mon, 14 Jul 2014 20:15:33 +1000 Subject: [PATCH 001/607] Initial commit --- pkgs/mockito/.gitignore | 13 +++++++++++++ pkgs/mockito/LICENSE | 21 +++++++++++++++++++++ pkgs/mockito/README.md | 4 ++++ 3 files changed, 38 insertions(+) create mode 100644 pkgs/mockito/.gitignore create mode 100644 pkgs/mockito/LICENSE create mode 100644 pkgs/mockito/README.md diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore new file mode 100644 index 000000000..74a3439f8 --- /dev/null +++ b/pkgs/mockito/.gitignore @@ -0,0 +1,13 @@ +# Don’t commit the following directories created by pub. +build/ +packages/ + +# Or the files created by dart2js. +*.dart.js +*.dart.precompiled.js +*.js_ +*.js.deps +*.js.map + +# Include when developing application packages. +pubspec.lock diff --git a/pkgs/mockito/LICENSE b/pkgs/mockito/LICENSE new file mode 100644 index 000000000..f351d4bf2 --- /dev/null +++ b/pkgs/mockito/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 fibulwinter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md new file mode 100644 index 000000000..651349e21 --- /dev/null +++ b/pkgs/mockito/README.md @@ -0,0 +1,4 @@ +dart-mockito +============ + +Mockito-inspired mock library for Dart From 63c00422dc39dd55cdf1b8aa479904fb6d78d08f Mon Sep 17 00:00:00 2001 From: fibulwinter Date: Mon, 14 Jul 2014 21:32:48 +1000 Subject: [PATCH 002/607] Update README.md --- pkgs/mockito/README.md | 125 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 651349e21..9d960d03e 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,4 +1,127 @@ dart-mockito ============ -Mockito-inspired mock library for Dart +Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). + +Current mock libraries suffer from specifing method names as strings, which cause a lot of problems: + * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. + * Poor support from IDE: no code-completion, no hints on argument types, can't jump to definition + +Dart-mockito fixes it - stubbing and verifing are first-class citisens. + +## Let's create mocks +```dart + //Real class + class Cat { + String sound() => "Meow"; + bool eatFood(String food, {bool hungry}) => true; + void sleep(){} + int lives = 9; + } + + //Mock class + class MockCat extends Mock implements Cat{} + + //mock creation + var cat = new MockCat(); +``` + +## Let's verify some behaviour! +```dart + //using mock object + cat.sound(); + //verify interaction + verify(cat.sound()); +``` +Once created, mock will remember all interactions. Then you can selectively verify whatever interaction you are interested in. + +## How about some stubbing? +```dart + //unstubbed methods return null + expect(cat.sound(), nullValue); + //stubbing - before execution + when(cat.sound()).thenReturn("Purr"); + expect(cat.sound(), "Purr"); + //you can call it again + expect(cat.sound(), "Purr"); + //let's change stub + when(cat.sound()).thenReturn("Meow"); + expect(cat.sound(), "Meow"); + //you can stub getters + when(cat.lives).thenReturn(9); + expect(cat.lives, 9); +``` + +By default, for all methods that return value, mock returns null. +Stubbing can be overridden: for example common stubbing can go to fixture setup but the test methods can override it. Please note that overridding stubbing is a potential code smell that points out too much stubbing. +Once stubbed, the method will always return stubbed value regardless of how many times it is called. +Last stubbing is more important - when you stubbed the same method with the same arguments many times. Other words: the order of stubbing matters but it is only meaningful rarely, e.g. when stubbing exactly the same method calls or sometimes when argument matchers are used, etc. + +## Argument matchers +```dart + //you can use arguments itself... + when(cat.eatFood("fish")).thenReturn(true); + //..or matchers + when(cat.eatFood(argThat(startsWith("dry"))).thenReturn(false); + //..or mix aguments with matchers + when(cat.eatFood(argThat(startsWith("dry")), true).thenReturn(true); + expect(cat.eatFood("fish"), isTrue); + expect(cat.eatFood("dry food"), isFalse); + expect(cat.eatFood("dry food", hungry: true), isTrue); + //you can also verify using an argument matcher + verify(cat.eatFood("fish")); + verify(cat.eatFood(argThat(contains("food")))); + //you can verify setters + cat.lives = 9; + verify(cat.lives=9); +``` +Argument matchers allow flexible verification or stubbing + +## Verifying exact number of invocations / at least x / never +```dart + cat.sound(); + cat.sound(); + //exact number of invocations + verify(cat.sound()).called(2); + //or using matcher + verify(cat.sound()).called(greaterThan(1)); + //or never called + verifyNever(cat.eatFood(any)); +``` +## Verification in order +```dart + cat.eatFood("Milk"); + cat.sound(); + cat.eatFood("Fish"); + verifyInOrder([ + cat.eatFood("Milk"), + cat.sound(), + cat.eatFood("Fish") + ]); +``` +Verification in order is flexible - you don't have to verify all interactions one-by-one but only those that you are interested in testing in order. + +## Making sure interaction(s) never happened on mock +```dart + verifyZeroInteractions(cat); +``` +## Finding redundant invocations +```dart + cat.sound(); + verify(cat.sound()); + verifyNoMoreInteractions(cat); +``` +## Capturing arguments for further assertions +```dart + //simple capture + cat.eatFood("Fish"); + expect(verify(cat.eatFood(capture)).captured.single, "Fish"); + //capture multiple calls + cat.eatFood("Milk"); + cat.eatFood("Fish"); + expect(verify(cat.eatFood(capture)).captured, ["Milk", "Fish"]); + //conditional capture + cat.eatFood("Milk"); + cat.eatFood("Fish"); + expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); +``` From 9227ac3ab03f5a0770297443433703aa0ef06059 Mon Sep 17 00:00:00 2001 From: deimon Date: Mon, 14 Jul 2014 21:45:45 +1000 Subject: [PATCH 003/607] Upload to GitHub --- pkgs/mockito/lib/mockito.dart | 369 +++++++++++++++++++++++++++++ pkgs/mockito/pubspec.yaml | 12 + pkgs/mockito/test/mockitoSpec.dart | 354 +++++++++++++++++++++++++++ pkgs/mockito/test/packages | 1 + 4 files changed, 736 insertions(+) create mode 100644 pkgs/mockito/lib/mockito.dart create mode 100644 pkgs/mockito/pubspec.yaml create mode 100644 pkgs/mockito/test/mockitoSpec.dart create mode 120000 pkgs/mockito/test/packages diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart new file mode 100644 index 000000000..784af9b80 --- /dev/null +++ b/pkgs/mockito/lib/mockito.dart @@ -0,0 +1,369 @@ +library mockito; + +import 'package:matcher/matcher.dart'; +import 'dart:mirrors'; + +bool _whenInProgress = false; +bool _verificationInProgress = false; +_WhenCall _whenCall=null; +List<_VerifyCall> _verifyCalls = []; +_TimeStampProvider _timer = new _TimeStampProvider(); +List _capturedArgs = []; + +class Mock { + List realCalls = []; + List _responses = []; + String _givenName = null; + int _givenHashCode = null; + var _defaultResponse = ()=>new CannedResponse(null, (_)=>null); + + void _setExpected(CannedResponse cannedResponse){ + _responses.add(cannedResponse); + } + + dynamic noSuchMethod(Invocation invocation){ + if(_whenInProgress){ + _whenCall = new _WhenCall(this, invocation); + return null; + }else if(_verificationInProgress){ + _verifyCalls.add(new _VerifyCall(this, invocation)); + return null; + }else{ + realCalls.add(new RealCall(this, invocation)); + var cannedResponse = _responses.lastWhere((cr)=>cr.matcher.matches(invocation), + orElse: _defaultResponse); + var response = cannedResponse.response(invocation); + return response; + } + } + + int get hashCode => _givenHashCode==null ? super.hashCode : _givenHashCode; + + bool operator ==(other) => (_givenHashCode!=null && other is Mock) + ? _givenHashCode==other._givenHashCode : super==other; + + String toString() => _givenName != null ? _givenName : runtimeType.toString(); +} + +named(dynamic mock, {String name, int hashCode}) => mock + .._givenName=name.._givenHashCode = hashCode; + +reset(var mock){ + mock.realCalls.clear(); + mock._responses.clear(); +} + +clearInteractions(var mock){ + mock.realCalls.clear(); +} + +class PostExpectation { + thenReturn(expected){ + return _completeWhen((_)=>expected); + } + + thenAnswer(Answering answer){ + return _completeWhen(answer); + } + + _completeWhen(Answering answer) { + _whenCall._setExpected(answer); + var mock = _whenCall.mock; + _whenCall = null; + _whenInProgress=false; + return mock; + } +} + +class InvocationMatcher { + Invocation roleInvocation; + + InvocationMatcher(this.roleInvocation); + + bool matches(Invocation invocation){ + var isMatching = _isMethodMatches(invocation) && _isArgumentsMatches(invocation); + if(isMatching){ + _captureArguments(invocation); + } + return isMatching; + } + + bool _isMethodMatches(Invocation invocation) { + if(invocation.memberName!=roleInvocation.memberName){ + return false; + } + if((invocation.isGetter!=roleInvocation.isGetter) + || (invocation.isSetter!=roleInvocation.isSetter) + || (invocation.isMethod!=roleInvocation.isMethod) + ){ + return false; + } + return true; + } + + void _captureArguments(Invocation invocation) { + int index = 0; + for(var roleArg in roleInvocation.positionalArguments){ + var actArg = invocation.positionalArguments[index]; + if(roleArg is _ArgMatcher && roleArg._capture){ + _capturedArgs.add(actArg); + } + index++; + } + for(var roleKey in roleInvocation.namedArguments.keys){ + var roleArg = roleInvocation.namedArguments[roleKey]; + var actArg = invocation.namedArguments[roleKey]; + if(roleArg is _ArgMatcher){ + if(roleArg is _ArgMatcher && roleArg._capture){ + _capturedArgs.add(actArg); + } + } + } + } + + bool _isArgumentsMatches(Invocation invocation) { + if(invocation.positionalArguments.length!=roleInvocation.positionalArguments.length){ + return false; + } + if(invocation.namedArguments.length!=roleInvocation.namedArguments.length){ + return false; + } + int index=0; + for(var roleArg in roleInvocation.positionalArguments){ + var actArg = invocation.positionalArguments[index]; + if(!isMatchingArg(roleArg, actArg)){ + return false; + } + index++; + } + for(var roleKey in roleInvocation.namedArguments.keys){ + var roleArg = roleInvocation.namedArguments[roleKey]; + var actArg = invocation.namedArguments[roleKey]; + if(!isMatchingArg(roleArg, actArg)){ + return false; + } + } + return true; + } + + bool isMatchingArg(roleArg, actArg) { + if(roleArg is _ArgMatcher){ + return roleArg._matcher==null || roleArg._matcher.matches(actArg, null); +// } else if(roleArg is Mock){ +// return identical(roleArg, actArg); + }else{ + return roleArg == actArg; + } + } +} + +class CannedResponse{ + InvocationMatcher matcher; + Answering response; + + CannedResponse(this.matcher, this.response); +} + +class _TimeStampProvider{ + int _now = 0; + DateTime now(){ + var candidate = new DateTime.now(); + if(candidate.millisecondsSinceEpoch<=_now){ + candidate = new DateTime.fromMillisecondsSinceEpoch(_now+1); + } + _now = candidate.millisecondsSinceEpoch; + return candidate; + } +} + +class RealCall{ + DateTime _timeStamp; + final Mock mock; + final Invocation invocation; + bool verified = false; + RealCall(this.mock, this.invocation){ + _timeStamp = _timer.now(); + } + + DateTime get timeStamp => _timeStamp; + + String toString(){ + var verifiedText = verified ? "[VERIFIED] " : ""; + List posArgs = invocation.positionalArguments.map((v)=>v==null?"null":v.toString()).toList(); + List mapArgList = invocation.namedArguments.keys.map((key){ + return "${MirrorSystem.getName(key)}: ${invocation.namedArguments[key]}"; + }).toList(growable: false); + if(mapArgList.isNotEmpty){ + posArgs.add("{${mapArgList.join(", ")}}"); + } + String args = posArgs.join(", "); + String method = MirrorSystem.getName(invocation.memberName); + if(invocation.isMethod){ + method = ".$method($args)"; + }else if(invocation.isGetter){ + method = ".$method"; + }else{ + method = ".$method=$args"; + } + return "$verifiedText$mock$method"; + } +} + +class _WhenCall { + Mock mock; + Invocation whenInvocation; + _WhenCall(this.mock, this.whenInvocation); + + void _setExpected(Answering answer){ + mock._setExpected(new CannedResponse(new InvocationMatcher(whenInvocation), answer)); + } +} + +class _VerifyCall { + Mock mock; + Invocation verifyInvocation; + List matchingInvocations; + + _VerifyCall(this.mock, this.verifyInvocation){ + var expectedMatcher = new InvocationMatcher(verifyInvocation); + matchingInvocations = mock.realCalls.where((RealCall recordedInvocation){ + return !recordedInvocation.verified && expectedMatcher.matches(recordedInvocation.invocation);}).toList(); + } + + _findAfter(DateTime dt){ + return matchingInvocations.firstWhere((inv)=>!inv.verified && inv.timeStamp.isAfter(dt), orElse: ()=>null); + } + + _checkWith(bool never){ + if(!never && matchingInvocations.isEmpty){ + var otherCallsText = ""; + if(mock.realCalls.isNotEmpty){ + otherCallsText = " All calls: "; + } + var calls = mock.realCalls.join(", "); + fail("No matching calls.$otherCallsText$calls"); + } + if(never && matchingInvocations.isNotEmpty){ + var calls = mock.realCalls.join(", "); + fail("Unexpected calls. All calls: $calls"); + } + matchingInvocations.forEach((inv){inv.verified=true;}); + } +} + +class _ArgMatcher{ + Matcher _matcher; + bool _capture; + + _ArgMatcher(this._matcher, this._capture); +} + + +get any => new _ArgMatcher(null, false); +get captureAny => new _ArgMatcher(null, true); +captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); +argThat(Matcher matcher) => new _ArgMatcher(matcher, false); + +class VerificationResult{ + List captured = []; + int callCount; + + VerificationResult(this.callCount){ + captured = new List.from(_capturedArgs, growable: false); + _capturedArgs.clear(); + } + + void called(matcher){ + expect(callCount, wrapMatcher(matcher), reason: "Unexpected number of calls"); + } +} + +typedef dynamic Answering(Invocation realInvocation); + +typedef VerificationResult Verification(matchingInvocations); + +typedef void InOrderVerification(recordedInvocations); + +Verification get verifyNever => _makeVerify(true); + +Verification get verify => _makeVerify(false); + +Verification _makeVerify(bool never) { + if(_verifyCalls.isNotEmpty){ + throw new StateError(_verifyCalls.join()); + } + _verificationInProgress = true; + return (mock){ + _verificationInProgress = false; + if(_verifyCalls.length==1){ + _VerifyCall verifyCall = _verifyCalls.removeLast(); + var result = new VerificationResult(verifyCall.matchingInvocations.length); + verifyCall._checkWith(never); + return result; + }else{ + fail("Used on non-mockito"); + } + }; +} + +InOrderVerification get verifyInOrder { + if(_verifyCalls.isNotEmpty){ + throw new StateError(_verifyCalls.join()); + } + _verificationInProgress = true; + return (verifyCalls){ + _verificationInProgress = false; + DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); + var tmpVerifyCalls = new List.from(_verifyCalls); + _verifyCalls.clear(); + List matchedCalls = []; + for(_VerifyCall verifyCall in tmpVerifyCalls){ + RealCall matched = verifyCall._findAfter(dt); + if(matched!=null){ + matchedCalls.add(matched); + dt=matched.timeStamp; + }else{ + Set mocks = tmpVerifyCalls.map((_VerifyCall vc)=>vc.mock).toSet(); + List allInvocations = mocks.expand((m)=>m.realCalls).toList(growable: false); + allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); + String otherCalls = ""; + if(allInvocations.isNotEmpty){ + otherCalls = " All calls: ${allInvocations.join(", ")}"; + } + fail("Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); + } + } + matchedCalls.forEach((rc){rc.verified=true;}); + }; +} + +verifyNoMoreInteractions(var mock) { + var unverified = mock.realCalls.where((inv)=>!inv.verified).toList(); + if(unverified.isNotEmpty){ + fail("No more calls expected, but following found: "+unverified.join()); + } +} + +verifyZeroInteractions(var mock) { + if(mock.realCalls.isNotEmpty){ + fail("No interaction expected, but following found: "+mock.realCalls.join()); + } +} + +typedef PostExpectation Expectation(x); + +Expectation get when { + _whenInProgress = true; + return (_){ + _whenInProgress = false; + return new PostExpectation(); + }; +} + +logInvocations(List mocks){ + List allInvocations = mocks.expand((m)=>m.realCalls).toList(growable: false); + allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); + allInvocations.forEach((inv){ + print(inv.toString()); + }); +} diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml new file mode 100644 index 000000000..d96dc5e9f --- /dev/null +++ b/pkgs/mockito/pubspec.yaml @@ -0,0 +1,12 @@ +name: dart-mockito +version: 0.8.0 +author: Dmitriy Fibulwinter +homepage: https://github.com/fibulwinter/dart-mockito +description: > + A mock framework inspired by Mockito. +dependencies: + matcher: ">=0.10.0 <1.0.0" +dev_dependencies: + unittest: ">=0.11.0 <1.0.0" +environment: + sdk: ">=1.0.0 <2.0.0" \ No newline at end of file diff --git a/pkgs/mockito/test/mockitoSpec.dart b/pkgs/mockito/test/mockitoSpec.dart new file mode 100644 index 000000000..8c2bc83b8 --- /dev/null +++ b/pkgs/mockito/test/mockitoSpec.dart @@ -0,0 +1,354 @@ +import 'package:unittest/unittest.dart'; +import '../lib/mockito.dart'; + +class RealClass { + String methodWithoutArgs() => "Real"; + String methodWithNormalArgs(int x) => "Real"; + String methodWithPositionalArgs(int x, [int y]) => "Real"; + String methodWithNamedArgs(int x, {int y}) => "Real"; + String get getter => "Real"; + void set setter(String arg){ + throw new StateError("I must be mocked"); + } +} + +class MockedClass extends Mock implements RealClass{} + +expectFail(String expectedMessage, expectedToFail()){ + try{ + expectedToFail(); + fail("It was expected to fail!"); + }catch(e){ + if(!(e is TestFailure)){ + throw e; + }else{ + if(expectedMessage!=e.message){ + throw new TestFailure("Failed, but with wrong message: ${e.message}"); + } + } + } +} + +main(){ + RealClass mock; + + setUp((){ + mock = new MockedClass(); + }); + + group("when()", (){ + test("should mock method without args", (){ + when(mock.methodWithoutArgs()).thenReturn("A"); + expect(mock.methodWithoutArgs(), equals("A")); + }); + test("should mock method with normal args", (){ + when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); + expect(mock.methodWithNormalArgs(43), isNull); + expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); + }); + test("should mock method with positional args", (){ + when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); + expect(mock.methodWithPositionalArgs(42), isNull); + expect(mock.methodWithPositionalArgs(42, 18), isNull); + expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); + }); + test("should mock method with named args", (){ + when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); + expect(mock.methodWithNamedArgs(42), isNull); + expect(mock.methodWithNamedArgs(42, y:18), isNull); + expect(mock.methodWithNamedArgs(42, y:17), equals("Why answer?")); + }); + test("should mock method with argument matcher", (){ + when(mock.methodWithNormalArgs(argThat(greaterThan(100)))).thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), isNull); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); + }); + test("should mock method with any argument matcher", (){ + when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); + }); + test("should mock method with mix of argument matchers and real things", (){ + when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)).thenReturn("A lot with 17"); + expect(mock.methodWithPositionalArgs(100, 17), isNull); + expect(mock.methodWithPositionalArgs(101, 18), isNull); + expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); + }); + test("should mock getter", (){ + when(mock.getter).thenReturn("A"); + expect(mock.getter, equals("A")); + }); + test("should mock hashCode", (){ + named(mock, hashCode: 42); + expect(mock.hashCode, equals(42)); + }); + test("should have hashCode when it is not mocked", (){ + expect(mock.hashCode, isNotNull); + }); +// test("should n't mock toString", (){ +// when(mock.toString()).thenReturn("meow"); +// expect(mock.toString(), equals("meow")); +// }); + test("should have default toString when it is not mocked", (){ + expect(mock.toString(), equals("MockedClass")); + }); + test("should have toString as name when it is not mocked", (){ + named(mock, name: "Cat"); + expect(mock.toString(), equals("Cat")); + }); + test("should mock equals between mocks when givenHashCode is equals", (){ + var anotherMock = named(new MockedClass(), hashCode: 42); + named(mock, hashCode: 42); + expect(mock==anotherMock, isTrue); + }); + test("should use identical equality between it is not mocked", (){ + var anotherMock = new MockedClass(); + expect(mock==anotherMock, isFalse); + expect(mock==mock, isTrue); + }); + //no need to mock setter, except if we will have spies later... + test("should mock method with calculated result", (){ + when(mock.methodWithNormalArgs(any)).thenAnswer((Invocation inv)=>inv.positionalArguments[0].toString()); + expect(mock.methodWithNormalArgs(43), equals("43")); + expect(mock.methodWithNormalArgs(42), equals("42")); + }); + test("should return mock to make simple oneline mocks", (){ + RealClass mockWithSetup = when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); + expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); + }); + test("should use latest matching when definition", (){ + when(mock.methodWithoutArgs()).thenReturn("A"); + when(mock.methodWithoutArgs()).thenReturn("B"); + expect(mock.methodWithoutArgs(), equals("B")); + }); + + }); + + group("verify()", (){ + test("should verify method without args", (){ + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + test("should verify method with normal args", (){ + mock.methodWithNormalArgs(42); + expectFail("No matching calls. All calls: MockedClass.methodWithNormalArgs(42)", (){ + verify(mock.methodWithNormalArgs(43)); + }); + verify(mock.methodWithNormalArgs(42)); + }); + test("should mock method with positional args", (){ + mock.methodWithPositionalArgs(42, 17); + expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", (){ + verify(mock.methodWithPositionalArgs(42)); + }); + expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", (){ + verify(mock.methodWithPositionalArgs(42, 18)); + }); + verify(mock.methodWithPositionalArgs(42, 17)); + }); + test("should mock method with named args", (){ + mock.methodWithNamedArgs(42, y: 17); + expectFail("No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", (){ + verify(mock.methodWithNamedArgs(42)); + }); + expectFail("No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", (){ + verify(mock.methodWithNamedArgs(42, y:18)); + }); + verify(mock.methodWithNamedArgs(42, y:17)); + }); + test("should mock method with argument matcher", (){ + mock.methodWithNormalArgs(100); + expectFail("No matching calls. All calls: MockedClass.methodWithNormalArgs(100)", (){ + verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); + }); + verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100)))); + }); + test("should mock method with argument capturer", (){ + mock.methodWithNormalArgs(50); + mock.methodWithNormalArgs(100); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([50, 100])); + }); + test("should mock method with argument matcher and capturer", (){ + mock.methodWithNormalArgs(50); + mock.methodWithNormalArgs(100); + var captured = 0; + expect(verify(mock.methodWithNormalArgs(captureThat(greaterThan(75)))).captured.single, equals(100)); + expect(verify(mock.methodWithNormalArgs(captureThat(lessThan(75)))).captured.single, equals(50)); + }); + test("should mock method with mix of argument matchers and real things", (){ + mock.methodWithPositionalArgs(100, 17); + expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", (){ + verify(mock.methodWithPositionalArgs(argThat(greaterThanOrEqualTo(100)), 18)); + }); + expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", (){ + verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); + }); + verify(mock.methodWithPositionalArgs(argThat(greaterThanOrEqualTo(100)), 17)); + }); + test("should mock getter", (){ + mock.getter; + verify(mock.getter); + }); + test("should mock setter", (){ + mock.setter = "A"; + expectFail("No matching calls. All calls: MockedClass.setter==A", (){ + verify(mock.setter="B"); + }); + verify(mock.setter="A"); + }); + }); + group("verify() qualifies", (){ + group("unqualified as at least one", (){ + test("zero fails", (){ + expectFail("No matching calls.", (){ + verify(mock.methodWithoutArgs()); + }); + }); + test("one passes", (){ + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + test("more than one passes", (){ + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + }); + group("counts calls", (){ + test("zero fails", (){ + expectFail("No matching calls.", (){ + verify(mock.methodWithoutArgs()).called(1); + }); + }); + test("one passes", (){ + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()).called(1); + }); + test("more than one fails", (){ + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", (){ + verify(mock.methodWithoutArgs()).called(1); + }); + }); + }); + group("verifyNever", (){ + test("zero passes", (){ + verifyNever(mock.methodWithoutArgs()); + }); + test("one fails", (){ + mock.methodWithoutArgs(); + expectFail("Unexpected calls. All calls: MockedClass.methodWithoutArgs()", (){ + verifyNever(mock.methodWithoutArgs()); + }); + }); + }); + group("doesn't count already verified again", (){ + test("fail case", (){ + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + expectFail("No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()", (){ + verify(mock.methodWithoutArgs()); + }); + }); + test("pass case", (){ + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + }); + }); + + group("verifyZeroInteractions()", (){ + test("never touched pass", (){ + verifyZeroInteractions(mock); + }); + test("any touch fails", (){ + mock.methodWithoutArgs(); + expectFail("No interaction expected, but following found: MockedClass.methodWithoutArgs()", (){ + verifyZeroInteractions(mock); + }); + }); + test("verifired call fails", (){ + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + expectFail("No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", (){ + verifyZeroInteractions(mock); + }); + }); + }); + group("verifyNoMoreInteractions()", (){ + test("never touched pass", (){ + verifyNoMoreInteractions(mock); + }); + test("any unverified touch fails", (){ + mock.methodWithoutArgs(); + expectFail("No more calls expected, but following found: MockedClass.methodWithoutArgs()", (){ + verifyNoMoreInteractions(mock); + }); + }); + test("verified touch passes", (){ + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + verifyNoMoreInteractions(mock); + }); + }); + group("verifyInOrder()", (){ + test("right order passes", (){ + mock.methodWithoutArgs(); + mock.getter; + verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + }); + test("wrong order fails", (){ + mock.methodWithoutArgs(); + mock.getter; + expectFail("Matching call #1 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.getter", (){ + verifyInOrder([mock.getter, mock.methodWithoutArgs()]); + }); + }); + test("uncomplete fails", (){ + mock.methodWithoutArgs(); + expectFail("Matching call #1 not found. All calls: MockedClass.methodWithoutArgs()", (){ + verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + }); + }); + test("methods can be called again and again", (){ + mock.methodWithoutArgs(); + mock.getter; + mock.methodWithoutArgs(); + verifyInOrder([mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + }); + test("methods can be called again and again - fail case", (){ + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + mock.getter; + expectFail("Matching call #2 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), MockedClass.getter", (){ + verifyInOrder([mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + }); + }); + }); + + group("capture", (){ + test("capture should work as captureOut", (){ + mock.methodWithNormalArgs(42); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, equals(42)); + }); + test("should captureOut multiple arguments", (){ + mock.methodWithPositionalArgs(1, 2); + expect(verify(mock.methodWithPositionalArgs(captureAny, captureAny)).captured, equals([1, 2])); + }); + test("should captureOut with matching arguments", (){ + mock.methodWithPositionalArgs(1); + mock.methodWithPositionalArgs(2, 3); + expect(verify(mock.methodWithPositionalArgs(captureAny, captureAny)).captured, equals([2, 3])); + }); + test("should captureOut multiple invocations", (){ + mock.methodWithNormalArgs(1); + mock.methodWithNormalArgs(2); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([1, 2])); + }); + }); + +} + diff --git a/pkgs/mockito/test/packages b/pkgs/mockito/test/packages new file mode 120000 index 000000000..a16c40501 --- /dev/null +++ b/pkgs/mockito/test/packages @@ -0,0 +1 @@ +../packages \ No newline at end of file From b384ad55d77b9b5c6923b2d139a223c479482312 Mon Sep 17 00:00:00 2001 From: deimon Date: Mon, 14 Jul 2014 21:57:13 +1000 Subject: [PATCH 004/607] Changed package name to mockito --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index d96dc5e9f..4c4603ebc 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,4 +1,4 @@ -name: dart-mockito +name: mockito version: 0.8.0 author: Dmitriy Fibulwinter homepage: https://github.com/fibulwinter/dart-mockito From 3184dab8d308504c9534d50941c37cab50b656be Mon Sep 17 00:00:00 2001 From: deimon Date: Sat, 19 Jul 2014 06:46:26 +1000 Subject: [PATCH 005/607] Mockito.realCalls is private. https://github.com/fibulwinter/dart-mockito/issues/1 --- pkgs/mockito/README.md | 2 ++ pkgs/mockito/lib/mockito.dart | 26 +++++++++++++------------- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 9d960d03e..a5c65eeb7 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -11,6 +11,8 @@ Dart-mockito fixes it - stubbing and verifing are first-class citisens. ## Let's create mocks ```dart + import 'package:mockito/mockito.dart'; + //Real class class Cat { String sound() => "Meow"; diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 784af9b80..7a8ad0fa1 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -11,7 +11,7 @@ _TimeStampProvider _timer = new _TimeStampProvider(); List _capturedArgs = []; class Mock { - List realCalls = []; + List _realCalls = []; List _responses = []; String _givenName = null; int _givenHashCode = null; @@ -29,7 +29,7 @@ class Mock { _verifyCalls.add(new _VerifyCall(this, invocation)); return null; }else{ - realCalls.add(new RealCall(this, invocation)); + _realCalls.add(new RealCall(this, invocation)); var cannedResponse = _responses.lastWhere((cr)=>cr.matcher.matches(invocation), orElse: _defaultResponse); var response = cannedResponse.response(invocation); @@ -49,12 +49,12 @@ named(dynamic mock, {String name, int hashCode}) => mock .._givenName=name.._givenHashCode = hashCode; reset(var mock){ - mock.realCalls.clear(); + mock._realCalls.clear(); mock._responses.clear(); } clearInteractions(var mock){ - mock.realCalls.clear(); + mock._realCalls.clear(); } class PostExpectation { @@ -226,7 +226,7 @@ class _VerifyCall { _VerifyCall(this.mock, this.verifyInvocation){ var expectedMatcher = new InvocationMatcher(verifyInvocation); - matchingInvocations = mock.realCalls.where((RealCall recordedInvocation){ + matchingInvocations = mock._realCalls.where((RealCall recordedInvocation){ return !recordedInvocation.verified && expectedMatcher.matches(recordedInvocation.invocation);}).toList(); } @@ -237,14 +237,14 @@ class _VerifyCall { _checkWith(bool never){ if(!never && matchingInvocations.isEmpty){ var otherCallsText = ""; - if(mock.realCalls.isNotEmpty){ + if(mock._realCalls.isNotEmpty){ otherCallsText = " All calls: "; } - var calls = mock.realCalls.join(", "); + var calls = mock._realCalls.join(", "); fail("No matching calls.$otherCallsText$calls"); } if(never && matchingInvocations.isNotEmpty){ - var calls = mock.realCalls.join(", "); + var calls = mock._realCalls.join(", "); fail("Unexpected calls. All calls: $calls"); } matchingInvocations.forEach((inv){inv.verified=true;}); @@ -324,7 +324,7 @@ InOrderVerification get verifyInOrder { dt=matched.timeStamp; }else{ Set mocks = tmpVerifyCalls.map((_VerifyCall vc)=>vc.mock).toSet(); - List allInvocations = mocks.expand((m)=>m.realCalls).toList(growable: false); + List allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); String otherCalls = ""; if(allInvocations.isNotEmpty){ @@ -338,15 +338,15 @@ InOrderVerification get verifyInOrder { } verifyNoMoreInteractions(var mock) { - var unverified = mock.realCalls.where((inv)=>!inv.verified).toList(); + var unverified = mock._realCalls.where((inv)=>!inv.verified).toList(); if(unverified.isNotEmpty){ fail("No more calls expected, but following found: "+unverified.join()); } } verifyZeroInteractions(var mock) { - if(mock.realCalls.isNotEmpty){ - fail("No interaction expected, but following found: "+mock.realCalls.join()); + if(mock._realCalls.isNotEmpty){ + fail("No interaction expected, but following found: "+mock._realCalls.join()); } } @@ -361,7 +361,7 @@ Expectation get when { } logInvocations(List mocks){ - List allInvocations = mocks.expand((m)=>m.realCalls).toList(growable: false); + List allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); allInvocations.forEach((inv){ print(inv.toString()); diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 4c4603ebc..df0b7f333 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 0.8.0 +version: 0.8.1 author: Dmitriy Fibulwinter homepage: https://github.com/fibulwinter/dart-mockito description: > From 5acb76dee9d1a92884589898ead5fc8dee151f50 Mon Sep 17 00:00:00 2001 From: deimon Date: Thu, 29 Jan 2015 21:21:52 +1100 Subject: [PATCH 006/607] Removed references to super which prevent use Mock as mixin. https://github.com/fibulwinter/dart-mockito/issues/2 --- pkgs/mockito/lib/mockito.dart | 4 ++-- pkgs/mockito/pubspec.yaml | 13 ++++++------- pkgs/mockito/test/mockitoSpec.dart | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 7a8ad0fa1..172babeb8 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -37,10 +37,10 @@ class Mock { } } - int get hashCode => _givenHashCode==null ? super.hashCode : _givenHashCode; + int get hashCode => _givenHashCode==null ? 0 : _givenHashCode; bool operator ==(other) => (_givenHashCode!=null && other is Mock) - ? _givenHashCode==other._givenHashCode : super==other; + ? _givenHashCode==other._givenHashCode : identical(this, other); String toString() => _givenName != null ? _givenName : runtimeType.toString(); } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index df0b7f333..c5845a4e8 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,12 +1,11 @@ name: mockito -version: 0.8.1 +version: 0.8.2 author: Dmitriy Fibulwinter +description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito -description: > - A mock framework inspired by Mockito. +environment: + sdk: '>=1.0.0 <2.0.0' dependencies: - matcher: ">=0.10.0 <1.0.0" + matcher: '>=0.10.0 <1.0.0' dev_dependencies: - unittest: ">=0.11.0 <1.0.0" -environment: - sdk: ">=1.0.0 <2.0.0" \ No newline at end of file + unittest: '>=0.11.0 <1.0.0' diff --git a/pkgs/mockito/test/mockitoSpec.dart b/pkgs/mockito/test/mockitoSpec.dart index 8c2bc83b8..2ea372877 100644 --- a/pkgs/mockito/test/mockitoSpec.dart +++ b/pkgs/mockito/test/mockitoSpec.dart @@ -12,6 +12,19 @@ class RealClass { } } +abstract class Foo { + String bar(); +} + +abstract class AbstractFoo implements Foo { + String bar() => baz(); + + String baz(); +} + +class MockFoo extends AbstractFoo with Mock { +} + class MockedClass extends Mock implements RealClass{} expectFail(String expectedMessage, expectedToFail()){ @@ -36,6 +49,14 @@ main(){ mock = new MockedClass(); }); + group("mixin support", (){ + test("should work", (){ + var foo = new MockFoo(); + when(foo.baz()).thenReturn('baz'); + expect(foo.bar(), 'baz'); + }); + }); + group("when()", (){ test("should mock method without args", (){ when(mock.methodWithoutArgs()).thenReturn("A"); From 3a12f3e00f0ed18876fd21f3a50ec70def7d4c65 Mon Sep 17 00:00:00 2001 From: Ted Sander Date: Mon, 23 Feb 2015 15:13:19 -0800 Subject: [PATCH 007/607] Fix bug where deep matcher expects a map passed into the matches function --- pkgs/mockito/lib/mockito.dart | 2 +- pkgs/mockito/test/mockitoSpec.dart | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 172babeb8..1c33a008a 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -148,7 +148,7 @@ class InvocationMatcher { bool isMatchingArg(roleArg, actArg) { if(roleArg is _ArgMatcher){ - return roleArg._matcher==null || roleArg._matcher.matches(actArg, null); + return roleArg._matcher==null || roleArg._matcher.matches(actArg, {}); // } else if(roleArg is Mock){ // return identical(roleArg, actArg); }else{ diff --git a/pkgs/mockito/test/mockitoSpec.dart b/pkgs/mockito/test/mockitoSpec.dart index 2ea372877..b3721e909 100644 --- a/pkgs/mockito/test/mockitoSpec.dart +++ b/pkgs/mockito/test/mockitoSpec.dart @@ -142,6 +142,11 @@ main(){ when(mock.methodWithoutArgs()).thenReturn("B"); expect(mock.methodWithoutArgs(), equals("B")); }); + test("should mock method with calculated result", (){ + when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); + when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); + expect(mock.methodWithNormalArgs(43), equals("43")); + }); }); From b43ca349960b9b2f005cee125aca0bee0938593f Mon Sep 17 00:00:00 2001 From: zoechi Date: Mon, 6 Apr 2015 14:08:54 +0200 Subject: [PATCH 008/607] Migrate from the unittest package to the new test package. Fix the import '../lib/..' to 'package:mockito/...' Fix a typo in README.md Add CHANGELOG.md Apply dartformat --- pkgs/mockito/.gitignore | 4 + pkgs/mockito/.idea/dictionaries/zoechi.xml | 7 + pkgs/mockito/CHANGELOG.md | 4 + pkgs/mockito/README.md | 10 +- pkgs/mockito/lib/mockito.dart | 280 +++++++------- pkgs/mockito/pubspec.yaml | 6 +- pkgs/mockito/test/mockitoSpec.dart | 380 ------------------ pkgs/mockito/test/mockito_test.dart | 428 +++++++++++++++++++++ 8 files changed, 602 insertions(+), 517 deletions(-) create mode 100644 pkgs/mockito/.idea/dictionaries/zoechi.xml create mode 100644 pkgs/mockito/CHANGELOG.md delete mode 100644 pkgs/mockito/test/mockitoSpec.dart create mode 100644 pkgs/mockito/test/mockito_test.dart diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index 74a3439f8..de7094865 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -1,6 +1,7 @@ # Don’t commit the following directories created by pub. build/ packages/ +.pub/ # Or the files created by dart2js. *.dart.js @@ -9,5 +10,8 @@ packages/ *.js.deps *.js.map +# IDE +.idea/ + # Include when developing application packages. pubspec.lock diff --git a/pkgs/mockito/.idea/dictionaries/zoechi.xml b/pkgs/mockito/.idea/dictionaries/zoechi.xml new file mode 100644 index 000000000..30f624959 --- /dev/null +++ b/pkgs/mockito/.idea/dictionaries/zoechi.xml @@ -0,0 +1,7 @@ + + + + matchers + + + \ No newline at end of file diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md new file mode 100644 index 000000000..ca63bc02f --- /dev/null +++ b/pkgs/mockito/CHANGELOG.md @@ -0,0 +1,4 @@ +## 0.9.0 + +* Migrate from the unittest package to use the new test package. +* Format code using dartformat diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a5c65eeb7..0a6e1580f 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -3,7 +3,7 @@ dart-mockito Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). -Current mock libraries suffer from specifing method names as strings, which cause a lot of problems: +Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. * Poor support from IDE: no code-completion, no hints on argument types, can't jump to definition @@ -20,10 +20,10 @@ Dart-mockito fixes it - stubbing and verifing are first-class citisens. void sleep(){} int lives = 9; } - + //Mock class class MockCat extends Mock implements Cat{} - + //mock creation var cat = new MockCat(); ``` @@ -44,7 +44,7 @@ Once created, mock will remember all interactions. Then you can selectively veri //stubbing - before execution when(cat.sound()).thenReturn("Purr"); expect(cat.sound(), "Purr"); - //you can call it again + //you can call it again expect(cat.sound(), "Purr"); //let's change stub when(cat.sound()).thenReturn("Meow"); @@ -53,7 +53,7 @@ Once created, mock will remember all interactions. Then you can selectively veri when(cat.lives).thenReturn(9); expect(cat.lives, 9); ``` - + By default, for all methods that return value, mock returns null. Stubbing can be overridden: for example common stubbing can go to fixture setup but the test methods can override it. Please note that overridding stubbing is a potential code smell that points out too much stubbing. Once stubbed, the method will always return stubbed value regardless of how many times it is called. diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 1c33a008a..c255a4e75 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,11 +1,11 @@ library mockito; -import 'package:matcher/matcher.dart'; +import 'package:test/test.dart'; import 'dart:mirrors'; -bool _whenInProgress = false; -bool _verificationInProgress = false; -_WhenCall _whenCall=null; +bool _whenInProgress = false; +bool _verificationInProgress = false; +_WhenCall _whenCall = null; List<_VerifyCall> _verifyCalls = []; _TimeStampProvider _timer = new _TimeStampProvider(); List _capturedArgs = []; @@ -15,54 +15,56 @@ class Mock { List _responses = []; String _givenName = null; int _givenHashCode = null; - var _defaultResponse = ()=>new CannedResponse(null, (_)=>null); - - void _setExpected(CannedResponse cannedResponse){ + var _defaultResponse = () => new CannedResponse(null, (_) => null); + + void _setExpected(CannedResponse cannedResponse) { _responses.add(cannedResponse); } - - dynamic noSuchMethod(Invocation invocation){ - if(_whenInProgress){ + + dynamic noSuchMethod(Invocation invocation) { + if (_whenInProgress) { _whenCall = new _WhenCall(this, invocation); return null; - }else if(_verificationInProgress){ + } else if (_verificationInProgress) { _verifyCalls.add(new _VerifyCall(this, invocation)); return null; - }else{ + } else { _realCalls.add(new RealCall(this, invocation)); - var cannedResponse = _responses.lastWhere((cr)=>cr.matcher.matches(invocation), - orElse: _defaultResponse); + var cannedResponse = _responses.lastWhere( + (cr) => cr.matcher.matches(invocation), orElse: _defaultResponse); var response = cannedResponse.response(invocation); return response; } } - int get hashCode => _givenHashCode==null ? 0 : _givenHashCode; - - bool operator ==(other) => (_givenHashCode!=null && other is Mock) - ? _givenHashCode==other._givenHashCode : identical(this, other); + int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; + + bool operator ==(other) => (_givenHashCode != null && other is Mock) + ? _givenHashCode == other._givenHashCode + : identical(this, other); - String toString() => _givenName != null ? _givenName : runtimeType.toString(); + String toString() => _givenName != null ? _givenName : runtimeType.toString(); } named(dynamic mock, {String name, int hashCode}) => mock - .._givenName=name.._givenHashCode = hashCode; + .._givenName = name + .._givenHashCode = hashCode; -reset(var mock){ +reset(var mock) { mock._realCalls.clear(); mock._responses.clear(); } -clearInteractions(var mock){ +clearInteractions(var mock) { mock._realCalls.clear(); } -class PostExpectation { - thenReturn(expected){ - return _completeWhen((_)=>expected); +class PostExpectation { + thenReturn(expected) { + return _completeWhen((_) => expected); } - - thenAnswer(Answering answer){ + + thenAnswer(Answering answer) { return _completeWhen(answer); } @@ -70,51 +72,51 @@ class PostExpectation { _whenCall._setExpected(answer); var mock = _whenCall.mock; _whenCall = null; - _whenInProgress=false; + _whenInProgress = false; return mock; } } class InvocationMatcher { Invocation roleInvocation; - + InvocationMatcher(this.roleInvocation); - - bool matches(Invocation invocation){ - var isMatching = _isMethodMatches(invocation) && _isArgumentsMatches(invocation); - if(isMatching){ + + bool matches(Invocation invocation) { + var isMatching = + _isMethodMatches(invocation) && _isArgumentsMatches(invocation); + if (isMatching) { _captureArguments(invocation); } return isMatching; } bool _isMethodMatches(Invocation invocation) { - if(invocation.memberName!=roleInvocation.memberName){ + if (invocation.memberName != roleInvocation.memberName) { return false; } - if((invocation.isGetter!=roleInvocation.isGetter) - || (invocation.isSetter!=roleInvocation.isSetter) - || (invocation.isMethod!=roleInvocation.isMethod) - ){ - return false; + if ((invocation.isGetter != roleInvocation.isGetter) || + (invocation.isSetter != roleInvocation.isSetter) || + (invocation.isMethod != roleInvocation.isMethod)) { + return false; } return true; } void _captureArguments(Invocation invocation) { int index = 0; - for(var roleArg in roleInvocation.positionalArguments){ + for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; - if(roleArg is _ArgMatcher && roleArg._capture){ + if (roleArg is _ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } index++; } - for(var roleKey in roleInvocation.namedArguments.keys){ + for (var roleKey in roleInvocation.namedArguments.keys) { var roleArg = roleInvocation.namedArguments[roleKey]; var actArg = invocation.namedArguments[roleKey]; - if(roleArg is _ArgMatcher){ - if(roleArg is _ArgMatcher && roleArg._capture){ + if (roleArg is _ArgMatcher) { + if (roleArg is _ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } } @@ -122,24 +124,26 @@ class InvocationMatcher { } bool _isArgumentsMatches(Invocation invocation) { - if(invocation.positionalArguments.length!=roleInvocation.positionalArguments.length){ + if (invocation.positionalArguments.length != + roleInvocation.positionalArguments.length) { return false; } - if(invocation.namedArguments.length!=roleInvocation.namedArguments.length){ + if (invocation.namedArguments.length != + roleInvocation.namedArguments.length) { return false; } - int index=0; - for(var roleArg in roleInvocation.positionalArguments){ + int index = 0; + for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; - if(!isMatchingArg(roleArg, actArg)){ + if (!isMatchingArg(roleArg, actArg)) { return false; } index++; } - for(var roleKey in roleInvocation.namedArguments.keys){ + for (var roleKey in roleInvocation.namedArguments.keys) { var roleArg = roleInvocation.namedArguments[roleKey]; var actArg = invocation.namedArguments[roleKey]; - if(!isMatchingArg(roleArg, actArg)){ + if (!isMatchingArg(roleArg, actArg)) { return false; } } @@ -147,62 +151,64 @@ class InvocationMatcher { } bool isMatchingArg(roleArg, actArg) { - if(roleArg is _ArgMatcher){ - return roleArg._matcher==null || roleArg._matcher.matches(actArg, {}); + if (roleArg is _ArgMatcher) { + return roleArg._matcher == null || roleArg._matcher.matches(actArg, {}); // } else if(roleArg is Mock){ // return identical(roleArg, actArg); - }else{ + } else { return roleArg == actArg; } } } -class CannedResponse{ +class CannedResponse { InvocationMatcher matcher; Answering response; - - CannedResponse(this.matcher, this.response); + + CannedResponse(this.matcher, this.response); } -class _TimeStampProvider{ +class _TimeStampProvider { int _now = 0; - DateTime now(){ + DateTime now() { var candidate = new DateTime.now(); - if(candidate.millisecondsSinceEpoch<=_now){ - candidate = new DateTime.fromMillisecondsSinceEpoch(_now+1); + if (candidate.millisecondsSinceEpoch <= _now) { + candidate = new DateTime.fromMillisecondsSinceEpoch(_now + 1); } _now = candidate.millisecondsSinceEpoch; return candidate; } } -class RealCall{ +class RealCall { DateTime _timeStamp; final Mock mock; final Invocation invocation; bool verified = false; - RealCall(this.mock, this.invocation){ - _timeStamp = _timer.now(); + RealCall(this.mock, this.invocation) { + _timeStamp = _timer.now(); } - + DateTime get timeStamp => _timeStamp; - - String toString(){ + + String toString() { var verifiedText = verified ? "[VERIFIED] " : ""; - List posArgs = invocation.positionalArguments.map((v)=>v==null?"null":v.toString()).toList(); - List mapArgList = invocation.namedArguments.keys.map((key){ + List posArgs = invocation.positionalArguments + .map((v) => v == null ? "null" : v.toString()) + .toList(); + List mapArgList = invocation.namedArguments.keys.map((key) { return "${MirrorSystem.getName(key)}: ${invocation.namedArguments[key]}"; }).toList(growable: false); - if(mapArgList.isNotEmpty){ - posArgs.add("{${mapArgList.join(", ")}}"); + if (mapArgList.isNotEmpty) { + posArgs.add("{${mapArgList.join(", ")}}"); } String args = posArgs.join(", "); - String method = MirrorSystem.getName(invocation.memberName); - if(invocation.isMethod){ + String method = MirrorSystem.getName(invocation.memberName); + if (invocation.isMethod) { method = ".$method($args)"; - }else if(invocation.isGetter){ + } else if (invocation.isGetter) { method = ".$method"; - }else{ + } else { method = ".$method=$args"; } return "$verifiedText$mock$method"; @@ -213,9 +219,10 @@ class _WhenCall { Mock mock; Invocation whenInvocation; _WhenCall(this.mock, this.whenInvocation); - - void _setExpected(Answering answer){ - mock._setExpected(new CannedResponse(new InvocationMatcher(whenInvocation), answer)); + + void _setExpected(Answering answer) { + mock._setExpected( + new CannedResponse(new InvocationMatcher(whenInvocation), answer)); } } @@ -223,58 +230,64 @@ class _VerifyCall { Mock mock; Invocation verifyInvocation; List matchingInvocations; - - _VerifyCall(this.mock, this.verifyInvocation){ + + _VerifyCall(this.mock, this.verifyInvocation) { var expectedMatcher = new InvocationMatcher(verifyInvocation); - matchingInvocations = mock._realCalls.where((RealCall recordedInvocation){ - return !recordedInvocation.verified && expectedMatcher.matches(recordedInvocation.invocation);}).toList(); + matchingInvocations = mock._realCalls.where((RealCall recordedInvocation) { + return !recordedInvocation.verified && + expectedMatcher.matches(recordedInvocation.invocation); + }).toList(); } - - _findAfter(DateTime dt){ - return matchingInvocations.firstWhere((inv)=>!inv.verified && inv.timeStamp.isAfter(dt), orElse: ()=>null); + + _findAfter(DateTime dt) { + return matchingInvocations.firstWhere( + (inv) => !inv.verified && inv.timeStamp.isAfter(dt), + orElse: () => null); } - - _checkWith(bool never){ - if(!never && matchingInvocations.isEmpty){ + + _checkWith(bool never) { + if (!never && matchingInvocations.isEmpty) { var otherCallsText = ""; - if(mock._realCalls.isNotEmpty){ + if (mock._realCalls.isNotEmpty) { otherCallsText = " All calls: "; } var calls = mock._realCalls.join(", "); fail("No matching calls.$otherCallsText$calls"); } - if(never && matchingInvocations.isNotEmpty){ + if (never && matchingInvocations.isNotEmpty) { var calls = mock._realCalls.join(", "); fail("Unexpected calls. All calls: $calls"); } - matchingInvocations.forEach((inv){inv.verified=true;}); + matchingInvocations.forEach((inv) { + inv.verified = true; + }); } } -class _ArgMatcher{ +class _ArgMatcher { Matcher _matcher; bool _capture; - + _ArgMatcher(this._matcher, this._capture); } - get any => new _ArgMatcher(null, false); get captureAny => new _ArgMatcher(null, true); captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); argThat(Matcher matcher) => new _ArgMatcher(matcher, false); -class VerificationResult{ +class VerificationResult { List captured = []; int callCount; - - VerificationResult(this.callCount){ + + VerificationResult(this.callCount) { captured = new List.from(_capturedArgs, growable: false); _capturedArgs.clear(); } - - void called(matcher){ - expect(callCount, wrapMatcher(matcher), reason: "Unexpected number of calls"); + + void called(matcher) { + expect(callCount, wrapMatcher(matcher), + reason: "Unexpected number of calls"); } } @@ -289,64 +302,72 @@ Verification get verifyNever => _makeVerify(true); Verification get verify => _makeVerify(false); Verification _makeVerify(bool never) { - if(_verifyCalls.isNotEmpty){ + if (_verifyCalls.isNotEmpty) { throw new StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (mock){ + return (mock) { _verificationInProgress = false; - if(_verifyCalls.length==1){ + if (_verifyCalls.length == 1) { _VerifyCall verifyCall = _verifyCalls.removeLast(); - var result = new VerificationResult(verifyCall.matchingInvocations.length); + var result = + new VerificationResult(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; - }else{ + } else { fail("Used on non-mockito"); } }; } InOrderVerification get verifyInOrder { - if(_verifyCalls.isNotEmpty){ + if (_verifyCalls.isNotEmpty) { throw new StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (verifyCalls){ + return (verifyCalls) { _verificationInProgress = false; DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = new List.from(_verifyCalls); _verifyCalls.clear(); List matchedCalls = []; - for(_VerifyCall verifyCall in tmpVerifyCalls){ + for (_VerifyCall verifyCall in tmpVerifyCalls) { RealCall matched = verifyCall._findAfter(dt); - if(matched!=null){ + if (matched != null) { matchedCalls.add(matched); - dt=matched.timeStamp; - }else{ - Set mocks = tmpVerifyCalls.map((_VerifyCall vc)=>vc.mock).toSet(); - List allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false); - allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); + dt = matched.timeStamp; + } else { + Set mocks = + tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); + List allInvocations = + mocks.expand((m) => m._realCalls).toList(growable: false); + allInvocations + .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); String otherCalls = ""; - if(allInvocations.isNotEmpty){ + if (allInvocations.isNotEmpty) { otherCalls = " All calls: ${allInvocations.join(", ")}"; } - fail("Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); + fail( + "Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); } } - matchedCalls.forEach((rc){rc.verified=true;}); + matchedCalls.forEach((rc) { + rc.verified = true; + }); }; } verifyNoMoreInteractions(var mock) { - var unverified = mock._realCalls.where((inv)=>!inv.verified).toList(); - if(unverified.isNotEmpty){ - fail("No more calls expected, but following found: "+unverified.join()); - } + var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); + if (unverified.isNotEmpty) { + fail("No more calls expected, but following found: " + unverified.join()); + } } verifyZeroInteractions(var mock) { - if(mock._realCalls.isNotEmpty){ - fail("No interaction expected, but following found: "+mock._realCalls.join()); + if (mock._realCalls.isNotEmpty) { + fail("No interaction expected, but following found: " + + mock._realCalls.join()); } } @@ -354,16 +375,17 @@ typedef PostExpectation Expectation(x); Expectation get when { _whenInProgress = true; - return (_){ + return (_) { _whenInProgress = false; return new PostExpectation(); }; } -logInvocations(List mocks){ - List allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false); - allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); - allInvocations.forEach((inv){ - print(inv.toString()); +logInvocations(List mocks) { + List allInvocations = + mocks.expand((m) => m._realCalls).toList(growable: false); + allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); + allInvocations.forEach((inv) { + print(inv.toString()); }); } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c5845a4e8..8a5c744f4 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,11 +1,11 @@ name: mockito -version: 0.8.2 +version: 0.9.0-beta.0 author: Dmitriy Fibulwinter description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito environment: sdk: '>=1.0.0 <2.0.0' dependencies: - matcher: '>=0.10.0 <1.0.0' + matcher: '>=0.12.0-alpha.0 <0.13.0' dev_dependencies: - unittest: '>=0.11.0 <1.0.0' + test: '>=0.12.0-beta.4 <0.13.0' diff --git a/pkgs/mockito/test/mockitoSpec.dart b/pkgs/mockito/test/mockitoSpec.dart deleted file mode 100644 index b3721e909..000000000 --- a/pkgs/mockito/test/mockitoSpec.dart +++ /dev/null @@ -1,380 +0,0 @@ -import 'package:unittest/unittest.dart'; -import '../lib/mockito.dart'; - -class RealClass { - String methodWithoutArgs() => "Real"; - String methodWithNormalArgs(int x) => "Real"; - String methodWithPositionalArgs(int x, [int y]) => "Real"; - String methodWithNamedArgs(int x, {int y}) => "Real"; - String get getter => "Real"; - void set setter(String arg){ - throw new StateError("I must be mocked"); - } -} - -abstract class Foo { - String bar(); -} - -abstract class AbstractFoo implements Foo { - String bar() => baz(); - - String baz(); -} - -class MockFoo extends AbstractFoo with Mock { -} - -class MockedClass extends Mock implements RealClass{} - -expectFail(String expectedMessage, expectedToFail()){ - try{ - expectedToFail(); - fail("It was expected to fail!"); - }catch(e){ - if(!(e is TestFailure)){ - throw e; - }else{ - if(expectedMessage!=e.message){ - throw new TestFailure("Failed, but with wrong message: ${e.message}"); - } - } - } -} - -main(){ - RealClass mock; - - setUp((){ - mock = new MockedClass(); - }); - - group("mixin support", (){ - test("should work", (){ - var foo = new MockFoo(); - when(foo.baz()).thenReturn('baz'); - expect(foo.bar(), 'baz'); - }); - }); - - group("when()", (){ - test("should mock method without args", (){ - when(mock.methodWithoutArgs()).thenReturn("A"); - expect(mock.methodWithoutArgs(), equals("A")); - }); - test("should mock method with normal args", (){ - when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); - expect(mock.methodWithNormalArgs(43), isNull); - expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); - }); - test("should mock method with positional args", (){ - when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); - expect(mock.methodWithPositionalArgs(42), isNull); - expect(mock.methodWithPositionalArgs(42, 18), isNull); - expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); - }); - test("should mock method with named args", (){ - when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); - expect(mock.methodWithNamedArgs(42), isNull); - expect(mock.methodWithNamedArgs(42, y:18), isNull); - expect(mock.methodWithNamedArgs(42, y:17), equals("Why answer?")); - }); - test("should mock method with argument matcher", (){ - when(mock.methodWithNormalArgs(argThat(greaterThan(100)))).thenReturn("A lot!"); - expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); - }); - test("should mock method with any argument matcher", (){ - when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); - expect(mock.methodWithNormalArgs(100), equals("A lot!")); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); - }); - test("should mock method with mix of argument matchers and real things", (){ - when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)).thenReturn("A lot with 17"); - expect(mock.methodWithPositionalArgs(100, 17), isNull); - expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); - }); - test("should mock getter", (){ - when(mock.getter).thenReturn("A"); - expect(mock.getter, equals("A")); - }); - test("should mock hashCode", (){ - named(mock, hashCode: 42); - expect(mock.hashCode, equals(42)); - }); - test("should have hashCode when it is not mocked", (){ - expect(mock.hashCode, isNotNull); - }); -// test("should n't mock toString", (){ -// when(mock.toString()).thenReturn("meow"); -// expect(mock.toString(), equals("meow")); -// }); - test("should have default toString when it is not mocked", (){ - expect(mock.toString(), equals("MockedClass")); - }); - test("should have toString as name when it is not mocked", (){ - named(mock, name: "Cat"); - expect(mock.toString(), equals("Cat")); - }); - test("should mock equals between mocks when givenHashCode is equals", (){ - var anotherMock = named(new MockedClass(), hashCode: 42); - named(mock, hashCode: 42); - expect(mock==anotherMock, isTrue); - }); - test("should use identical equality between it is not mocked", (){ - var anotherMock = new MockedClass(); - expect(mock==anotherMock, isFalse); - expect(mock==mock, isTrue); - }); - //no need to mock setter, except if we will have spies later... - test("should mock method with calculated result", (){ - when(mock.methodWithNormalArgs(any)).thenAnswer((Invocation inv)=>inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals("43")); - expect(mock.methodWithNormalArgs(42), equals("42")); - }); - test("should return mock to make simple oneline mocks", (){ - RealClass mockWithSetup = when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); - expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); - }); - test("should use latest matching when definition", (){ - when(mock.methodWithoutArgs()).thenReturn("A"); - when(mock.methodWithoutArgs()).thenReturn("B"); - expect(mock.methodWithoutArgs(), equals("B")); - }); - test("should mock method with calculated result", (){ - when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); - when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); - expect(mock.methodWithNormalArgs(43), equals("43")); - }); - - }); - - group("verify()", (){ - test("should verify method without args", (){ - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - test("should verify method with normal args", (){ - mock.methodWithNormalArgs(42); - expectFail("No matching calls. All calls: MockedClass.methodWithNormalArgs(42)", (){ - verify(mock.methodWithNormalArgs(43)); - }); - verify(mock.methodWithNormalArgs(42)); - }); - test("should mock method with positional args", (){ - mock.methodWithPositionalArgs(42, 17); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", (){ - verify(mock.methodWithPositionalArgs(42)); - }); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", (){ - verify(mock.methodWithPositionalArgs(42, 18)); - }); - verify(mock.methodWithPositionalArgs(42, 17)); - }); - test("should mock method with named args", (){ - mock.methodWithNamedArgs(42, y: 17); - expectFail("No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", (){ - verify(mock.methodWithNamedArgs(42)); - }); - expectFail("No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", (){ - verify(mock.methodWithNamedArgs(42, y:18)); - }); - verify(mock.methodWithNamedArgs(42, y:17)); - }); - test("should mock method with argument matcher", (){ - mock.methodWithNormalArgs(100); - expectFail("No matching calls. All calls: MockedClass.methodWithNormalArgs(100)", (){ - verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); - }); - verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100)))); - }); - test("should mock method with argument capturer", (){ - mock.methodWithNormalArgs(50); - mock.methodWithNormalArgs(100); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([50, 100])); - }); - test("should mock method with argument matcher and capturer", (){ - mock.methodWithNormalArgs(50); - mock.methodWithNormalArgs(100); - var captured = 0; - expect(verify(mock.methodWithNormalArgs(captureThat(greaterThan(75)))).captured.single, equals(100)); - expect(verify(mock.methodWithNormalArgs(captureThat(lessThan(75)))).captured.single, equals(50)); - }); - test("should mock method with mix of argument matchers and real things", (){ - mock.methodWithPositionalArgs(100, 17); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", (){ - verify(mock.methodWithPositionalArgs(argThat(greaterThanOrEqualTo(100)), 18)); - }); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", (){ - verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); - }); - verify(mock.methodWithPositionalArgs(argThat(greaterThanOrEqualTo(100)), 17)); - }); - test("should mock getter", (){ - mock.getter; - verify(mock.getter); - }); - test("should mock setter", (){ - mock.setter = "A"; - expectFail("No matching calls. All calls: MockedClass.setter==A", (){ - verify(mock.setter="B"); - }); - verify(mock.setter="A"); - }); - }); - group("verify() qualifies", (){ - group("unqualified as at least one", (){ - test("zero fails", (){ - expectFail("No matching calls.", (){ - verify(mock.methodWithoutArgs()); - }); - }); - test("one passes", (){ - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - test("more than one passes", (){ - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - }); - group("counts calls", (){ - test("zero fails", (){ - expectFail("No matching calls.", (){ - verify(mock.methodWithoutArgs()).called(1); - }); - }); - test("one passes", (){ - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()).called(1); - }); - test("more than one fails", (){ - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", (){ - verify(mock.methodWithoutArgs()).called(1); - }); - }); - }); - group("verifyNever", (){ - test("zero passes", (){ - verifyNever(mock.methodWithoutArgs()); - }); - test("one fails", (){ - mock.methodWithoutArgs(); - expectFail("Unexpected calls. All calls: MockedClass.methodWithoutArgs()", (){ - verifyNever(mock.methodWithoutArgs()); - }); - }); - }); - group("doesn't count already verified again", (){ - test("fail case", (){ - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - expectFail("No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()", (){ - verify(mock.methodWithoutArgs()); - }); - }); - test("pass case", (){ - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - }); - }); - - group("verifyZeroInteractions()", (){ - test("never touched pass", (){ - verifyZeroInteractions(mock); - }); - test("any touch fails", (){ - mock.methodWithoutArgs(); - expectFail("No interaction expected, but following found: MockedClass.methodWithoutArgs()", (){ - verifyZeroInteractions(mock); - }); - }); - test("verifired call fails", (){ - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - expectFail("No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", (){ - verifyZeroInteractions(mock); - }); - }); - }); - group("verifyNoMoreInteractions()", (){ - test("never touched pass", (){ - verifyNoMoreInteractions(mock); - }); - test("any unverified touch fails", (){ - mock.methodWithoutArgs(); - expectFail("No more calls expected, but following found: MockedClass.methodWithoutArgs()", (){ - verifyNoMoreInteractions(mock); - }); - }); - test("verified touch passes", (){ - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - verifyNoMoreInteractions(mock); - }); - }); - group("verifyInOrder()", (){ - test("right order passes", (){ - mock.methodWithoutArgs(); - mock.getter; - verifyInOrder([mock.methodWithoutArgs(), mock.getter]); - }); - test("wrong order fails", (){ - mock.methodWithoutArgs(); - mock.getter; - expectFail("Matching call #1 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.getter", (){ - verifyInOrder([mock.getter, mock.methodWithoutArgs()]); - }); - }); - test("uncomplete fails", (){ - mock.methodWithoutArgs(); - expectFail("Matching call #1 not found. All calls: MockedClass.methodWithoutArgs()", (){ - verifyInOrder([mock.methodWithoutArgs(), mock.getter]); - }); - }); - test("methods can be called again and again", (){ - mock.methodWithoutArgs(); - mock.getter; - mock.methodWithoutArgs(); - verifyInOrder([mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); - }); - test("methods can be called again and again - fail case", (){ - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - mock.getter; - expectFail("Matching call #2 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), MockedClass.getter", (){ - verifyInOrder([mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); - }); - }); - }); - - group("capture", (){ - test("capture should work as captureOut", (){ - mock.methodWithNormalArgs(42); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, equals(42)); - }); - test("should captureOut multiple arguments", (){ - mock.methodWithPositionalArgs(1, 2); - expect(verify(mock.methodWithPositionalArgs(captureAny, captureAny)).captured, equals([1, 2])); - }); - test("should captureOut with matching arguments", (){ - mock.methodWithPositionalArgs(1); - mock.methodWithPositionalArgs(2, 3); - expect(verify(mock.methodWithPositionalArgs(captureAny, captureAny)).captured, equals([2, 3])); - }); - test("should captureOut multiple invocations", (){ - mock.methodWithNormalArgs(1); - mock.methodWithNormalArgs(2); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([1, 2])); - }); - }); - -} - diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart new file mode 100644 index 000000000..4d61b6271 --- /dev/null +++ b/pkgs/mockito/test/mockito_test.dart @@ -0,0 +1,428 @@ +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; + +class RealClass { + String methodWithoutArgs() => "Real"; + String methodWithNormalArgs(int x) => "Real"; + String methodWithPositionalArgs(int x, [int y]) => "Real"; + String methodWithNamedArgs(int x, {int y}) => "Real"; + String get getter => "Real"; + void set setter(String arg) { + throw new StateError("I must be mocked"); + } +} + +abstract class Foo { + String bar(); +} + +abstract class AbstractFoo implements Foo { + String bar() => baz(); + + String baz(); +} + +class MockFoo extends AbstractFoo with Mock {} + +class MockedClass extends Mock implements RealClass {} + +expectFail(String expectedMessage, expectedToFail()) { + try { + expectedToFail(); + fail("It was expected to fail!"); + } catch (e) { + if (!(e is TestFailure)) { + throw e; + } else { + if (expectedMessage != e.message) { + throw new TestFailure("Failed, but with wrong message: ${e.message}"); + } + } + } +} + +main() { + RealClass mock; + + setUp(() { + mock = new MockedClass(); + }); + + group("mixin support", () { + test("should work", () { + var foo = new MockFoo(); + when(foo.baz()).thenReturn('baz'); + expect(foo.bar(), 'baz'); + }); + }); + + group("when()", () { + test("should mock method without args", () { + when(mock.methodWithoutArgs()).thenReturn("A"); + expect(mock.methodWithoutArgs(), equals("A")); + }); + test("should mock method with normal args", () { + when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); + expect(mock.methodWithNormalArgs(43), isNull); + expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); + }); + test("should mock method with positional args", () { + when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); + expect(mock.methodWithPositionalArgs(42), isNull); + expect(mock.methodWithPositionalArgs(42, 18), isNull); + expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); + }); + test("should mock method with named args", () { + when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); + expect(mock.methodWithNamedArgs(42), isNull); + expect(mock.methodWithNamedArgs(42, y: 18), isNull); + expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); + }); + test("should mock method with argument matcher", () { + when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) + .thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), isNull); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); + }); + test("should mock method with any argument matcher", () { + when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); + }); + test("should mock method with mix of argument matchers and real things", + () { + when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) + .thenReturn("A lot with 17"); + expect(mock.methodWithPositionalArgs(100, 17), isNull); + expect(mock.methodWithPositionalArgs(101, 18), isNull); + expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); + }); + test("should mock getter", () { + when(mock.getter).thenReturn("A"); + expect(mock.getter, equals("A")); + }); + test("should mock hashCode", () { + named(mock, hashCode: 42); + expect(mock.hashCode, equals(42)); + }); + test("should have hashCode when it is not mocked", () { + expect(mock.hashCode, isNotNull); + }); +// test("should n't mock toString", (){ +// when(mock.toString()).thenReturn("meow"); +// expect(mock.toString(), equals("meow")); +// }); + test("should have default toString when it is not mocked", () { + expect(mock.toString(), equals("MockedClass")); + }); + test("should have toString as name when it is not mocked", () { + named(mock, name: "Cat"); + expect(mock.toString(), equals("Cat")); + }); + test("should mock equals between mocks when givenHashCode is equals", () { + var anotherMock = named(new MockedClass(), hashCode: 42); + named(mock, hashCode: 42); + expect(mock == anotherMock, isTrue); + }); + test("should use identical equality between it is not mocked", () { + var anotherMock = new MockedClass(); + expect(mock == anotherMock, isFalse); + expect(mock == mock, isTrue); + }); + //no need to mock setter, except if we will have spies later... + test("should mock method with calculated result", () { + when(mock.methodWithNormalArgs(any)).thenAnswer( + (Invocation inv) => inv.positionalArguments[0].toString()); + expect(mock.methodWithNormalArgs(43), equals("43")); + expect(mock.methodWithNormalArgs(42), equals("42")); + }); + test("should return mock to make simple oneline mocks", () { + RealClass mockWithSetup = + when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); + expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); + }); + test("should use latest matching when definition", () { + when(mock.methodWithoutArgs()).thenReturn("A"); + when(mock.methodWithoutArgs()).thenReturn("B"); + expect(mock.methodWithoutArgs(), equals("B")); + }); + test("should mock method with calculated result", () { + when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); + when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); + expect(mock.methodWithNormalArgs(43), equals("43")); + }); + }); + + group("verify()", () { + test("should verify method without args", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + test("should verify method with normal args", () { + mock.methodWithNormalArgs(42); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNormalArgs(42)", + () { + verify(mock.methodWithNormalArgs(43)); + }); + verify(mock.methodWithNormalArgs(42)); + }); + test("should mock method with positional args", () { + mock.methodWithPositionalArgs(42, 17); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", + () { + verify(mock.methodWithPositionalArgs(42)); + }); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", + () { + verify(mock.methodWithPositionalArgs(42, 18)); + }); + verify(mock.methodWithPositionalArgs(42, 17)); + }); + test("should mock method with named args", () { + mock.methodWithNamedArgs(42, y: 17); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", + () { + verify(mock.methodWithNamedArgs(42)); + }); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", + () { + verify(mock.methodWithNamedArgs(42, y: 18)); + }); + verify(mock.methodWithNamedArgs(42, y: 17)); + }); + test("should mock method with argument matcher", () { + mock.methodWithNormalArgs(100); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNormalArgs(100)", + () { + verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); + }); + verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100)))); + }); + test("should mock method with argument capturer", () { + mock.methodWithNormalArgs(50); + mock.methodWithNormalArgs(100); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured, + equals([50, 100])); + }); + test("should mock method with argument matcher and capturer", () { + mock.methodWithNormalArgs(50); + mock.methodWithNormalArgs(100); + var captured = 0; + expect(verify(mock.methodWithNormalArgs( + captureThat(greaterThan(75)))).captured.single, equals(100)); + expect(verify(mock + .methodWithNormalArgs(captureThat(lessThan(75)))).captured.single, + equals(50)); + }); + test("should mock method with mix of argument matchers and real things", + () { + mock.methodWithPositionalArgs(100, 17); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", + () { + verify(mock.methodWithPositionalArgs( + argThat(greaterThanOrEqualTo(100)), 18)); + }); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", + () { + verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); + }); + verify(mock.methodWithPositionalArgs( + argThat(greaterThanOrEqualTo(100)), 17)); + }); + test("should mock getter", () { + mock.getter; + verify(mock.getter); + }); + test("should mock setter", () { + mock.setter = "A"; + expectFail("No matching calls. All calls: MockedClass.setter==A", () { + verify(mock.setter = "B"); + }); + verify(mock.setter = "A"); + }); + }); + group("verify() qualifies", () { + group("unqualified as at least one", () { + test("zero fails", () { + expectFail("No matching calls.", () { + verify(mock.methodWithoutArgs()); + }); + }); + test("one passes", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + test("more than one passes", () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + }); + group("counts calls", () { + test("zero fails", () { + expectFail("No matching calls.", () { + verify(mock.methodWithoutArgs()).called(1); + }); + }); + test("one passes", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()).called(1); + }); + test("more than one fails", () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", + () { + verify(mock.methodWithoutArgs()).called(1); + }); + }); + }); + group("verifyNever", () { + test("zero passes", () { + verifyNever(mock.methodWithoutArgs()); + }); + test("one fails", () { + mock.methodWithoutArgs(); + expectFail( + "Unexpected calls. All calls: MockedClass.methodWithoutArgs()", () { + verifyNever(mock.methodWithoutArgs()); + }); + }); + }); + group("doesn't count already verified again", () { + test("fail case", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + expectFail( + "No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()", + () { + verify(mock.methodWithoutArgs()); + }); + }); + test("pass case", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + }); + }); + + group("verifyZeroInteractions()", () { + test("never touched pass", () { + verifyZeroInteractions(mock); + }); + test("any touch fails", () { + mock.methodWithoutArgs(); + expectFail( + "No interaction expected, but following found: MockedClass.methodWithoutArgs()", + () { + verifyZeroInteractions(mock); + }); + }); + test("verifired call fails", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + expectFail( + "No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", + () { + verifyZeroInteractions(mock); + }); + }); + }); + group("verifyNoMoreInteractions()", () { + test("never touched pass", () { + verifyNoMoreInteractions(mock); + }); + test("any unverified touch fails", () { + mock.methodWithoutArgs(); + expectFail( + "No more calls expected, but following found: MockedClass.methodWithoutArgs()", + () { + verifyNoMoreInteractions(mock); + }); + }); + test("verified touch passes", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + verifyNoMoreInteractions(mock); + }); + }); + group("verifyInOrder()", () { + test("right order passes", () { + mock.methodWithoutArgs(); + mock.getter; + verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + }); + test("wrong order fails", () { + mock.methodWithoutArgs(); + mock.getter; + expectFail( + "Matching call #1 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.getter", + () { + verifyInOrder([mock.getter, mock.methodWithoutArgs()]); + }); + }); + test("uncomplete fails", () { + mock.methodWithoutArgs(); + expectFail( + "Matching call #1 not found. All calls: MockedClass.methodWithoutArgs()", + () { + verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + }); + }); + test("methods can be called again and again", () { + mock.methodWithoutArgs(); + mock.getter; + mock.methodWithoutArgs(); + verifyInOrder( + [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + }); + test("methods can be called again and again - fail case", () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + mock.getter; + expectFail( + "Matching call #2 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), MockedClass.getter", + () { + verifyInOrder( + [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + }); + }); + }); + + group("capture", () { + test("capture should work as captureOut", () { + mock.methodWithNormalArgs(42); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, + equals(42)); + }); + test("should captureOut multiple arguments", () { + mock.methodWithPositionalArgs(1, 2); + expect(verify( + mock.methodWithPositionalArgs(captureAny, captureAny)).captured, + equals([1, 2])); + }); + test("should captureOut with matching arguments", () { + mock.methodWithPositionalArgs(1); + mock.methodWithPositionalArgs(2, 3); + expect(verify( + mock.methodWithPositionalArgs(captureAny, captureAny)).captured, + equals([2, 3])); + }); + test("should captureOut multiple invocations", () { + mock.methodWithNormalArgs(1); + mock.methodWithNormalArgs(2); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured, + equals([1, 2])); + }); + }); +} From fdc29bcea8d25177b7c9f19b26497dffcfd320da Mon Sep 17 00:00:00 2001 From: Stephen Turley Date: Wed, 6 May 2015 22:18:52 -0400 Subject: [PATCH 009/607] Added .pub/ and .idea/ to .gitignore --- pkgs/mockito/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index 74a3439f8..390e5b8c1 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -1,6 +1,7 @@ # Don’t commit the following directories created by pub. build/ packages/ +.pub/ # Or the files created by dart2js. *.dart.js @@ -11,3 +12,7 @@ packages/ # Include when developing application packages. pubspec.lock + + +# IDE files +.idea/ From 88ac524fa1fb77554751251db9f140449cefbb88 Mon Sep 17 00:00:00 2001 From: Stephen Turley Date: Wed, 6 May 2015 22:19:09 -0400 Subject: [PATCH 010/607] fixed typo in README --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a5c65eeb7..9978d0418 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -7,7 +7,7 @@ Current mock libraries suffer from specifing method names as strings, which caus * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. * Poor support from IDE: no code-completion, no hints on argument types, can't jump to definition -Dart-mockito fixes it - stubbing and verifing are first-class citisens. +Dart-mockito fixes it - stubbing and verifing are first-class citizens. ## Let's create mocks ```dart From ef9525468f69fd6228eddfd8906b772fd981ba72 Mon Sep 17 00:00:00 2001 From: Stephen Turley Date: Wed, 6 May 2015 22:38:09 -0400 Subject: [PATCH 011/607] removed deprecated UnitTest framework and added missing Test import in mockito.dart --- pkgs/mockito/lib/mockito.dart | 1 + pkgs/mockito/test/mockitoSpec.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 1c33a008a..35d55533e 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,6 +1,7 @@ library mockito; import 'package:matcher/matcher.dart'; +import 'package:test/test.dart'; import 'dart:mirrors'; bool _whenInProgress = false; diff --git a/pkgs/mockito/test/mockitoSpec.dart b/pkgs/mockito/test/mockitoSpec.dart index b3721e909..e12dc56f1 100644 --- a/pkgs/mockito/test/mockitoSpec.dart +++ b/pkgs/mockito/test/mockitoSpec.dart @@ -1,4 +1,4 @@ -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import '../lib/mockito.dart'; class RealClass { From 7ac2602ea8f0ec25229a800f123907efdd265993 Mon Sep 17 00:00:00 2001 From: Stephen Turley Date: Wed, 6 May 2015 22:42:06 -0400 Subject: [PATCH 012/607] removed unittest dependency --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c5845a4e8..6728a5282 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -8,4 +8,4 @@ environment: dependencies: matcher: '>=0.10.0 <1.0.0' dev_dependencies: - unittest: '>=0.11.0 <1.0.0' + test: '>=0.11.0 <1.0.0' From 77f638ee722ee010103869ee20c0257872092616 Mon Sep 17 00:00:00 2001 From: Stephen Turley Date: Wed, 6 May 2015 22:46:51 -0400 Subject: [PATCH 013/607] Fixed two more typos and removed unused import --- pkgs/mockito/README.md | 4 ++-- pkgs/mockito/lib/mockito.dart | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 9978d0418..4932fd8f4 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -3,11 +3,11 @@ dart-mockito Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). -Current mock libraries suffer from specifing method names as strings, which cause a lot of problems: +Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. * Poor support from IDE: no code-completion, no hints on argument types, can't jump to definition -Dart-mockito fixes it - stubbing and verifing are first-class citizens. +Dart-mockito fixes it - stubbing and verifying are first-class citizens. ## Let's create mocks ```dart diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 35d55533e..0fd73944f 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,6 +1,5 @@ library mockito; -import 'package:matcher/matcher.dart'; import 'package:test/test.dart'; import 'dart:mirrors'; From 990e52a0aa84f13eda53144a74fb2866bf4f0919 Mon Sep 17 00:00:00 2001 From: Stephen Turley Date: Fri, 8 May 2015 19:41:16 -0400 Subject: [PATCH 014/607] Removed IDE specific files from .gitignore --- pkgs/mockito/.gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index 390e5b8c1..c41da4fd3 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -12,7 +12,3 @@ packages/ # Include when developing application packages. pubspec.lock - - -# IDE files -.idea/ From 1b8bc4e3c405c5c37334a13b11f4602c81855766 Mon Sep 17 00:00:00 2001 From: dvishnyakov Date: Mon, 11 May 2015 10:55:27 +1000 Subject: [PATCH 015/607] Apply dartformat --- pkgs/mockito/lib/mockito.dart | 278 ++++++++++---------- pkgs/mockito/test/mockitoSpec.dart | 390 ++++++++++++++++------------- 2 files changed, 369 insertions(+), 299 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 0fd73944f..c255a4e75 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -3,9 +3,9 @@ library mockito; import 'package:test/test.dart'; import 'dart:mirrors'; -bool _whenInProgress = false; -bool _verificationInProgress = false; -_WhenCall _whenCall=null; +bool _whenInProgress = false; +bool _verificationInProgress = false; +_WhenCall _whenCall = null; List<_VerifyCall> _verifyCalls = []; _TimeStampProvider _timer = new _TimeStampProvider(); List _capturedArgs = []; @@ -15,54 +15,56 @@ class Mock { List _responses = []; String _givenName = null; int _givenHashCode = null; - var _defaultResponse = ()=>new CannedResponse(null, (_)=>null); - - void _setExpected(CannedResponse cannedResponse){ + var _defaultResponse = () => new CannedResponse(null, (_) => null); + + void _setExpected(CannedResponse cannedResponse) { _responses.add(cannedResponse); } - - dynamic noSuchMethod(Invocation invocation){ - if(_whenInProgress){ + + dynamic noSuchMethod(Invocation invocation) { + if (_whenInProgress) { _whenCall = new _WhenCall(this, invocation); return null; - }else if(_verificationInProgress){ + } else if (_verificationInProgress) { _verifyCalls.add(new _VerifyCall(this, invocation)); return null; - }else{ + } else { _realCalls.add(new RealCall(this, invocation)); - var cannedResponse = _responses.lastWhere((cr)=>cr.matcher.matches(invocation), - orElse: _defaultResponse); + var cannedResponse = _responses.lastWhere( + (cr) => cr.matcher.matches(invocation), orElse: _defaultResponse); var response = cannedResponse.response(invocation); return response; } } - int get hashCode => _givenHashCode==null ? 0 : _givenHashCode; - - bool operator ==(other) => (_givenHashCode!=null && other is Mock) - ? _givenHashCode==other._givenHashCode : identical(this, other); + int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; + + bool operator ==(other) => (_givenHashCode != null && other is Mock) + ? _givenHashCode == other._givenHashCode + : identical(this, other); - String toString() => _givenName != null ? _givenName : runtimeType.toString(); + String toString() => _givenName != null ? _givenName : runtimeType.toString(); } named(dynamic mock, {String name, int hashCode}) => mock - .._givenName=name.._givenHashCode = hashCode; + .._givenName = name + .._givenHashCode = hashCode; -reset(var mock){ +reset(var mock) { mock._realCalls.clear(); mock._responses.clear(); } -clearInteractions(var mock){ +clearInteractions(var mock) { mock._realCalls.clear(); } -class PostExpectation { - thenReturn(expected){ - return _completeWhen((_)=>expected); +class PostExpectation { + thenReturn(expected) { + return _completeWhen((_) => expected); } - - thenAnswer(Answering answer){ + + thenAnswer(Answering answer) { return _completeWhen(answer); } @@ -70,51 +72,51 @@ class PostExpectation { _whenCall._setExpected(answer); var mock = _whenCall.mock; _whenCall = null; - _whenInProgress=false; + _whenInProgress = false; return mock; } } class InvocationMatcher { Invocation roleInvocation; - + InvocationMatcher(this.roleInvocation); - - bool matches(Invocation invocation){ - var isMatching = _isMethodMatches(invocation) && _isArgumentsMatches(invocation); - if(isMatching){ + + bool matches(Invocation invocation) { + var isMatching = + _isMethodMatches(invocation) && _isArgumentsMatches(invocation); + if (isMatching) { _captureArguments(invocation); } return isMatching; } bool _isMethodMatches(Invocation invocation) { - if(invocation.memberName!=roleInvocation.memberName){ + if (invocation.memberName != roleInvocation.memberName) { return false; } - if((invocation.isGetter!=roleInvocation.isGetter) - || (invocation.isSetter!=roleInvocation.isSetter) - || (invocation.isMethod!=roleInvocation.isMethod) - ){ - return false; + if ((invocation.isGetter != roleInvocation.isGetter) || + (invocation.isSetter != roleInvocation.isSetter) || + (invocation.isMethod != roleInvocation.isMethod)) { + return false; } return true; } void _captureArguments(Invocation invocation) { int index = 0; - for(var roleArg in roleInvocation.positionalArguments){ + for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; - if(roleArg is _ArgMatcher && roleArg._capture){ + if (roleArg is _ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } index++; } - for(var roleKey in roleInvocation.namedArguments.keys){ + for (var roleKey in roleInvocation.namedArguments.keys) { var roleArg = roleInvocation.namedArguments[roleKey]; var actArg = invocation.namedArguments[roleKey]; - if(roleArg is _ArgMatcher){ - if(roleArg is _ArgMatcher && roleArg._capture){ + if (roleArg is _ArgMatcher) { + if (roleArg is _ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } } @@ -122,24 +124,26 @@ class InvocationMatcher { } bool _isArgumentsMatches(Invocation invocation) { - if(invocation.positionalArguments.length!=roleInvocation.positionalArguments.length){ + if (invocation.positionalArguments.length != + roleInvocation.positionalArguments.length) { return false; } - if(invocation.namedArguments.length!=roleInvocation.namedArguments.length){ + if (invocation.namedArguments.length != + roleInvocation.namedArguments.length) { return false; } - int index=0; - for(var roleArg in roleInvocation.positionalArguments){ + int index = 0; + for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; - if(!isMatchingArg(roleArg, actArg)){ + if (!isMatchingArg(roleArg, actArg)) { return false; } index++; } - for(var roleKey in roleInvocation.namedArguments.keys){ + for (var roleKey in roleInvocation.namedArguments.keys) { var roleArg = roleInvocation.namedArguments[roleKey]; var actArg = invocation.namedArguments[roleKey]; - if(!isMatchingArg(roleArg, actArg)){ + if (!isMatchingArg(roleArg, actArg)) { return false; } } @@ -147,62 +151,64 @@ class InvocationMatcher { } bool isMatchingArg(roleArg, actArg) { - if(roleArg is _ArgMatcher){ - return roleArg._matcher==null || roleArg._matcher.matches(actArg, {}); + if (roleArg is _ArgMatcher) { + return roleArg._matcher == null || roleArg._matcher.matches(actArg, {}); // } else if(roleArg is Mock){ // return identical(roleArg, actArg); - }else{ + } else { return roleArg == actArg; } } } -class CannedResponse{ +class CannedResponse { InvocationMatcher matcher; Answering response; - - CannedResponse(this.matcher, this.response); + + CannedResponse(this.matcher, this.response); } -class _TimeStampProvider{ +class _TimeStampProvider { int _now = 0; - DateTime now(){ + DateTime now() { var candidate = new DateTime.now(); - if(candidate.millisecondsSinceEpoch<=_now){ - candidate = new DateTime.fromMillisecondsSinceEpoch(_now+1); + if (candidate.millisecondsSinceEpoch <= _now) { + candidate = new DateTime.fromMillisecondsSinceEpoch(_now + 1); } _now = candidate.millisecondsSinceEpoch; return candidate; } } -class RealCall{ +class RealCall { DateTime _timeStamp; final Mock mock; final Invocation invocation; bool verified = false; - RealCall(this.mock, this.invocation){ - _timeStamp = _timer.now(); + RealCall(this.mock, this.invocation) { + _timeStamp = _timer.now(); } - + DateTime get timeStamp => _timeStamp; - - String toString(){ + + String toString() { var verifiedText = verified ? "[VERIFIED] " : ""; - List posArgs = invocation.positionalArguments.map((v)=>v==null?"null":v.toString()).toList(); - List mapArgList = invocation.namedArguments.keys.map((key){ + List posArgs = invocation.positionalArguments + .map((v) => v == null ? "null" : v.toString()) + .toList(); + List mapArgList = invocation.namedArguments.keys.map((key) { return "${MirrorSystem.getName(key)}: ${invocation.namedArguments[key]}"; }).toList(growable: false); - if(mapArgList.isNotEmpty){ - posArgs.add("{${mapArgList.join(", ")}}"); + if (mapArgList.isNotEmpty) { + posArgs.add("{${mapArgList.join(", ")}}"); } String args = posArgs.join(", "); - String method = MirrorSystem.getName(invocation.memberName); - if(invocation.isMethod){ + String method = MirrorSystem.getName(invocation.memberName); + if (invocation.isMethod) { method = ".$method($args)"; - }else if(invocation.isGetter){ + } else if (invocation.isGetter) { method = ".$method"; - }else{ + } else { method = ".$method=$args"; } return "$verifiedText$mock$method"; @@ -213,9 +219,10 @@ class _WhenCall { Mock mock; Invocation whenInvocation; _WhenCall(this.mock, this.whenInvocation); - - void _setExpected(Answering answer){ - mock._setExpected(new CannedResponse(new InvocationMatcher(whenInvocation), answer)); + + void _setExpected(Answering answer) { + mock._setExpected( + new CannedResponse(new InvocationMatcher(whenInvocation), answer)); } } @@ -223,58 +230,64 @@ class _VerifyCall { Mock mock; Invocation verifyInvocation; List matchingInvocations; - - _VerifyCall(this.mock, this.verifyInvocation){ + + _VerifyCall(this.mock, this.verifyInvocation) { var expectedMatcher = new InvocationMatcher(verifyInvocation); - matchingInvocations = mock._realCalls.where((RealCall recordedInvocation){ - return !recordedInvocation.verified && expectedMatcher.matches(recordedInvocation.invocation);}).toList(); + matchingInvocations = mock._realCalls.where((RealCall recordedInvocation) { + return !recordedInvocation.verified && + expectedMatcher.matches(recordedInvocation.invocation); + }).toList(); } - - _findAfter(DateTime dt){ - return matchingInvocations.firstWhere((inv)=>!inv.verified && inv.timeStamp.isAfter(dt), orElse: ()=>null); + + _findAfter(DateTime dt) { + return matchingInvocations.firstWhere( + (inv) => !inv.verified && inv.timeStamp.isAfter(dt), + orElse: () => null); } - - _checkWith(bool never){ - if(!never && matchingInvocations.isEmpty){ + + _checkWith(bool never) { + if (!never && matchingInvocations.isEmpty) { var otherCallsText = ""; - if(mock._realCalls.isNotEmpty){ + if (mock._realCalls.isNotEmpty) { otherCallsText = " All calls: "; } var calls = mock._realCalls.join(", "); fail("No matching calls.$otherCallsText$calls"); } - if(never && matchingInvocations.isNotEmpty){ + if (never && matchingInvocations.isNotEmpty) { var calls = mock._realCalls.join(", "); fail("Unexpected calls. All calls: $calls"); } - matchingInvocations.forEach((inv){inv.verified=true;}); + matchingInvocations.forEach((inv) { + inv.verified = true; + }); } } -class _ArgMatcher{ +class _ArgMatcher { Matcher _matcher; bool _capture; - + _ArgMatcher(this._matcher, this._capture); } - get any => new _ArgMatcher(null, false); get captureAny => new _ArgMatcher(null, true); captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); argThat(Matcher matcher) => new _ArgMatcher(matcher, false); -class VerificationResult{ +class VerificationResult { List captured = []; int callCount; - - VerificationResult(this.callCount){ + + VerificationResult(this.callCount) { captured = new List.from(_capturedArgs, growable: false); _capturedArgs.clear(); } - - void called(matcher){ - expect(callCount, wrapMatcher(matcher), reason: "Unexpected number of calls"); + + void called(matcher) { + expect(callCount, wrapMatcher(matcher), + reason: "Unexpected number of calls"); } } @@ -289,64 +302,72 @@ Verification get verifyNever => _makeVerify(true); Verification get verify => _makeVerify(false); Verification _makeVerify(bool never) { - if(_verifyCalls.isNotEmpty){ + if (_verifyCalls.isNotEmpty) { throw new StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (mock){ + return (mock) { _verificationInProgress = false; - if(_verifyCalls.length==1){ + if (_verifyCalls.length == 1) { _VerifyCall verifyCall = _verifyCalls.removeLast(); - var result = new VerificationResult(verifyCall.matchingInvocations.length); + var result = + new VerificationResult(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; - }else{ + } else { fail("Used on non-mockito"); } }; } InOrderVerification get verifyInOrder { - if(_verifyCalls.isNotEmpty){ + if (_verifyCalls.isNotEmpty) { throw new StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (verifyCalls){ + return (verifyCalls) { _verificationInProgress = false; DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = new List.from(_verifyCalls); _verifyCalls.clear(); List matchedCalls = []; - for(_VerifyCall verifyCall in tmpVerifyCalls){ + for (_VerifyCall verifyCall in tmpVerifyCalls) { RealCall matched = verifyCall._findAfter(dt); - if(matched!=null){ + if (matched != null) { matchedCalls.add(matched); - dt=matched.timeStamp; - }else{ - Set mocks = tmpVerifyCalls.map((_VerifyCall vc)=>vc.mock).toSet(); - List allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false); - allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); + dt = matched.timeStamp; + } else { + Set mocks = + tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); + List allInvocations = + mocks.expand((m) => m._realCalls).toList(growable: false); + allInvocations + .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); String otherCalls = ""; - if(allInvocations.isNotEmpty){ + if (allInvocations.isNotEmpty) { otherCalls = " All calls: ${allInvocations.join(", ")}"; } - fail("Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); + fail( + "Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); } } - matchedCalls.forEach((rc){rc.verified=true;}); + matchedCalls.forEach((rc) { + rc.verified = true; + }); }; } verifyNoMoreInteractions(var mock) { - var unverified = mock._realCalls.where((inv)=>!inv.verified).toList(); - if(unverified.isNotEmpty){ - fail("No more calls expected, but following found: "+unverified.join()); - } + var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); + if (unverified.isNotEmpty) { + fail("No more calls expected, but following found: " + unverified.join()); + } } verifyZeroInteractions(var mock) { - if(mock._realCalls.isNotEmpty){ - fail("No interaction expected, but following found: "+mock._realCalls.join()); + if (mock._realCalls.isNotEmpty) { + fail("No interaction expected, but following found: " + + mock._realCalls.join()); } } @@ -354,16 +375,17 @@ typedef PostExpectation Expectation(x); Expectation get when { _whenInProgress = true; - return (_){ + return (_) { _whenInProgress = false; return new PostExpectation(); }; } -logInvocations(List mocks){ - List allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false); - allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp)); - allInvocations.forEach((inv){ - print(inv.toString()); +logInvocations(List mocks) { + List allInvocations = + mocks.expand((m) => m._realCalls).toList(growable: false); + allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); + allInvocations.forEach((inv) { + print(inv.toString()); }); } diff --git a/pkgs/mockito/test/mockitoSpec.dart b/pkgs/mockito/test/mockitoSpec.dart index e12dc56f1..d693e071d 100644 --- a/pkgs/mockito/test/mockitoSpec.dart +++ b/pkgs/mockito/test/mockitoSpec.dart @@ -2,14 +2,14 @@ import 'package:test/test.dart'; import '../lib/mockito.dart'; class RealClass { - String methodWithoutArgs() => "Real"; - String methodWithNormalArgs(int x) => "Real"; - String methodWithPositionalArgs(int x, [int y]) => "Real"; - String methodWithNamedArgs(int x, {int y}) => "Real"; - String get getter => "Real"; - void set setter(String arg){ + String methodWithoutArgs() => "Real"; + String methodWithNormalArgs(int x) => "Real"; + String methodWithPositionalArgs(int x, [int y]) => "Real"; + String methodWithNamedArgs(int x, {int y}) => "Real"; + String get getter => "Real"; + void set setter(String arg) { throw new StateError("I must be mocked"); - } + } } abstract class Foo { @@ -22,262 +22,292 @@ abstract class AbstractFoo implements Foo { String baz(); } -class MockFoo extends AbstractFoo with Mock { -} +class MockFoo extends AbstractFoo with Mock {} -class MockedClass extends Mock implements RealClass{} +class MockedClass extends Mock implements RealClass {} -expectFail(String expectedMessage, expectedToFail()){ - try{ +expectFail(String expectedMessage, expectedToFail()) { + try { expectedToFail(); fail("It was expected to fail!"); - }catch(e){ - if(!(e is TestFailure)){ + } catch (e) { + if (!(e is TestFailure)) { throw e; - }else{ - if(expectedMessage!=e.message){ + } else { + if (expectedMessage != e.message) { throw new TestFailure("Failed, but with wrong message: ${e.message}"); } } } } -main(){ +main() { RealClass mock; - - setUp((){ + + setUp(() { mock = new MockedClass(); }); - - group("mixin support", (){ - test("should work", (){ + + group("mixin support", () { + test("should work", () { var foo = new MockFoo(); when(foo.baz()).thenReturn('baz'); expect(foo.bar(), 'baz'); }); }); - - group("when()", (){ - test("should mock method without args", (){ + + group("when()", () { + test("should mock method without args", () { when(mock.methodWithoutArgs()).thenReturn("A"); expect(mock.methodWithoutArgs(), equals("A")); - }); - test("should mock method with normal args", (){ + }); + test("should mock method with normal args", () { when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); expect(mock.methodWithNormalArgs(43), isNull); expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); - }); - test("should mock method with positional args", (){ + }); + test("should mock method with positional args", () { when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); expect(mock.methodWithPositionalArgs(42), isNull); expect(mock.methodWithPositionalArgs(42, 18), isNull); expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); - }); - test("should mock method with named args", (){ + }); + test("should mock method with named args", () { when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); expect(mock.methodWithNamedArgs(42), isNull); - expect(mock.methodWithNamedArgs(42, y:18), isNull); - expect(mock.methodWithNamedArgs(42, y:17), equals("Why answer?")); - }); - test("should mock method with argument matcher", (){ - when(mock.methodWithNormalArgs(argThat(greaterThan(100)))).thenReturn("A lot!"); + expect(mock.methodWithNamedArgs(42, y: 18), isNull); + expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); + }); + test("should mock method with argument matcher", () { + when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) + .thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), isNull); expect(mock.methodWithNormalArgs(101), equals("A lot!")); - }); - test("should mock method with any argument matcher", (){ + }); + test("should mock method with any argument matcher", () { when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), equals("A lot!")); expect(mock.methodWithNormalArgs(101), equals("A lot!")); - }); - test("should mock method with mix of argument matchers and real things", (){ - when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)).thenReturn("A lot with 17"); + }); + test("should mock method with mix of argument matchers and real things", + () { + when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) + .thenReturn("A lot with 17"); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); - }); - test("should mock getter", (){ + }); + test("should mock getter", () { when(mock.getter).thenReturn("A"); expect(mock.getter, equals("A")); }); - test("should mock hashCode", (){ + test("should mock hashCode", () { named(mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); - test("should have hashCode when it is not mocked", (){ + test("should have hashCode when it is not mocked", () { expect(mock.hashCode, isNotNull); }); // test("should n't mock toString", (){ // when(mock.toString()).thenReturn("meow"); // expect(mock.toString(), equals("meow")); // }); - test("should have default toString when it is not mocked", (){ + test("should have default toString when it is not mocked", () { expect(mock.toString(), equals("MockedClass")); }); - test("should have toString as name when it is not mocked", (){ + test("should have toString as name when it is not mocked", () { named(mock, name: "Cat"); expect(mock.toString(), equals("Cat")); }); - test("should mock equals between mocks when givenHashCode is equals", (){ + test("should mock equals between mocks when givenHashCode is equals", () { var anotherMock = named(new MockedClass(), hashCode: 42); named(mock, hashCode: 42); - expect(mock==anotherMock, isTrue); + expect(mock == anotherMock, isTrue); }); - test("should use identical equality between it is not mocked", (){ + test("should use identical equality between it is not mocked", () { var anotherMock = new MockedClass(); - expect(mock==anotherMock, isFalse); - expect(mock==mock, isTrue); + expect(mock == anotherMock, isFalse); + expect(mock == mock, isTrue); }); //no need to mock setter, except if we will have spies later... - test("should mock method with calculated result", (){ - when(mock.methodWithNormalArgs(any)).thenAnswer((Invocation inv)=>inv.positionalArguments[0].toString()); + test("should mock method with calculated result", () { + when(mock.methodWithNormalArgs(any)).thenAnswer( + (Invocation inv) => inv.positionalArguments[0].toString()); expect(mock.methodWithNormalArgs(43), equals("43")); expect(mock.methodWithNormalArgs(42), equals("42")); - }); - test("should return mock to make simple oneline mocks", (){ - RealClass mockWithSetup = when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); + }); + test("should return mock to make simple oneline mocks", () { + RealClass mockWithSetup = + when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); - }); - test("should use latest matching when definition", (){ + }); + test("should use latest matching when definition", () { when(mock.methodWithoutArgs()).thenReturn("A"); when(mock.methodWithoutArgs()).thenReturn("B"); expect(mock.methodWithoutArgs(), equals("B")); - }); - test("should mock method with calculated result", (){ + }); + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); - }); - group("verify()", (){ - test("should verify method without args", (){ + group("verify()", () { + test("should verify method without args", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); - }); - test("should verify method with normal args", (){ + }); + test("should verify method with normal args", () { mock.methodWithNormalArgs(42); - expectFail("No matching calls. All calls: MockedClass.methodWithNormalArgs(42)", (){ - verify(mock.methodWithNormalArgs(43)); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNormalArgs(42)", + () { + verify(mock.methodWithNormalArgs(43)); }); verify(mock.methodWithNormalArgs(42)); - }); - test("should mock method with positional args", (){ + }); + test("should mock method with positional args", () { mock.methodWithPositionalArgs(42, 17); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", (){ - verify(mock.methodWithPositionalArgs(42)); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", + () { + verify(mock.methodWithPositionalArgs(42)); }); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", (){ - verify(mock.methodWithPositionalArgs(42, 18)); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", + () { + verify(mock.methodWithPositionalArgs(42, 18)); }); verify(mock.methodWithPositionalArgs(42, 17)); - }); - test("should mock method with named args", (){ + }); + test("should mock method with named args", () { mock.methodWithNamedArgs(42, y: 17); - expectFail("No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", (){ - verify(mock.methodWithNamedArgs(42)); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", + () { + verify(mock.methodWithNamedArgs(42)); }); - expectFail("No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", (){ - verify(mock.methodWithNamedArgs(42, y:18)); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", + () { + verify(mock.methodWithNamedArgs(42, y: 18)); }); - verify(mock.methodWithNamedArgs(42, y:17)); - }); - test("should mock method with argument matcher", (){ + verify(mock.methodWithNamedArgs(42, y: 17)); + }); + test("should mock method with argument matcher", () { mock.methodWithNormalArgs(100); - expectFail("No matching calls. All calls: MockedClass.methodWithNormalArgs(100)", (){ - verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); + expectFail( + "No matching calls. All calls: MockedClass.methodWithNormalArgs(100)", + () { + verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); }); verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100)))); - }); - test("should mock method with argument capturer", (){ + }); + test("should mock method with argument capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([50, 100])); - }); - test("should mock method with argument matcher and capturer", (){ + expect(verify(mock.methodWithNormalArgs(captureAny)).captured, + equals([50, 100])); + }); + test("should mock method with argument matcher and capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); var captured = 0; - expect(verify(mock.methodWithNormalArgs(captureThat(greaterThan(75)))).captured.single, equals(100)); - expect(verify(mock.methodWithNormalArgs(captureThat(lessThan(75)))).captured.single, equals(50)); - }); - test("should mock method with mix of argument matchers and real things", (){ + expect(verify(mock.methodWithNormalArgs( + captureThat(greaterThan(75)))).captured.single, equals(100)); + expect(verify(mock + .methodWithNormalArgs(captureThat(lessThan(75)))).captured.single, + equals(50)); + }); + test("should mock method with mix of argument matchers and real things", + () { mock.methodWithPositionalArgs(100, 17); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", (){ - verify(mock.methodWithPositionalArgs(argThat(greaterThanOrEqualTo(100)), 18)); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", + () { + verify(mock.methodWithPositionalArgs( + argThat(greaterThanOrEqualTo(100)), 18)); }); - expectFail("No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", (){ - verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); + expectFail( + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", + () { + verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); }); - verify(mock.methodWithPositionalArgs(argThat(greaterThanOrEqualTo(100)), 17)); - }); - test("should mock getter", (){ + verify(mock.methodWithPositionalArgs( + argThat(greaterThanOrEqualTo(100)), 17)); + }); + test("should mock getter", () { mock.getter; verify(mock.getter); }); - test("should mock setter", (){ + test("should mock setter", () { mock.setter = "A"; - expectFail("No matching calls. All calls: MockedClass.setter==A", (){ - verify(mock.setter="B"); + expectFail("No matching calls. All calls: MockedClass.setter==A", () { + verify(mock.setter = "B"); }); - verify(mock.setter="A"); + verify(mock.setter = "A"); }); }); - group("verify() qualifies", (){ - group("unqualified as at least one", (){ - test("zero fails", (){ - expectFail("No matching calls.", (){ - verify(mock.methodWithoutArgs()); + group("verify() qualifies", () { + group("unqualified as at least one", () { + test("zero fails", () { + expectFail("No matching calls.", () { + verify(mock.methodWithoutArgs()); }); }); - test("one passes", (){ + test("one passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); }); - test("more than one passes", (){ + test("more than one passes", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); }); }); - group("counts calls", (){ - test("zero fails", (){ - expectFail("No matching calls.", (){ - verify(mock.methodWithoutArgs()).called(1); + group("counts calls", () { + test("zero fails", () { + expectFail("No matching calls.", () { + verify(mock.methodWithoutArgs()).called(1); }); }); - test("one passes", (){ + test("one passes", () { mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()).called(1); + verify(mock.methodWithoutArgs()).called(1); }); - test("more than one fails", (){ + test("more than one fails", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); - expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", (){ - verify(mock.methodWithoutArgs()).called(1); + expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", + () { + verify(mock.methodWithoutArgs()).called(1); }); }); }); - group("verifyNever", (){ - test("zero passes", (){ + group("verifyNever", () { + test("zero passes", () { verifyNever(mock.methodWithoutArgs()); }); - test("one fails", (){ + test("one fails", () { mock.methodWithoutArgs(); - expectFail("Unexpected calls. All calls: MockedClass.methodWithoutArgs()", (){ - verifyNever(mock.methodWithoutArgs()); + expectFail( + "Unexpected calls. All calls: MockedClass.methodWithoutArgs()", () { + verifyNever(mock.methodWithoutArgs()); }); }); }); - group("doesn't count already verified again", (){ - test("fail case", (){ + group("doesn't count already verified again", () { + test("fail case", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); - expectFail("No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()", (){ - verify(mock.methodWithoutArgs()); + expectFail( + "No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()", + () { + verify(mock.methodWithoutArgs()); }); }); - test("pass case", (){ + test("pass case", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); mock.methodWithoutArgs(); @@ -285,96 +315,114 @@ main(){ }); }); }); - - group("verifyZeroInteractions()", (){ - test("never touched pass", (){ + + group("verifyZeroInteractions()", () { + test("never touched pass", () { verifyZeroInteractions(mock); }); - test("any touch fails", (){ + test("any touch fails", () { mock.methodWithoutArgs(); - expectFail("No interaction expected, but following found: MockedClass.methodWithoutArgs()", (){ - verifyZeroInteractions(mock); + expectFail( + "No interaction expected, but following found: MockedClass.methodWithoutArgs()", + () { + verifyZeroInteractions(mock); }); }); - test("verifired call fails", (){ + test("verifired call fails", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); - expectFail("No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", (){ - verifyZeroInteractions(mock); + expectFail( + "No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", + () { + verifyZeroInteractions(mock); }); }); }); - group("verifyNoMoreInteractions()", (){ - test("never touched pass", (){ + group("verifyNoMoreInteractions()", () { + test("never touched pass", () { verifyNoMoreInteractions(mock); }); - test("any unverified touch fails", (){ + test("any unverified touch fails", () { mock.methodWithoutArgs(); - expectFail("No more calls expected, but following found: MockedClass.methodWithoutArgs()", (){ - verifyNoMoreInteractions(mock); + expectFail( + "No more calls expected, but following found: MockedClass.methodWithoutArgs()", + () { + verifyNoMoreInteractions(mock); }); }); - test("verified touch passes", (){ + test("verified touch passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); verifyNoMoreInteractions(mock); }); }); - group("verifyInOrder()", (){ - test("right order passes", (){ + group("verifyInOrder()", () { + test("right order passes", () { mock.methodWithoutArgs(); mock.getter; verifyInOrder([mock.methodWithoutArgs(), mock.getter]); }); - test("wrong order fails", (){ + test("wrong order fails", () { mock.methodWithoutArgs(); mock.getter; - expectFail("Matching call #1 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.getter", (){ - verifyInOrder([mock.getter, mock.methodWithoutArgs()]); + expectFail( + "Matching call #1 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.getter", + () { + verifyInOrder([mock.getter, mock.methodWithoutArgs()]); }); }); - test("uncomplete fails", (){ + test("uncomplete fails", () { mock.methodWithoutArgs(); - expectFail("Matching call #1 not found. All calls: MockedClass.methodWithoutArgs()", (){ - verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + expectFail( + "Matching call #1 not found. All calls: MockedClass.methodWithoutArgs()", + () { + verifyInOrder([mock.methodWithoutArgs(), mock.getter]); }); }); - test("methods can be called again and again", (){ + test("methods can be called again and again", () { mock.methodWithoutArgs(); mock.getter; mock.methodWithoutArgs(); - verifyInOrder([mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + verifyInOrder( + [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); }); - test("methods can be called again and again - fail case", (){ + test("methods can be called again and again - fail case", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); mock.getter; - expectFail("Matching call #2 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), MockedClass.getter", (){ - verifyInOrder([mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + expectFail( + "Matching call #2 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), MockedClass.getter", + () { + verifyInOrder( + [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); }); }); }); - - group("capture", (){ - test("capture should work as captureOut", (){ + + group("capture", () { + test("capture should work as captureOut", () { mock.methodWithNormalArgs(42); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, equals(42)); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, + equals(42)); }); - test("should captureOut multiple arguments", (){ + test("should captureOut multiple arguments", () { mock.methodWithPositionalArgs(1, 2); - expect(verify(mock.methodWithPositionalArgs(captureAny, captureAny)).captured, equals([1, 2])); + expect(verify( + mock.methodWithPositionalArgs(captureAny, captureAny)).captured, + equals([1, 2])); }); - test("should captureOut with matching arguments", (){ + test("should captureOut with matching arguments", () { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); - expect(verify(mock.methodWithPositionalArgs(captureAny, captureAny)).captured, equals([2, 3])); + expect(verify( + mock.methodWithPositionalArgs(captureAny, captureAny)).captured, + equals([2, 3])); }); - test("should captureOut multiple invocations", (){ + test("should captureOut multiple invocations", () { mock.methodWithNormalArgs(1); mock.methodWithNormalArgs(2); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([1, 2])); + expect(verify(mock.methodWithNormalArgs(captureAny)).captured, + equals([1, 2])); }); }); - } - From 58d7ccc86ed6d7cd4e2cb9d70748f139893ff6cc Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 11 May 2015 09:13:15 -0700 Subject: [PATCH 016/607] cleanup .gitignore This package does not have js output IDE-specific exclusions should be done on the users machine --- pkgs/mockito/.gitignore | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index de7094865..ba3353329 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -3,15 +3,5 @@ build/ packages/ .pub/ -# Or the files created by dart2js. -*.dart.js -*.dart.precompiled.js -*.js_ -*.js.deps -*.js.map - -# IDE -.idea/ - # Include when developing application packages. pubspec.lock From e69e53aff01f992594eabcf1dfdad8c864b27f33 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 11 May 2015 09:13:45 -0700 Subject: [PATCH 017/607] set a tighter upper bound on matcher and test --- pkgs/mockito/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index f5768457e..9db502eac 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,11 +1,11 @@ name: mockito -version: 0.9.0 +version: 0.9.1-dev author: Dmitriy Fibulwinter description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito environment: sdk: '>=1.0.0 <2.0.0' dependencies: - matcher: '>=0.12.0 <1.0.0' + matcher: '>=0.12.0 <0.13.0' dev_dependencies: - test: '>=0.12.0 <1.0.0' + test: '>=0.12.0 <0.13.0' From 43ef451e57b6b5766b7f507f8064c754881f6619 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 11 May 2015 09:13:53 -0700 Subject: [PATCH 018/607] readme cleanup --- pkgs/mockito/README.md | 151 ++++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 77 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a1617a529..45a6ce9a3 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,6 +1,3 @@ -dart-mockito -============ - Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: @@ -11,47 +8,47 @@ Dart-mockito fixes it - stubbing and verifying are first-class citizens. ## Let's create mocks ```dart - import 'package:mockito/mockito.dart'; +import 'package:mockito/mockito.dart'; - //Real class - class Cat { - String sound() => "Meow"; - bool eatFood(String food, {bool hungry}) => true; - void sleep(){} - int lives = 9; - } +//Real class +class Cat { + String sound() => "Meow"; + bool eatFood(String food, {bool hungry}) => true; + void sleep(){} + int lives = 9; +} - //Mock class - class MockCat extends Mock implements Cat{} +//Mock class +class MockCat extends Mock implements Cat{} - //mock creation - var cat = new MockCat(); +//mock creation +var cat = new MockCat(); ``` ## Let's verify some behaviour! ```dart - //using mock object - cat.sound(); - //verify interaction - verify(cat.sound()); +//using mock object +cat.sound(); +//verify interaction +verify(cat.sound()); ``` Once created, mock will remember all interactions. Then you can selectively verify whatever interaction you are interested in. ## How about some stubbing? ```dart - //unstubbed methods return null - expect(cat.sound(), nullValue); - //stubbing - before execution - when(cat.sound()).thenReturn("Purr"); - expect(cat.sound(), "Purr"); - //you can call it again - expect(cat.sound(), "Purr"); - //let's change stub - when(cat.sound()).thenReturn("Meow"); - expect(cat.sound(), "Meow"); - //you can stub getters - when(cat.lives).thenReturn(9); - expect(cat.lives, 9); +//unstubbed methods return null +expect(cat.sound(), nullValue); +//stubbing - before execution +when(cat.sound()).thenReturn("Purr"); +expect(cat.sound(), "Purr"); +//you can call it again +expect(cat.sound(), "Purr"); +//let's change stub +when(cat.sound()).thenReturn("Meow"); +expect(cat.sound(), "Meow"); +//you can stub getters +when(cat.lives).thenReturn(9); +expect(cat.lives, 9); ``` By default, for all methods that return value, mock returns null. @@ -61,45 +58,45 @@ Last stubbing is more important - when you stubbed the same method with the same ## Argument matchers ```dart - //you can use arguments itself... - when(cat.eatFood("fish")).thenReturn(true); - //..or matchers - when(cat.eatFood(argThat(startsWith("dry"))).thenReturn(false); - //..or mix aguments with matchers - when(cat.eatFood(argThat(startsWith("dry")), true).thenReturn(true); - expect(cat.eatFood("fish"), isTrue); - expect(cat.eatFood("dry food"), isFalse); - expect(cat.eatFood("dry food", hungry: true), isTrue); - //you can also verify using an argument matcher - verify(cat.eatFood("fish")); - verify(cat.eatFood(argThat(contains("food")))); - //you can verify setters - cat.lives = 9; - verify(cat.lives=9); +//you can use arguments itself... +when(cat.eatFood("fish")).thenReturn(true); +//..or matchers +when(cat.eatFood(argThat(startsWith("dry"))).thenReturn(false); +//..or mix aguments with matchers +when(cat.eatFood(argThat(startsWith("dry")), true).thenReturn(true); +expect(cat.eatFood("fish"), isTrue); +expect(cat.eatFood("dry food"), isFalse); +expect(cat.eatFood("dry food", hungry: true), isTrue); +//you can also verify using an argument matcher +verify(cat.eatFood("fish")); +verify(cat.eatFood(argThat(contains("food")))); +//you can verify setters +cat.lives = 9; +verify(cat.lives=9); ``` Argument matchers allow flexible verification or stubbing ## Verifying exact number of invocations / at least x / never ```dart - cat.sound(); - cat.sound(); - //exact number of invocations - verify(cat.sound()).called(2); - //or using matcher - verify(cat.sound()).called(greaterThan(1)); - //or never called - verifyNever(cat.eatFood(any)); +cat.sound(); +cat.sound(); +//exact number of invocations +verify(cat.sound()).called(2); +//or using matcher +verify(cat.sound()).called(greaterThan(1)); +//or never called +verifyNever(cat.eatFood(any)); ``` ## Verification in order ```dart - cat.eatFood("Milk"); - cat.sound(); - cat.eatFood("Fish"); - verifyInOrder([ - cat.eatFood("Milk"), - cat.sound(), - cat.eatFood("Fish") - ]); +cat.eatFood("Milk"); +cat.sound(); +cat.eatFood("Fish"); +verifyInOrder([ + cat.eatFood("Milk"), + cat.sound(), + cat.eatFood("Fish") +]); ``` Verification in order is flexible - you don't have to verify all interactions one-by-one but only those that you are interested in testing in order. @@ -109,21 +106,21 @@ Verification in order is flexible - you don't have to verify all interactions on ``` ## Finding redundant invocations ```dart - cat.sound(); - verify(cat.sound()); - verifyNoMoreInteractions(cat); +cat.sound(); +verify(cat.sound()); +verifyNoMoreInteractions(cat); ``` ## Capturing arguments for further assertions ```dart - //simple capture - cat.eatFood("Fish"); - expect(verify(cat.eatFood(capture)).captured.single, "Fish"); - //capture multiple calls - cat.eatFood("Milk"); - cat.eatFood("Fish"); - expect(verify(cat.eatFood(capture)).captured, ["Milk", "Fish"]); - //conditional capture - cat.eatFood("Milk"); - cat.eatFood("Fish"); - expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); +//simple capture +cat.eatFood("Fish"); +expect(verify(cat.eatFood(capture)).captured.single, "Fish"); +//capture multiple calls +cat.eatFood("Milk"); +cat.eatFood("Fish"); +expect(verify(cat.eatFood(capture)).captured, ["Milk", "Fish"]); +//conditional capture +cat.eatFood("Milk"); +cat.eatFood("Fish"); +expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); ``` From 3f298fbf0915fef6e912e97371aabcf06e3c7a60 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 11 May 2015 09:16:37 -0700 Subject: [PATCH 019/607] make dependency on test a regular dependency --- pkgs/mockito/pubspec.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 9db502eac..97fd5f90d 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,6 +6,4 @@ homepage: https://github.com/fibulwinter/dart-mockito environment: sdk: '>=1.0.0 <2.0.0' dependencies: - matcher: '>=0.12.0 <0.13.0' -dev_dependencies: test: '>=0.12.0 <0.13.0' From 40e331aeeb595614f0bfb403b26fe062fabd82f1 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 11 May 2015 09:28:26 -0700 Subject: [PATCH 020/607] API cleanup --- pkgs/mockito/lib/mockito.dart | 44 +++++++++++++++-------------- pkgs/mockito/test/mockito_test.dart | 29 ++++++++++--------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index c255a4e75..fe6bfb6ec 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,18 +1,20 @@ library mockito; -import 'package:test/test.dart'; import 'dart:mirrors'; +import 'package:test/test.dart'; + +final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; +final _TimeStampProvider _timer = new _TimeStampProvider(); +final List _capturedArgs = []; + bool _whenInProgress = false; bool _verificationInProgress = false; _WhenCall _whenCall = null; -List<_VerifyCall> _verifyCalls = []; -_TimeStampProvider _timer = new _TimeStampProvider(); -List _capturedArgs = []; class Mock { - List _realCalls = []; - List _responses = []; + final List _realCalls = []; + final List _responses = []; String _givenName = null; int _givenHashCode = null; var _defaultResponse = () => new CannedResponse(null, (_) => null); @@ -46,16 +48,16 @@ class Mock { String toString() => _givenName != null ? _givenName : runtimeType.toString(); } -named(dynamic mock, {String name, int hashCode}) => mock +named(Mock mock, {String name, int hashCode}) => mock .._givenName = name .._givenHashCode = hashCode; -reset(var mock) { +void reset(Mock mock) { mock._realCalls.clear(); mock._responses.clear(); } -clearInteractions(var mock) { +void clearInteractions(Mock mock) { mock._realCalls.clear(); } @@ -78,7 +80,7 @@ class PostExpectation { } class InvocationMatcher { - Invocation roleInvocation; + final Invocation roleInvocation; InvocationMatcher(this.roleInvocation); @@ -216,8 +218,8 @@ class RealCall { } class _WhenCall { - Mock mock; - Invocation whenInvocation; + final Mock mock; + final Invocation whenInvocation; _WhenCall(this.mock, this.whenInvocation); void _setExpected(Answering answer) { @@ -227,8 +229,8 @@ class _WhenCall { } class _VerifyCall { - Mock mock; - Invocation verifyInvocation; + final Mock mock; + final Invocation verifyInvocation; List matchingInvocations; _VerifyCall(this.mock, this.verifyInvocation) { @@ -239,13 +241,13 @@ class _VerifyCall { }).toList(); } - _findAfter(DateTime dt) { + RealCall _findAfter(DateTime dt) { return matchingInvocations.firstWhere( (inv) => !inv.verified && inv.timeStamp.isAfter(dt), orElse: () => null); } - _checkWith(bool never) { + void _checkWith(bool never) { if (!never && matchingInvocations.isEmpty) { var otherCallsText = ""; if (mock._realCalls.isNotEmpty) { @@ -265,8 +267,8 @@ class _VerifyCall { } class _ArgMatcher { - Matcher _matcher; - bool _capture; + final Matcher _matcher; + final bool _capture; _ArgMatcher(this._matcher, this._capture); } @@ -357,14 +359,14 @@ InOrderVerification get verifyInOrder { }; } -verifyNoMoreInteractions(var mock) { +void verifyNoMoreInteractions(Mock mock) { var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); if (unverified.isNotEmpty) { fail("No more calls expected, but following found: " + unverified.join()); } } -verifyZeroInteractions(var mock) { +void verifyZeroInteractions(Mock mock) { if (mock._realCalls.isNotEmpty) { fail("No interaction expected, but following found: " + mock._realCalls.join()); @@ -381,7 +383,7 @@ Expectation get when { }; } -logInvocations(List mocks) { +void logInvocations(List mocks) { List allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 4d61b6271..98793fdf0 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -22,9 +22,13 @@ abstract class AbstractFoo implements Foo { String baz(); } -class MockFoo extends AbstractFoo with Mock {} +class MockFoo extends AbstractFoo with Mock { + noSuchMethod(i) => super.noSuchMethod(i); +} -class MockedClass extends Mock implements RealClass {} +class MockedClass extends Mock implements RealClass { + noSuchMethod(i) => super.noSuchMethod(i); +} expectFail(String expectedMessage, expectedToFail()) { try { @@ -41,7 +45,7 @@ expectFail(String expectedMessage, expectedToFail()) { } } -main() { +void main() { RealClass mock; setUp(() { @@ -102,7 +106,7 @@ main() { expect(mock.getter, equals("A")); }); test("should mock hashCode", () { - named(mock, hashCode: 42); + named(mock as Mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); test("should have hashCode when it is not mocked", () { @@ -116,12 +120,12 @@ main() { expect(mock.toString(), equals("MockedClass")); }); test("should have toString as name when it is not mocked", () { - named(mock, name: "Cat"); + named(mock as Mock, name: "Cat"); expect(mock.toString(), equals("Cat")); }); test("should mock equals between mocks when givenHashCode is equals", () { var anotherMock = named(new MockedClass(), hashCode: 42); - named(mock, hashCode: 42); + named(mock as Mock, hashCode: 42); expect(mock == anotherMock, isTrue); }); test("should use identical equality between it is not mocked", () { @@ -213,7 +217,6 @@ main() { test("should mock method with argument matcher and capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); - var captured = 0; expect(verify(mock.methodWithNormalArgs( captureThat(greaterThan(75)))).captured.single, equals(100)); expect(verify(mock @@ -318,14 +321,14 @@ main() { group("verifyZeroInteractions()", () { test("never touched pass", () { - verifyZeroInteractions(mock); + verifyZeroInteractions(mock as Mock); }); test("any touch fails", () { mock.methodWithoutArgs(); expectFail( "No interaction expected, but following found: MockedClass.methodWithoutArgs()", () { - verifyZeroInteractions(mock); + verifyZeroInteractions(mock as Mock); }); }); test("verifired call fails", () { @@ -334,26 +337,26 @@ main() { expectFail( "No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", () { - verifyZeroInteractions(mock); + verifyZeroInteractions(mock as Mock); }); }); }); group("verifyNoMoreInteractions()", () { test("never touched pass", () { - verifyNoMoreInteractions(mock); + verifyNoMoreInteractions(mock as Mock); }); test("any unverified touch fails", () { mock.methodWithoutArgs(); expectFail( "No more calls expected, but following found: MockedClass.methodWithoutArgs()", () { - verifyNoMoreInteractions(mock); + verifyNoMoreInteractions(mock as Mock); }); }); test("verified touch passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); - verifyNoMoreInteractions(mock); + verifyNoMoreInteractions(mock as Mock); }); }); group("verifyInOrder()", () { From 9b4ea48ae3320585f23ac0ed2a7c7fdba7676766 Mon Sep 17 00:00:00 2001 From: dvishnyakov Date: Wed, 13 May 2015 12:34:59 +1000 Subject: [PATCH 021/607] Added support for spy. --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/mockito.dart | 7 +++++++ pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/mockito_test.dart | 19 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ca63bc02f..ce1deb170 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.0 + +* Added support for spy. + ## 0.9.0 * Migrate from the unittest package to use the new test package. diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index c255a4e75..2c69a2069 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -59,6 +59,13 @@ clearInteractions(var mock) { mock._realCalls.clear(); } +dynamic spy(dynamic mock, dynamic spiedObject) { + var mirror = reflect(spiedObject); + mock._defaultResponse = () => new CannedResponse( + null, (Invocation realInvocation) => mirror.delegate(realInvocation)); + return mock; +} + class PostExpectation { thenReturn(expected) { return _completeWhen((_) => expected); diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index f5768457e..3fbf6b153 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 0.9.0 +version: 0.10.0 author: Dmitriy Fibulwinter description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 4d61b6271..30b9b7248 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -48,6 +48,25 @@ main() { mock = new MockedClass(); }); + group("spy", () { + setUp(() { + mock = spy(new MockedClass(), new RealClass()); + }); + + test("should delegate to real object by default", () { + expect(mock.methodWithoutArgs(), 'Real'); + }); + test("should record interactions delegated to real object", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + test("should behave as mock when expectation are set", () { + when(mock.methodWithoutArgs()).thenReturn('Spied'); + expect(mock.methodWithoutArgs(), 'Spied'); + }); + }); + + group("mixin support", () { test("should work", () { var foo = new MockFoo(); From 8c1335a1985dc85e236d4ed653be374935f35b6a Mon Sep 17 00:00:00 2001 From: dvishnyakov Date: Wed, 13 May 2015 12:41:59 +1000 Subject: [PATCH 022/607] README.md fix for spy() --- pkgs/mockito/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a1617a529..ebcbe05af 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -127,3 +127,14 @@ Verification in order is flexible - you don't have to verify all interactions on cat.eatFood("Fish"); expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); ``` +## Spy +```dart + //spy creation + var cat = spy(new MockCat(), new Cat()); + //stubbing - before execution + when(cat.sound()).thenReturn("Purr"); + //using mocked interaction + expect(cat.sound(), "Purr"); + //using real object + expect(cat.lives, 9); +``` From 1f564749e87a0fffadd45eac92ed5c9d5ab95fa6 Mon Sep 17 00:00:00 2001 From: dvishnyakov Date: Wed, 13 May 2015 13:12:35 +1000 Subject: [PATCH 023/607] Merge branch 'tweaks' of https://github.com/kevmoo/dart-mockito into kevmoo-tweaks --- pkgs/mockito/README.md | 16 ++++++++-------- pkgs/mockito/lib/mockito.dart | 16 ++++++++-------- pkgs/mockito/test/mockito_test.dart | 26 +++++++++++--------------- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index d392c7f37..d8dca1c49 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -126,12 +126,12 @@ expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); ``` ## Spy ```dart - //spy creation - var cat = spy(new MockCat(), new Cat()); - //stubbing - before execution - when(cat.sound()).thenReturn("Purr"); - //using mocked interaction - expect(cat.sound(), "Purr"); - //using real object - expect(cat.lives, 9); +//spy creation +var cat = spy(new MockCat(), new Cat()); +//stubbing - before execution +when(cat.sound()).thenReturn("Purr"); +//using mocked interaction +expect(cat.sound(), "Purr"); +//using real object +expect(cat.lives, 9); ``` diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 1798265a9..04bfeec0a 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -4,13 +4,13 @@ import 'dart:mirrors'; import 'package:test/test.dart'; -final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; -final _TimeStampProvider _timer = new _TimeStampProvider(); -final List _capturedArgs = []; bool _whenInProgress = false; bool _verificationInProgress = false; _WhenCall _whenCall = null; +final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; +final _TimeStampProvider _timer = new _TimeStampProvider(); +final List _capturedArgs = []; class Mock { final List _realCalls = []; @@ -48,16 +48,16 @@ class Mock { String toString() => _givenName != null ? _givenName : runtimeType.toString(); } -named(Mock mock, {String name, int hashCode}) => mock +named(var mock, {String name, int hashCode}) => mock .._givenName = name .._givenHashCode = hashCode; -void reset(Mock mock) { +void reset(var mock) { mock._realCalls.clear(); mock._responses.clear(); } -void clearInteractions(Mock mock) { +void clearInteractions(var mock) { mock._realCalls.clear(); } @@ -366,14 +366,14 @@ InOrderVerification get verifyInOrder { }; } -void verifyNoMoreInteractions(Mock mock) { +void verifyNoMoreInteractions(var mock) { var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); if (unverified.isNotEmpty) { fail("No more calls expected, but following found: " + unverified.join()); } } -void verifyZeroInteractions(Mock mock) { +void verifyZeroInteractions(var mock) { if (mock._realCalls.isNotEmpty) { fail("No interaction expected, but following found: " + mock._realCalls.join()); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 76b62a47d..75289bac6 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -22,13 +22,9 @@ abstract class AbstractFoo implements Foo { String baz(); } -class MockFoo extends AbstractFoo with Mock { - noSuchMethod(i) => super.noSuchMethod(i); -} +class MockFoo extends AbstractFoo with Mock {} -class MockedClass extends Mock implements RealClass { - noSuchMethod(i) => super.noSuchMethod(i); -} +class MockedClass extends Mock implements RealClass {} expectFail(String expectedMessage, expectedToFail()) { try { @@ -125,7 +121,7 @@ void main() { expect(mock.getter, equals("A")); }); test("should mock hashCode", () { - named(mock as Mock, hashCode: 42); + named(mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); test("should have hashCode when it is not mocked", () { @@ -139,12 +135,12 @@ void main() { expect(mock.toString(), equals("MockedClass")); }); test("should have toString as name when it is not mocked", () { - named(mock as Mock, name: "Cat"); + named(mock, name: "Cat"); expect(mock.toString(), equals("Cat")); }); test("should mock equals between mocks when givenHashCode is equals", () { var anotherMock = named(new MockedClass(), hashCode: 42); - named(mock as Mock, hashCode: 42); + named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); }); test("should use identical equality between it is not mocked", () { @@ -340,14 +336,14 @@ void main() { group("verifyZeroInteractions()", () { test("never touched pass", () { - verifyZeroInteractions(mock as Mock); + verifyZeroInteractions(mock); }); test("any touch fails", () { mock.methodWithoutArgs(); expectFail( "No interaction expected, but following found: MockedClass.methodWithoutArgs()", () { - verifyZeroInteractions(mock as Mock); + verifyZeroInteractions(mock); }); }); test("verifired call fails", () { @@ -356,26 +352,26 @@ void main() { expectFail( "No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", () { - verifyZeroInteractions(mock as Mock); + verifyZeroInteractions(mock); }); }); }); group("verifyNoMoreInteractions()", () { test("never touched pass", () { - verifyNoMoreInteractions(mock as Mock); + verifyNoMoreInteractions(mock); }); test("any unverified touch fails", () { mock.methodWithoutArgs(); expectFail( "No more calls expected, but following found: MockedClass.methodWithoutArgs()", () { - verifyNoMoreInteractions(mock as Mock); + verifyNoMoreInteractions(mock); }); }); test("verified touch passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); - verifyNoMoreInteractions(mock as Mock); + verifyNoMoreInteractions(mock); }); }); group("verifyInOrder()", () { From 5b926505c8be8c1007c9d21180f9bdcff2b348f2 Mon Sep 17 00:00:00 2001 From: dvishnyakov Date: Wed, 13 May 2015 14:16:45 +1000 Subject: [PATCH 024/607] Added explicit noSuchMethod override to tell Dart analyzer that we meant not to implement all methods, and not to hint/warn that methods are missing --- pkgs/mockito/README.md | 5 ++++- pkgs/mockito/test/mockito_test.dart | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index d8dca1c49..61ec6f94f 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -19,7 +19,10 @@ class Cat { } //Mock class -class MockCat extends Mock implements Cat{} +class MockCat extends Mock implements Cat{ + //this tells Dart analyzer you meant not to implement all methods, and not to hint/warn that methods are missing + noSuchMethod(i) => super.noSuchMethod(i); +} //mock creation var cat = new MockCat(); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 75289bac6..e3a1a4d6e 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -22,9 +22,13 @@ abstract class AbstractFoo implements Foo { String baz(); } -class MockFoo extends AbstractFoo with Mock {} +class MockFoo extends AbstractFoo with Mock { + noSuchMethod(i) => super.noSuchMethod(i); +} -class MockedClass extends Mock implements RealClass {} +class MockedClass extends Mock implements RealClass { + noSuchMethod(i) => super.noSuchMethod(i); +} expectFail(String expectedMessage, expectedToFail()) { try { From a2f07d2372eb979ec4369eaaa5beb9850f440690 Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 13 May 2015 08:37:29 -0700 Subject: [PATCH 025/607] Travis config. --- pkgs/mockito/.travis.yml | 3 +++ pkgs/mockito/tool/travis.sh | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 pkgs/mockito/.travis.yml create mode 100644 pkgs/mockito/tool/travis.sh diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml new file mode 100644 index 000000000..b1279b7e4 --- /dev/null +++ b/pkgs/mockito/.travis.yml @@ -0,0 +1,3 @@ +language: dart +script: ./tool/travis.sh +sudo: false diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh new file mode 100644 index 000000000..5387de554 --- /dev/null +++ b/pkgs/mockito/tool/travis.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Fast fail the script on failures. +set -e + +# Verify that the libraries are error free. +dartanalyzer --fatal-warnings \ + lib/mockito.dart \ + test/mockito_test.dart + +# Run the tests. +dart test/mockito_test.dart + From d60646b2589e0b7c15e8e1d9ea126c50b9b32f15 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Wed, 13 May 2015 08:55:07 -0700 Subject: [PATCH 026/607] Update README.md Pub badge. --- pkgs/mockito/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 61ec6f94f..a2dded1cb 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,5 +1,7 @@ Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). +[![Pub](https://img.shields.io/pub/v/mockito.svg)]() + Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. * Poor support from IDE: no code-completion, no hints on argument types, can't jump to definition From e4c0e489b40d15e45001d6b50f18e97afd0933cd Mon Sep 17 00:00:00 2001 From: pquitslund Date: Wed, 13 May 2015 09:37:46 -0700 Subject: [PATCH 027/607] +x bit --- pkgs/mockito/tool/travis.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 pkgs/mockito/tool/travis.sh diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh old mode 100644 new mode 100755 From d4c974add89322f5c4925d905361a8e54b690297 Mon Sep 17 00:00:00 2001 From: dvishnyakov Date: Thu, 14 May 2015 12:48:03 +1000 Subject: [PATCH 028/607] Added travis build shield. --- pkgs/mockito/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a2dded1cb..c60f8ab1a 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,6 +1,7 @@ Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). [![Pub](https://img.shields.io/pub/v/mockito.svg)]() +[![Build Status](https://travis-ci.org/fibulwinter/dart-mockito.svg?branch=master)](https://travis-ci.org/fibulwinter/dart-mockito) Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. From 654114f26384c8f37f4f7af4d0a67542ee813061 Mon Sep 17 00:00:00 2001 From: Matias Meno Date: Fri, 10 Jul 2015 16:41:26 +0200 Subject: [PATCH 029/607] Update README to add "How it works" section closes dart-lang/mockito#16 --- pkgs/mockito/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index c60f8ab1a..8c8084368 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -141,3 +141,30 @@ expect(cat.sound(), "Purr"); //using real object expect(cat.lives, 9); ``` +## How it works +The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to catch +all method invocations, and returns the value that you have configured beforehand with +`when()` calls. + +The implementation of `when()` is a bit more tricky. Take this example: + +```dart +//unstubbed methods return null +expect(cat.sound(), nullValue); +//stubbing - before execution +when(cat.sound()).thenReturn("Purr"); +``` + +Since `cat.sound()` returns `null`, how can the `when()` call configure it? + +It works, because `when` is not a function, but a top level getter that _returns_ a function. +Before returning the function, it sets a flag (`_whenInProgress`), so that all `Mock` objects +know to return a "matcher" (internally `_WhenCall`) instead of the expected value. As soon as +the function has been invoked `_whenInProgress` is set back to `false` and Mock objects behave +as normal. + +> Be careful never to write `when;` (without the function call) anywhere. This would set +> `_whenInProgress` to `true`, and the next mock invocation will return an unexpected value. + +You can look at the `when` and `Mock.noSuchMethod` implementations to see how it's done. +It's very straightforward. From 15b28f67b81de5cf8967b8cbf877ee591190d15d Mon Sep 17 00:00:00 2001 From: Matias Meno Date: Fri, 10 Jul 2015 19:04:59 +0200 Subject: [PATCH 030/607] Update README to add note about edge case usage --- pkgs/mockito/README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 8c8084368..cbdcae26f 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -163,8 +163,29 @@ know to return a "matcher" (internally `_WhenCall`) instead of the expected valu the function has been invoked `_whenInProgress` is set back to `false` and Mock objects behave as normal. -> Be careful never to write `when;` (without the function call) anywhere. This would set +> **Be careful** never to write `when;` (without the function call) anywhere. This would set > `_whenInProgress` to `true`, and the next mock invocation will return an unexpected value. +The same goes for "chaining" mock objects in a test call. This will fail: + +```dart +var mockUtils = new MockUtils(); +var mockStringUtils = new MockStringUtils(); +// Setting up mockUtils.stringUtils to return a mock StringUtils implementation +when(mockUtils.stringUtils).thenReturn(mockStringUtils); + +// Some tests + +// FAILS! +verify(mockUtils.stringUtils.uppercase()).called(1); +// Instead use this: +verify(mockStringUtils.uppercase()).called(1); +``` + +This fails, because `verify` sets an internal flag, so mock objects don't return their mocked +values anymore but their matchers. So `mockUtils.stringUtils` will *not* return the mocked +`stringUtils` object you put inside. + + You can look at the `when` and `Mock.noSuchMethod` implementations to see how it's done. It's very straightforward. From e5e3179f8988ba499e7cadc3f3f3702d90ec0d74 Mon Sep 17 00:00:00 2001 From: Jason Daly Date: Wed, 22 Jul 2015 12:02:36 -0700 Subject: [PATCH 031/607] Added an explicit check of named arguments to InvocationMatcher to ensure that the match will fail if the specified named arguments in the actual invocation differ from the mocked invocation. Updated tests to include test cases for the above change. Changed the top-level "any" and "captureAny" _ArgMatchers to use the "anything" Matcher from test, rather than using null and having to test for a null Matcher in isMatchingArg(). --- pkgs/mockito/lib/mockito.dart | 12 +++++++++--- pkgs/mockito/test/mockito_test.dart | 12 ++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 04bfeec0a..3c7313dc4 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -149,6 +149,12 @@ class InvocationMatcher { } index++; } + Set roleKeys = roleInvocation.namedArguments.keys.toSet(); + Set actKeys = invocation.namedArguments.keys.toSet(); + if (roleKeys.difference(actKeys).isNotEmpty || + actKeys.difference(roleKeys).isNotEmpty) { + return false; + } for (var roleKey in roleInvocation.namedArguments.keys) { var roleArg = roleInvocation.namedArguments[roleKey]; var actArg = invocation.namedArguments[roleKey]; @@ -161,7 +167,7 @@ class InvocationMatcher { bool isMatchingArg(roleArg, actArg) { if (roleArg is _ArgMatcher) { - return roleArg._matcher == null || roleArg._matcher.matches(actArg, {}); + return roleArg._matcher.matches(actArg, {}); // } else if(roleArg is Mock){ // return identical(roleArg, actArg); } else { @@ -280,8 +286,8 @@ class _ArgMatcher { _ArgMatcher(this._matcher, this._capture); } -get any => new _ArgMatcher(null, false); -get captureAny => new _ArgMatcher(null, true); +get any => new _ArgMatcher(anything, false); +get captureAny => new _ArgMatcher(anything, true); captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); argThat(Matcher matcher) => new _ArgMatcher(matcher, false); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index e3a1a4d6e..2b39cbefa 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -6,6 +6,7 @@ class RealClass { String methodWithNormalArgs(int x) => "Real"; String methodWithPositionalArgs(int x, [int y]) => "Real"; String methodWithNamedArgs(int x, {int y}) => "Real"; + String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; String get getter => "Real"; void set setter(String arg) { throw new StateError("I must be mocked"); @@ -112,6 +113,17 @@ void main() { expect(mock.methodWithNormalArgs(100), equals("A lot!")); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); + test("should mock method with multiple named args and matchers", (){ + when(mock.methodWithTwoNamedArgs(any, y: any)).thenReturn("x y"); + when(mock.methodWithTwoNamedArgs(any, z: any)).thenReturn("x z"); + expect(mock.methodWithTwoNamedArgs(42), isNull); + expect(mock.methodWithTwoNamedArgs(42, y:18), equals("x y")); + expect(mock.methodWithTwoNamedArgs(42, z:17), equals("x z")); + expect(mock.methodWithTwoNamedArgs(42, y:18, z:17), isNull); + when(mock.methodWithTwoNamedArgs(any, y: any, z: any)) + .thenReturn("x y z"); + expect(mock.methodWithTwoNamedArgs(42, y:18, z:17), equals("x y z")); + }); test("should mock method with mix of argument matchers and real things", () { when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) From 5f62c399487a72baebef654674b9b0a44061e1a8 Mon Sep 17 00:00:00 2001 From: deimon Date: Mon, 14 Sep 2015 12:43:08 +1000 Subject: [PATCH 032/607] 0.11.0 Equality matcher used by default to simplify matching collections as arguments. Should be non-breaking change in most cases, otherwise consider using `argThat(identical(arg))`. --- pkgs/mockito/CHANGELOG.md | 4 +++ pkgs/mockito/README.md | 6 +++++ pkgs/mockito/lib/mockito.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/mockito_test.dart | 41 +++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ce1deb170..4c1f15064 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.0 + +* Equality matcher used by default to simplify matching collections as arguments. Should be non-breaking change in most cases, otherwise consider using `argThat(identical(arg))`. + ## 0.10.0 * Added support for spy. diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index cbdcae26f..36e931606 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -17,6 +17,7 @@ import 'package:mockito/mockito.dart'; class Cat { String sound() => "Meow"; bool eatFood(String food, {bool hungry}) => true; + int walk(List places); void sleep(){} int lives = 9; } @@ -66,20 +67,25 @@ Last stubbing is more important - when you stubbed the same method with the same ```dart //you can use arguments itself... when(cat.eatFood("fish")).thenReturn(true); +//..or collections +when(cat.walk(["roof","tree"])).thenReturn(2); //..or matchers when(cat.eatFood(argThat(startsWith("dry"))).thenReturn(false); //..or mix aguments with matchers when(cat.eatFood(argThat(startsWith("dry")), true).thenReturn(true); expect(cat.eatFood("fish"), isTrue); +expect(cat.walk(["roof","tree"]), equals(2)); expect(cat.eatFood("dry food"), isFalse); expect(cat.eatFood("dry food", hungry: true), isTrue); //you can also verify using an argument matcher verify(cat.eatFood("fish")); +verify(cat.walk(["roof","tree"])); verify(cat.eatFood(argThat(contains("food")))); //you can verify setters cat.lives = 9; verify(cat.lives=9); ``` +By default equals matcher is used to argument matching (since 0.11.0). It simplifies matching for collections as arguments. If you need more strict matching consider use `argThat(identical(arg))`. Argument matchers allow flexible verification or stubbing ## Verifying exact number of invocations / at least x / never diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 3c7313dc4..1b367616a 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -171,7 +171,7 @@ class InvocationMatcher { // } else if(roleArg is Mock){ // return identical(roleArg, actArg); } else { - return roleArg == actArg; + return equals(roleArg).matches(actArg, {}); } } } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 34f009641..4596018d9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 0.10.1 +version: 0.11.0 author: Dmitriy Fibulwinter description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 2b39cbefa..03a2ed951 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -4,9 +4,11 @@ import 'package:mockito/mockito.dart'; class RealClass { String methodWithoutArgs() => "Real"; String methodWithNormalArgs(int x) => "Real"; + String methodWithListArgs(List x) => "Real"; String methodWithPositionalArgs(int x, [int y]) => "Real"; String methodWithNamedArgs(int x, {int y}) => "Real"; String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; + String methodWithObjArgs(RealClass x) => "Real"; String get getter => "Real"; void set setter(String arg) { throw new StateError("I must be mocked"); @@ -90,6 +92,12 @@ void main() { expect(mock.methodWithNormalArgs(43), isNull); expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); }); + test("should mock method with mock args", () { + var m1 = new MockedClass(); + when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); + expect(mock.methodWithObjArgs(new MockedClass()), isNull); + expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); + }); test("should mock method with positional args", () { when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); expect(mock.methodWithPositionalArgs(42), isNull); @@ -102,6 +110,11 @@ void main() { expect(mock.methodWithNamedArgs(42, y: 18), isNull); expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); }); + test("should mock method with List args", () { + when(mock.methodWithListArgs([42])).thenReturn("Ultimate answer"); + expect(mock.methodWithListArgs([43]), isNull); + expect(mock.methodWithListArgs([42]), equals("Ultimate answer")); + }); test("should mock method with argument matcher", () { when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) .thenReturn("A lot!"); @@ -113,6 +126,11 @@ void main() { expect(mock.methodWithNormalArgs(100), equals("A lot!")); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); + test("should mock method with any list argument matcher", () { + when(mock.methodWithListArgs(any)).thenReturn("A lot!"); + expect(mock.methodWithListArgs([42]), equals("A lot!")); + expect(mock.methodWithListArgs([43]), equals("A lot!")); + }); test("should mock method with multiple named args and matchers", (){ when(mock.methodWithTwoNamedArgs(any, y: any)).thenReturn("x y"); when(mock.methodWithTwoNamedArgs(any, z: any)).thenReturn("x z"); @@ -230,6 +248,25 @@ void main() { }); verify(mock.methodWithNamedArgs(42, y: 17)); }); + test("should mock method with mock args", () { + var m1 = named(new MockedClass(), name: "m1"); + mock.methodWithObjArgs(m1); + expectFail( + "No matching calls. All calls: MockedClass.methodWithObjArgs(m1)", + () { + verify(mock.methodWithObjArgs(new MockedClass())); + }); + verify(mock.methodWithObjArgs(m1)); + }); + test("should mock method with list args", () { + mock.methodWithListArgs([42]); + expectFail( + "No matching calls. All calls: MockedClass.methodWithListArgs([42])", + () { + verify(mock.methodWithListArgs([43])); + }); + verify(mock.methodWithListArgs([42])); + }); test("should mock method with argument matcher", () { mock.methodWithNormalArgs(100); expectFail( @@ -439,6 +476,10 @@ void main() { expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, equals(42)); }); + test("should captureOut list arguments", () { + mock.methodWithListArgs([42]); + expect(verify(mock.methodWithListArgs(captureAny)).captured.single, equals([42])); + }); test("should captureOut multiple arguments", () { mock.methodWithPositionalArgs(1, 2); expect(verify( From 116399554a002d01fe8a959d0b32638f0c3dd21b Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 2 Aug 2016 09:01:13 -0700 Subject: [PATCH 033/607] Move reflection-based methods to lib/src/spy.dart. --- pkgs/mockito/.gitignore | 30 +- pkgs/mockito/lib/mockito.dart | 408 +--------------------- pkgs/mockito/lib/mockito_no_mirrors.dart | 1 + pkgs/mockito/lib/src/mock.dart | 415 +++++++++++++++++++++++ pkgs/mockito/lib/src/spy.dart | 12 + 5 files changed, 456 insertions(+), 410 deletions(-) create mode 100644 pkgs/mockito/lib/mockito_no_mirrors.dart create mode 100644 pkgs/mockito/lib/src/mock.dart create mode 100644 pkgs/mockito/lib/src/spy.dart diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index ba3353329..a0e505d7f 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -1,7 +1,29 @@ -# Don’t commit the following directories created by pub. -build/ -packages/ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project .pub/ +build/ +**/packages/ -# Include when developing application packages. +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) pubspec.lock + +.idea diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 1b367616a..ccb197a95 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,406 +1,2 @@ -library mockito; - -import 'dart:mirrors'; - -import 'package:test/test.dart'; - - -bool _whenInProgress = false; -bool _verificationInProgress = false; -_WhenCall _whenCall = null; -final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; -final _TimeStampProvider _timer = new _TimeStampProvider(); -final List _capturedArgs = []; - -class Mock { - final List _realCalls = []; - final List _responses = []; - String _givenName = null; - int _givenHashCode = null; - var _defaultResponse = () => new CannedResponse(null, (_) => null); - - void _setExpected(CannedResponse cannedResponse) { - _responses.add(cannedResponse); - } - - dynamic noSuchMethod(Invocation invocation) { - if (_whenInProgress) { - _whenCall = new _WhenCall(this, invocation); - return null; - } else if (_verificationInProgress) { - _verifyCalls.add(new _VerifyCall(this, invocation)); - return null; - } else { - _realCalls.add(new RealCall(this, invocation)); - var cannedResponse = _responses.lastWhere( - (cr) => cr.matcher.matches(invocation), orElse: _defaultResponse); - var response = cannedResponse.response(invocation); - return response; - } - } - - int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; - - bool operator ==(other) => (_givenHashCode != null && other is Mock) - ? _givenHashCode == other._givenHashCode - : identical(this, other); - - String toString() => _givenName != null ? _givenName : runtimeType.toString(); -} - -named(var mock, {String name, int hashCode}) => mock - .._givenName = name - .._givenHashCode = hashCode; - -void reset(var mock) { - mock._realCalls.clear(); - mock._responses.clear(); -} - -void clearInteractions(var mock) { - mock._realCalls.clear(); -} - -dynamic spy(dynamic mock, dynamic spiedObject) { - var mirror = reflect(spiedObject); - mock._defaultResponse = () => new CannedResponse( - null, (Invocation realInvocation) => mirror.delegate(realInvocation)); - return mock; -} - -class PostExpectation { - thenReturn(expected) { - return _completeWhen((_) => expected); - } - - thenAnswer(Answering answer) { - return _completeWhen(answer); - } - - _completeWhen(Answering answer) { - _whenCall._setExpected(answer); - var mock = _whenCall.mock; - _whenCall = null; - _whenInProgress = false; - return mock; - } -} - -class InvocationMatcher { - final Invocation roleInvocation; - - InvocationMatcher(this.roleInvocation); - - bool matches(Invocation invocation) { - var isMatching = - _isMethodMatches(invocation) && _isArgumentsMatches(invocation); - if (isMatching) { - _captureArguments(invocation); - } - return isMatching; - } - - bool _isMethodMatches(Invocation invocation) { - if (invocation.memberName != roleInvocation.memberName) { - return false; - } - if ((invocation.isGetter != roleInvocation.isGetter) || - (invocation.isSetter != roleInvocation.isSetter) || - (invocation.isMethod != roleInvocation.isMethod)) { - return false; - } - return true; - } - - void _captureArguments(Invocation invocation) { - int index = 0; - for (var roleArg in roleInvocation.positionalArguments) { - var actArg = invocation.positionalArguments[index]; - if (roleArg is _ArgMatcher && roleArg._capture) { - _capturedArgs.add(actArg); - } - index++; - } - for (var roleKey in roleInvocation.namedArguments.keys) { - var roleArg = roleInvocation.namedArguments[roleKey]; - var actArg = invocation.namedArguments[roleKey]; - if (roleArg is _ArgMatcher) { - if (roleArg is _ArgMatcher && roleArg._capture) { - _capturedArgs.add(actArg); - } - } - } - } - - bool _isArgumentsMatches(Invocation invocation) { - if (invocation.positionalArguments.length != - roleInvocation.positionalArguments.length) { - return false; - } - if (invocation.namedArguments.length != - roleInvocation.namedArguments.length) { - return false; - } - int index = 0; - for (var roleArg in roleInvocation.positionalArguments) { - var actArg = invocation.positionalArguments[index]; - if (!isMatchingArg(roleArg, actArg)) { - return false; - } - index++; - } - Set roleKeys = roleInvocation.namedArguments.keys.toSet(); - Set actKeys = invocation.namedArguments.keys.toSet(); - if (roleKeys.difference(actKeys).isNotEmpty || - actKeys.difference(roleKeys).isNotEmpty) { - return false; - } - for (var roleKey in roleInvocation.namedArguments.keys) { - var roleArg = roleInvocation.namedArguments[roleKey]; - var actArg = invocation.namedArguments[roleKey]; - if (!isMatchingArg(roleArg, actArg)) { - return false; - } - } - return true; - } - - bool isMatchingArg(roleArg, actArg) { - if (roleArg is _ArgMatcher) { - return roleArg._matcher.matches(actArg, {}); -// } else if(roleArg is Mock){ -// return identical(roleArg, actArg); - } else { - return equals(roleArg).matches(actArg, {}); - } - } -} - -class CannedResponse { - InvocationMatcher matcher; - Answering response; - - CannedResponse(this.matcher, this.response); -} - -class _TimeStampProvider { - int _now = 0; - DateTime now() { - var candidate = new DateTime.now(); - if (candidate.millisecondsSinceEpoch <= _now) { - candidate = new DateTime.fromMillisecondsSinceEpoch(_now + 1); - } - _now = candidate.millisecondsSinceEpoch; - return candidate; - } -} - -class RealCall { - DateTime _timeStamp; - final Mock mock; - final Invocation invocation; - bool verified = false; - RealCall(this.mock, this.invocation) { - _timeStamp = _timer.now(); - } - - DateTime get timeStamp => _timeStamp; - - String toString() { - var verifiedText = verified ? "[VERIFIED] " : ""; - List posArgs = invocation.positionalArguments - .map((v) => v == null ? "null" : v.toString()) - .toList(); - List mapArgList = invocation.namedArguments.keys.map((key) { - return "${MirrorSystem.getName(key)}: ${invocation.namedArguments[key]}"; - }).toList(growable: false); - if (mapArgList.isNotEmpty) { - posArgs.add("{${mapArgList.join(", ")}}"); - } - String args = posArgs.join(", "); - String method = MirrorSystem.getName(invocation.memberName); - if (invocation.isMethod) { - method = ".$method($args)"; - } else if (invocation.isGetter) { - method = ".$method"; - } else { - method = ".$method=$args"; - } - return "$verifiedText$mock$method"; - } -} - -class _WhenCall { - final Mock mock; - final Invocation whenInvocation; - _WhenCall(this.mock, this.whenInvocation); - - void _setExpected(Answering answer) { - mock._setExpected( - new CannedResponse(new InvocationMatcher(whenInvocation), answer)); - } -} - -class _VerifyCall { - final Mock mock; - final Invocation verifyInvocation; - List matchingInvocations; - - _VerifyCall(this.mock, this.verifyInvocation) { - var expectedMatcher = new InvocationMatcher(verifyInvocation); - matchingInvocations = mock._realCalls.where((RealCall recordedInvocation) { - return !recordedInvocation.verified && - expectedMatcher.matches(recordedInvocation.invocation); - }).toList(); - } - - RealCall _findAfter(DateTime dt) { - return matchingInvocations.firstWhere( - (inv) => !inv.verified && inv.timeStamp.isAfter(dt), - orElse: () => null); - } - - void _checkWith(bool never) { - if (!never && matchingInvocations.isEmpty) { - var otherCallsText = ""; - if (mock._realCalls.isNotEmpty) { - otherCallsText = " All calls: "; - } - var calls = mock._realCalls.join(", "); - fail("No matching calls.$otherCallsText$calls"); - } - if (never && matchingInvocations.isNotEmpty) { - var calls = mock._realCalls.join(", "); - fail("Unexpected calls. All calls: $calls"); - } - matchingInvocations.forEach((inv) { - inv.verified = true; - }); - } -} - -class _ArgMatcher { - final Matcher _matcher; - final bool _capture; - - _ArgMatcher(this._matcher, this._capture); -} - -get any => new _ArgMatcher(anything, false); -get captureAny => new _ArgMatcher(anything, true); -captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); -argThat(Matcher matcher) => new _ArgMatcher(matcher, false); - -class VerificationResult { - List captured = []; - int callCount; - - VerificationResult(this.callCount) { - captured = new List.from(_capturedArgs, growable: false); - _capturedArgs.clear(); - } - - void called(matcher) { - expect(callCount, wrapMatcher(matcher), - reason: "Unexpected number of calls"); - } -} - -typedef dynamic Answering(Invocation realInvocation); - -typedef VerificationResult Verification(matchingInvocations); - -typedef void InOrderVerification(recordedInvocations); - -Verification get verifyNever => _makeVerify(true); - -Verification get verify => _makeVerify(false); - -Verification _makeVerify(bool never) { - if (_verifyCalls.isNotEmpty) { - throw new StateError(_verifyCalls.join()); - } - _verificationInProgress = true; - return (mock) { - _verificationInProgress = false; - if (_verifyCalls.length == 1) { - _VerifyCall verifyCall = _verifyCalls.removeLast(); - var result = - new VerificationResult(verifyCall.matchingInvocations.length); - verifyCall._checkWith(never); - return result; - } else { - fail("Used on non-mockito"); - } - }; -} - -InOrderVerification get verifyInOrder { - if (_verifyCalls.isNotEmpty) { - throw new StateError(_verifyCalls.join()); - } - _verificationInProgress = true; - return (verifyCalls) { - _verificationInProgress = false; - DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); - var tmpVerifyCalls = new List.from(_verifyCalls); - _verifyCalls.clear(); - List matchedCalls = []; - for (_VerifyCall verifyCall in tmpVerifyCalls) { - RealCall matched = verifyCall._findAfter(dt); - if (matched != null) { - matchedCalls.add(matched); - dt = matched.timeStamp; - } else { - Set mocks = - tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); - List allInvocations = - mocks.expand((m) => m._realCalls).toList(growable: false); - allInvocations - .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); - String otherCalls = ""; - if (allInvocations.isNotEmpty) { - otherCalls = " All calls: ${allInvocations.join(", ")}"; - } - fail( - "Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); - } - } - matchedCalls.forEach((rc) { - rc.verified = true; - }); - }; -} - -void verifyNoMoreInteractions(var mock) { - var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); - if (unverified.isNotEmpty) { - fail("No more calls expected, but following found: " + unverified.join()); - } -} - -void verifyZeroInteractions(var mock) { - if (mock._realCalls.isNotEmpty) { - fail("No interaction expected, but following found: " + - mock._realCalls.join()); - } -} - -typedef PostExpectation Expectation(x); - -Expectation get when { - _whenInProgress = true; - return (_) { - _whenInProgress = false; - return new PostExpectation(); - }; -} - -void logInvocations(List mocks) { - List allInvocations = - mocks.expand((m) => m._realCalls).toList(growable: false); - allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); - allInvocations.forEach((inv) { - print(inv.toString()); - }); -} +export 'src/mock.dart' hide setDefaultResponse; +export 'src/spy.dart'; diff --git a/pkgs/mockito/lib/mockito_no_mirrors.dart b/pkgs/mockito/lib/mockito_no_mirrors.dart new file mode 100644 index 000000000..17414e3cc --- /dev/null +++ b/pkgs/mockito/lib/mockito_no_mirrors.dart @@ -0,0 +1 @@ +export 'src/mock.dart' hide setDefaultResponse; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart new file mode 100644 index 000000000..71b8d9cea --- /dev/null +++ b/pkgs/mockito/lib/src/mock.dart @@ -0,0 +1,415 @@ +// Warning: Do not import dart:mirrors in this library, as it's exported via +// lib/mockito_no_mirrors.dart, which is used for Dart AOT projects such as +// Flutter. + +import 'package:test/test.dart'; + +bool _whenInProgress = false; +bool _verificationInProgress = false; +_WhenCall _whenCall = null; +final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; +final _TimeStampProvider _timer = new _TimeStampProvider(); +final List _capturedArgs = []; + +// Hidden from the public API, used by spy.dart. +void setDefaultResponse(Mock mock, dynamic defaultResponse) { + mock._defaultResponse = defaultResponse; +} + +class Mock { + static var _nullResponse = () => new CannedResponse(null, (_) => null); + + final List _realCalls = []; + final List _responses = []; + String _givenName = null; + int _givenHashCode = null; + + var _defaultResponse = _nullResponse; + + void _setExpected(CannedResponse cannedResponse) { + _responses.add(cannedResponse); + } + + dynamic noSuchMethod(Invocation invocation) { + if (_whenInProgress) { + _whenCall = new _WhenCall(this, invocation); + return null; + } else if (_verificationInProgress) { + _verifyCalls.add(new _VerifyCall(this, invocation)); + return null; + } else { + _realCalls.add(new RealCall(this, invocation)); + var cannedResponse = _responses.lastWhere( + (cr) => cr.matcher.matches(invocation), orElse: _defaultResponse); + var response = cannedResponse.response(invocation); + return response; + } + } + + int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; + + bool operator ==(other) => (_givenHashCode != null && other is Mock) + ? _givenHashCode == other._givenHashCode + : identical(this, other); + + String toString() => _givenName != null ? _givenName : runtimeType.toString(); +} + +named(var mock, {String name, int hashCode}) => mock + .._givenName = name + .._givenHashCode = hashCode; + +void reset(var mock) { + mock._realCalls.clear(); + mock._responses.clear(); +} + +void clearInteractions(var mock) { + mock._realCalls.clear(); +} + +class PostExpectation { + thenReturn(expected) { + return _completeWhen((_) => expected); + } + + thenAnswer(Answering answer) { + return _completeWhen(answer); + } + + _completeWhen(Answering answer) { + _whenCall._setExpected(answer); + var mock = _whenCall.mock; + _whenCall = null; + _whenInProgress = false; + return mock; + } +} + +class InvocationMatcher { + final Invocation roleInvocation; + + InvocationMatcher(this.roleInvocation); + + bool matches(Invocation invocation) { + var isMatching = + _isMethodMatches(invocation) && _isArgumentsMatches(invocation); + if (isMatching) { + _captureArguments(invocation); + } + return isMatching; + } + + bool _isMethodMatches(Invocation invocation) { + if (invocation.memberName != roleInvocation.memberName) { + return false; + } + if ((invocation.isGetter != roleInvocation.isGetter) || + (invocation.isSetter != roleInvocation.isSetter) || + (invocation.isMethod != roleInvocation.isMethod)) { + return false; + } + return true; + } + + void _captureArguments(Invocation invocation) { + int index = 0; + for (var roleArg in roleInvocation.positionalArguments) { + var actArg = invocation.positionalArguments[index]; + if (roleArg is _ArgMatcher && roleArg._capture) { + _capturedArgs.add(actArg); + } + index++; + } + for (var roleKey in roleInvocation.namedArguments.keys) { + var roleArg = roleInvocation.namedArguments[roleKey]; + var actArg = invocation.namedArguments[roleKey]; + if (roleArg is _ArgMatcher) { + if (roleArg is _ArgMatcher && roleArg._capture) { + _capturedArgs.add(actArg); + } + } + } + } + + bool _isArgumentsMatches(Invocation invocation) { + if (invocation.positionalArguments.length != + roleInvocation.positionalArguments.length) { + return false; + } + if (invocation.namedArguments.length != + roleInvocation.namedArguments.length) { + return false; + } + int index = 0; + for (var roleArg in roleInvocation.positionalArguments) { + var actArg = invocation.positionalArguments[index]; + if (!isMatchingArg(roleArg, actArg)) { + return false; + } + index++; + } + Set roleKeys = roleInvocation.namedArguments.keys.toSet(); + Set actKeys = invocation.namedArguments.keys.toSet(); + if (roleKeys.difference(actKeys).isNotEmpty || + actKeys.difference(roleKeys).isNotEmpty) { + return false; + } + for (var roleKey in roleInvocation.namedArguments.keys) { + var roleArg = roleInvocation.namedArguments[roleKey]; + var actArg = invocation.namedArguments[roleKey]; + if (!isMatchingArg(roleArg, actArg)) { + return false; + } + } + return true; + } + + bool isMatchingArg(roleArg, actArg) { + if (roleArg is _ArgMatcher) { + return roleArg._matcher.matches(actArg, {}); +// } else if(roleArg is Mock){ +// return identical(roleArg, actArg); + } else { + return equals(roleArg).matches(actArg, {}); + } + } +} + +class CannedResponse { + InvocationMatcher matcher; + Answering response; + + CannedResponse(this.matcher, this.response); +} + +class _TimeStampProvider { + int _now = 0; + DateTime now() { + var candidate = new DateTime.now(); + if (candidate.millisecondsSinceEpoch <= _now) { + candidate = new DateTime.fromMillisecondsSinceEpoch(_now + 1); + } + _now = candidate.millisecondsSinceEpoch; + return candidate; + } +} + +class RealCall { + // This used to use MirrorSystem, which cleans up the Symbol() wrapper. + // Since this toString method is just used in Mockito's own tests, it's not + // a big deal to massage the toString a bit. + // + // Input: Symbol("someMethodName") + static String _symbolToString(Symbol symbol) { + return symbol.toString().split('"')[1]; + } + + DateTime _timeStamp; + final Mock mock; + final Invocation invocation; + bool verified = false; + RealCall(this.mock, this.invocation) { + _timeStamp = _timer.now(); + } + + DateTime get timeStamp => _timeStamp; + + String toString() { + var verifiedText = verified ? "[VERIFIED] " : ""; + List posArgs = invocation.positionalArguments + .map((v) => v == null ? "null" : v.toString()) + .toList(); + List mapArgList = invocation.namedArguments.keys.map((key) { + return "${_symbolToString(key)}: ${invocation.namedArguments[key]}"; + }).toList(growable: false); + if (mapArgList.isNotEmpty) { + posArgs.add("{${mapArgList.join(", ")}}"); + } + String args = posArgs.join(", "); + String method = _symbolToString(invocation.memberName); + if (invocation.isMethod) { + method = ".$method($args)"; + } else if (invocation.isGetter) { + method = ".$method"; + } else { + method = ".$method=$args"; + } + return "$verifiedText$mock$method"; + } +} + +class _WhenCall { + final Mock mock; + final Invocation whenInvocation; + _WhenCall(this.mock, this.whenInvocation); + + void _setExpected(Answering answer) { + mock._setExpected( + new CannedResponse(new InvocationMatcher(whenInvocation), answer)); + } +} + +class _VerifyCall { + final Mock mock; + final Invocation verifyInvocation; + List matchingInvocations; + + _VerifyCall(this.mock, this.verifyInvocation) { + var expectedMatcher = new InvocationMatcher(verifyInvocation); + matchingInvocations = mock._realCalls.where((RealCall recordedInvocation) { + return !recordedInvocation.verified && + expectedMatcher.matches(recordedInvocation.invocation); + }).toList(); + } + + RealCall _findAfter(DateTime dt) { + return matchingInvocations.firstWhere( + (inv) => !inv.verified && inv.timeStamp.isAfter(dt), + orElse: () => null); + } + + void _checkWith(bool never) { + if (!never && matchingInvocations.isEmpty) { + var otherCallsText = ""; + if (mock._realCalls.isNotEmpty) { + otherCallsText = " All calls: "; + } + var calls = mock._realCalls.join(", "); + fail("No matching calls.$otherCallsText$calls"); + } + if (never && matchingInvocations.isNotEmpty) { + var calls = mock._realCalls.join(", "); + fail("Unexpected calls. All calls: $calls"); + } + matchingInvocations.forEach((inv) { + inv.verified = true; + }); + } +} + +class _ArgMatcher { + final Matcher _matcher; + final bool _capture; + + _ArgMatcher(this._matcher, this._capture); +} + +get any => new _ArgMatcher(anything, false); +get captureAny => new _ArgMatcher(anything, true); +captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); +argThat(Matcher matcher) => new _ArgMatcher(matcher, false); + +class VerificationResult { + List captured = []; + int callCount; + + VerificationResult(this.callCount) { + captured = new List.from(_capturedArgs, growable: false); + _capturedArgs.clear(); + } + + void called(matcher) { + expect(callCount, wrapMatcher(matcher), + reason: "Unexpected number of calls"); + } +} + +typedef dynamic Answering(Invocation realInvocation); + +typedef VerificationResult Verification(matchingInvocations); + +typedef void InOrderVerification(recordedInvocations); + +Verification get verifyNever => _makeVerify(true); + +Verification get verify => _makeVerify(false); + +Verification _makeVerify(bool never) { + if (_verifyCalls.isNotEmpty) { + throw new StateError(_verifyCalls.join()); + } + _verificationInProgress = true; + return (mock) { + _verificationInProgress = false; + if (_verifyCalls.length == 1) { + _VerifyCall verifyCall = _verifyCalls.removeLast(); + var result = + new VerificationResult(verifyCall.matchingInvocations.length); + verifyCall._checkWith(never); + return result; + } else { + fail("Used on non-mockito"); + } + }; +} + +InOrderVerification get verifyInOrder { + if (_verifyCalls.isNotEmpty) { + throw new StateError(_verifyCalls.join()); + } + _verificationInProgress = true; + return (verifyCalls) { + _verificationInProgress = false; + DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); + var tmpVerifyCalls = new List.from(_verifyCalls); + _verifyCalls.clear(); + List matchedCalls = []; + for (_VerifyCall verifyCall in tmpVerifyCalls) { + RealCall matched = verifyCall._findAfter(dt); + if (matched != null) { + matchedCalls.add(matched); + dt = matched.timeStamp; + } else { + Set mocks = + tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); + List allInvocations = + mocks.expand((m) => m._realCalls).toList(growable: false); + allInvocations + .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); + String otherCalls = ""; + if (allInvocations.isNotEmpty) { + otherCalls = " All calls: ${allInvocations.join(", ")}"; + } + fail( + "Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); + } + } + matchedCalls.forEach((rc) { + rc.verified = true; + }); + }; +} + +void verifyNoMoreInteractions(var mock) { + var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); + if (unverified.isNotEmpty) { + fail("No more calls expected, but following found: " + unverified.join()); + } +} + +void verifyZeroInteractions(var mock) { + if (mock._realCalls.isNotEmpty) { + fail("No interaction expected, but following found: " + + mock._realCalls.join()); + } +} + +typedef PostExpectation Expectation(x); + +Expectation get when { + _whenInProgress = true; + return (_) { + _whenInProgress = false; + return new PostExpectation(); + }; +} + +void logInvocations(List mocks) { + List allInvocations = + mocks.expand((m) => m._realCalls).toList(growable: false); + allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); + allInvocations.forEach((inv) { + print(inv.toString()); + }); +} diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart new file mode 100644 index 000000000..00177698a --- /dev/null +++ b/pkgs/mockito/lib/src/spy.dart @@ -0,0 +1,12 @@ +import 'dart:mirrors'; + +import 'mock.dart' show CannedResponse, setDefaultResponse; + +dynamic spy(dynamic mock, dynamic spiedObject) { + var mirror = reflect(spiedObject); + setDefaultResponse( + mock, + () => new CannedResponse(null, + (Invocation realInvocation) => mirror.delegate(realInvocation))); + return mock; +} From 500f2f6edc58f8235656c339eaf30e7da5de9b1e Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 2 Aug 2016 15:22:11 -0700 Subject: [PATCH 034/607] Add a comment to spy.dart, up pubspec to 11.1 --- pkgs/mockito/lib/src/spy.dart | 2 ++ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart index 00177698a..d7fbccddf 100644 --- a/pkgs/mockito/lib/src/spy.dart +++ b/pkgs/mockito/lib/src/spy.dart @@ -1,3 +1,5 @@ +// This file is intentionally separated from 'mock.dart' in order to avoid +// bringing in the mirrors dependency into mockito_no_mirrors.dart. import 'dart:mirrors'; import 'mock.dart' show CannedResponse, setDefaultResponse; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 4596018d9..4cdcff5ed 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 0.11.0 +version: 0.11.1 author: Dmitriy Fibulwinter description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito From 057eff79b540bf874dcadea60981a8c9703c4046 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 16 Jun 2016 08:03:05 -0700 Subject: [PATCH 035/607] Add strong mode-compliant 'typed' API --- pkgs/mockito/README.md | 89 +++++++++++++++- pkgs/mockito/lib/src/mock.dart | 151 ++++++++++++++++++++++++++++ pkgs/mockito/test/mockito_test.dart | 120 +++++++++++++++++++++- 3 files changed, 357 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 36e931606..883584e27 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -126,11 +126,11 @@ verifyNoMoreInteractions(cat); ```dart //simple capture cat.eatFood("Fish"); -expect(verify(cat.eatFood(capture)).captured.single, "Fish"); +expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); //capture multiple calls cat.eatFood("Milk"); cat.eatFood("Fish"); -expect(verify(cat.eatFood(capture)).captured, ["Milk", "Fish"]); +expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); //conditional capture cat.eatFood("Milk"); cat.eatFood("Fish"); @@ -147,6 +147,91 @@ expect(cat.sound(), "Purr"); //using real object expect(cat.lives, 9); ``` + +## Strong mode compliance + +Unfortunately, the use of the arg matchers in mock method calls (like `cat.eatFood(any)`) +violates the [Strong mode] type system. Specifically, if the method signature of a mocked +method has a parameter with a parameterized type (like `List`), then passing `any` or +`argThat` will result in a Strong mode warning: + +> [warning] Unsound implicit cast from dynamic to List<int> + +In order to write Strong mode-compliant tests with Mockito, you might need to use `typed`, +annotating it with a type parameter comment. Let's use a slightly different `Cat` class to +show some examples: + +```dart +class Cat { + bool eatFood(List foods, [List mixins]) => true; + int walk(List places, {Map gaits}) => 0; +} + +class MockCat extends Mock implements Cat {} + +var cat = new MockCat(); +``` + +OK, what if we try to stub using `any`: + +```dart +when(cat.eatFood(any)).thenReturn(true); +``` + +Let's analyze this code: + +``` +$ dartanalyzer --strong test/cat_test.dart +Analyzing [lib/cat_test.dart]... +[warning] Unsound implicit cast from dynamic to List (test/cat_test.dart, line 12, col 20) +1 warning found. +``` + +This code is not Strong mode-compliant. Let's change it to use `typed`: + +```dart +when(cat.eatFood(typed/*>*/(any))) +``` + +``` +$ dartanalyzer --strong test/cat_test.dart +Analyzing [lib/cat_test.dart]... +No issues found +``` + +Great! A little ugly, but it works. Here are some more examples: + +```dart +when(cat.eatFood(typed/*>*/(any), typed/*>*/(any))) + .thenReturn(true); +when(cat.eatFood(typed/*>*/(argThat(contains("fish"))))) + .thenReturn(true); +``` + +Named args require one more component: `typed` needs to know what named argument it is +being passed into: + +```dart +when(cat.walk( + typed/*>*/(any), + gaits: typed/*>*/(any), name: 'gaits')).thenReturn(true); +``` + +Note the `name` argument. Mockito should fail gracefully if you forget to name a `typed` +call passed in as a named argument, or name the argument incorrectly. + +One more note about the `typed` API: you cannot mix `typed` arguments with `null` +arguments: + +```dart +when(cat.eatFood(null, typed/*>*/(any))).thenReturn(true) // Throws! +when(cat.eatFood( + argThat(equals(null)), + typed/*>*/(any))).thenReturn(true); // Works. +``` + +[Strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md + ## How it works The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to catch all method invocations, and returns the value that you have configured beforehand with diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 71b8d9cea..d4bcfa9b7 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -10,6 +10,8 @@ _WhenCall _whenCall = null; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; final _TimeStampProvider _timer = new _TimeStampProvider(); final List _capturedArgs = []; +final List<_ArgMatcher> _typedArgs = <_ArgMatcher>[]; +final Map _typedNamedArgs = {}; // Hidden from the public API, used by spy.dart. void setDefaultResponse(Mock mock, dynamic defaultResponse) { @@ -31,6 +33,9 @@ class Mock { } dynamic noSuchMethod(Invocation invocation) { + if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) { + invocation = _reconstituteInvocation(invocation); + } if (_whenInProgress) { _whenCall = new _WhenCall(this, invocation); return null; @@ -55,6 +60,132 @@ class Mock { String toString() => _givenName != null ? _givenName : runtimeType.toString(); } +// Return a new [Invocation], reconstituted from [invocation], [_typedArgs], +// and [_typedNamedArgs]. +Invocation _reconstituteInvocation(Invocation invocation) { + var newInvocation = new FakeInvocation(invocation); + return newInvocation; +} + +/// An Invocation implementation that allows all attributes to be passed into +/// the constructor. +class FakeInvocation extends Invocation { + final Symbol memberName; + final Map namedArguments; + final List positionalArguments; + final bool isGetter; + final bool isMethod; + final bool isSetter; + + factory FakeInvocation(Invocation invocation) { + if (_typedArgs.isEmpty && _typedNamedArgs.isEmpty) { + throw new StateError("FakeInvocation called when no typed calls have been saved."); + } + + // Handle named arguments first, so that we can provide useful errors for + // the various bad states. If all is well with the named arguments, then we + // can process the positional arguments, and resort to more general errors + // if the state is still bad. + var namedArguments = _reconstituteNamedArgs(invocation); + var positionalArguments = _reconstitutePositionalArgs(invocation); + + _typedArgs.clear(); + _typedNamedArgs.clear(); + + return new FakeInvocation._( + invocation.memberName, + positionalArguments, + namedArguments, + invocation.isGetter, + invocation.isMethod, + invocation.isSetter); + } + + static Map _reconstituteNamedArgs(Invocation invocation) { + var namedArguments = {}; + var _typedNamedArgSymbols = _typedNamedArgs.keys.map((name) => new Symbol(name)); + invocation.namedArguments.forEach((name, arg) { + if (arg == null) { + if (!_typedNamedArgSymbols.contains(name)) { + // Incorrect usage of [typed], something like: + // `when(obj.fn(a: typed(any)))`. + throw new ArgumentError( + 'A typed argument was passed in as a named argument named "$name", ' + 'but did not a value for its name. Each typed argument that is ' + 'passed as a named argument needs to specify the `name` argument. ' + 'For example: `when(obj.fn(x: typed(any, name: "x")))`.'); + } + } else { + // Add each real named argument that was _not_ passed with [typed]. + namedArguments[name] = arg; + } + }); + + _typedNamedArgs.forEach((name, arg) { + Symbol nameSymbol = new Symbol(name); + if (!invocation.namedArguments.containsKey(nameSymbol)) { + // Incorrect usage of [name], something like: + // `when(obj.fn(typed(any, name: 'a')))`. + throw new ArgumentError( + 'A typed argument was declared with name $name, but was not passed ' + 'as an argument named $name.'); + } + if (invocation.namedArguments[nameSymbol] != null) { + // Incorrect usage of [name], something like: + // `when(obj.fn(a: typed(any, name: 'b'), b: "string"))`. + throw new ArgumentError( + 'A typed argument was declared with name $name, but a different ' + 'value (${invocation.namedArguments[nameSymbol]}) was passed as ' + '$name.'); + } + namedArguments[nameSymbol] = arg; + }); + + return namedArguments; + } + + static List _reconstitutePositionalArgs(Invocation invocation) { + var positionalArguments = []; + var nullPositionalArguments = + invocation.positionalArguments.where((arg) => arg == null); + if (_typedArgs.length != nullPositionalArguments.length) { + throw new ArgumentError( + 'null arguments are not allowed alongside typed(); use ' + '"typed(eq(null))"'); + } + int i = 0; + int j = 0; + while (i < _typedArgs.length && j < invocation.positionalArguments.length) { + var arg = _typedArgs[i]; + if (invocation.positionalArguments[j] == null) { + // [typed] was used; add the [_ArgMatcher] given to [typed]. + positionalArguments.add(arg); + i++; + j++; + } else { + // [typed] was not used; add the [_ArgMatcher] from [invocation]. + positionalArguments.add(invocation.positionalArguments[j]); + j++; + } + } + while (j < invocation.positionalArguments.length) { + // Some trailing non-[typed] arguments. + positionalArguments.add(invocation.positionalArguments[j]); + j++; + } + + return positionalArguments; + } + + FakeInvocation._( + this.memberName, + this.positionalArguments, + this.namedArguments, + this.isGetter, + this.isMethod, + this.isSetter); +} + named(var mock, {String name, int hashCode}) => mock .._givenName = name .._givenHashCode = hashCode; @@ -300,6 +431,15 @@ get captureAny => new _ArgMatcher(anything, true); captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); argThat(Matcher matcher) => new _ArgMatcher(matcher, false); +/*=T*/ typed/**/(_ArgMatcher matcher, {String name}) { + if (name == null) { + _typedArgs.add(matcher); + } else { + _typedNamedArgs[name] = matcher; + } + return null; +} + class VerificationResult { List captured = []; int callCount; @@ -413,3 +553,14 @@ void logInvocations(List mocks) { print(inv.toString()); }); } + +/// Should only be used during Mockito testing. +void resetMockitoState() { + _whenInProgress = false; + _verificationInProgress = false; + _whenCall = null; + _verifyCalls.clear(); + _capturedArgs.clear(); + _typedArgs.clear(); + _typedNamedArgs.clear(); +} diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 03a2ed951..461643f60 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -9,6 +9,14 @@ class RealClass { String methodWithNamedArgs(int x, {int y}) => "Real"; String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; String methodWithObjArgs(RealClass x) => "Real"; + // "SpecialArgs" here means type-parameterized args. But that makes for a long + // method name. + String methodWithSpecialArgs( + List w, List x, [List y, List z]) => "Real"; + // "SpecialNamedArgs" here means type-parameterized, named args. But that + // makes for a long method name. + String methodWithSpecialNamedArgs(List w, List x, {List y, List z}) => + "Real"; String get getter => "Real"; void set setter(String arg) { throw new StateError("I must be mocked"); @@ -55,6 +63,12 @@ void main() { mock = new MockedClass(); }); + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + group("spy", () { setUp(() { mock = spy(new MockedClass(), new RealClass()); @@ -204,6 +218,96 @@ void main() { when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); + test("should mock method with typed arg matchers", () { + when(mock.methodWithSpecialArgs( + typed/*>*/(any), typed/*>*/(any))) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialArgs([42], [43]), equals("A lot!")); + expect(mock.methodWithSpecialArgs([43], [44]), equals("A lot!")); + }); + test("should mock method with an optional typed arg matcher", () { + when(mock.methodWithSpecialArgs( + typed/*>*/(any), + typed/*>*/(any), + typed/*>*/(any))) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialArgs([42], [43], [44]), equals("A lot!")); + }); + test("should mock method with an optional typed arg matcher and an optional real arg", () { + when(mock.methodWithSpecialArgs( + typed/*>*/(any), + typed/*>*/(any), + [44], + typed/*>*/(any))) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialArgs([42], [43], [44], [45]), equals("A lot!")); + }); + test("should mock method with only some typed arg matchers", () { + when(mock.methodWithSpecialArgs( + typed/*>*/(any), [43], typed/*>*/(any))) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialArgs([42], [43], [44]), equals("A lot!")); + when(mock.methodWithSpecialArgs(typed/*>*/(any), [43])) + .thenReturn("A bunch!"); + expect(mock.methodWithSpecialArgs([42], [43]), equals("A bunch!")); + }); + test("should throw when [typed] used alongside [null].", () { + expect(() => when(mock.methodWithSpecialArgs( + typed/*>*/(any), null, typed/*>*/(any))), + throwsArgumentError); + expect(() => when(mock.methodWithSpecialArgs( + typed/*>*/(any), typed/*>*/(any), null)), + throwsArgumentError); + }); + test("should mock method when [typed] used alongside matched [null].", () { + when(mock.methodWithSpecialArgs( + typed/*>*/(any), argThat(equals(null)), typed/*>*/(any))) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialArgs([42], null, [44]), equals("A lot!")); + }); + test("should mock method with named, typed arg matcher", () { + when(mock.methodWithSpecialNamedArgs( + typed/*>*/(any), [43], y: typed/*>*/(any, name: "y"))) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialNamedArgs([42], [43], y: [44]), equals("A lot!")); + }); + test("should mock method with named, typed arg matcher and an arg matcher", () { + when( + mock.methodWithSpecialNamedArgs( + typed/*>*/(any), + [43], + y: typed/*>*/(any, name: "y"), + z: argThat(contains(45)))) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialNamedArgs([42], [43], y: [44], z: [45]), + equals("A lot!")); + }); + test("should mock method with named, typed arg matcher and a regular arg", () { + when( + mock.methodWithSpecialNamedArgs( + typed/*>*/(any), + [43], + y: typed/*>*/(any, name: "y"), + z: [45])) + .thenReturn("A lot!"); + expect(mock.methodWithSpecialNamedArgs([42], [43], y: [44], z: [45]), + equals("A lot!")); + }); + test("should throw when [typed] used as a named arg, without `name:`", () { + expect(() => when(mock.methodWithSpecialNamedArgs( + typed/*>*/(any), [43], y: typed/*>*/(any))), + throwsArgumentError); + }); + test("should throw when [typed] used as a positional arg, with `name:`", () { + expect(() => when(mock.methodWithSpecialNamedArgs( + typed/*>*/(any), typed/*>*/(any, name: "y"))), + throwsArgumentError); + }); + test("should throw when [typed] used as a named arg, with the wrong `name:`", () { + expect(() => when(mock.methodWithSpecialNamedArgs( + typed/*>*/(any), [43], y: typed/*>*/(any, name: "z"))), + throwsArgumentError); + }); }); group("verify()", () { @@ -319,6 +423,18 @@ void main() { }); verify(mock.setter = "A"); }); + test("should verify method with typed arg matchers", () { + mock.methodWithSpecialArgs([42], [43]); + verify(mock.methodWithSpecialArgs( + typed/*>*/(any), typed/*>*/(any))); + }); + test("should verify method with argument capturer", () { + mock.methodWithSpecialArgs([50], [17]); + mock.methodWithSpecialArgs([100], [17]); + expect(verify(mock.methodWithSpecialArgs( + typed/*>*/(captureAny), [17])).captured, + equals([[50], [100]])); + }); }); group("verify() qualifies", () { group("unqualified as at least one", () { @@ -478,7 +594,9 @@ void main() { }); test("should captureOut list arguments", () { mock.methodWithListArgs([42]); - expect(verify(mock.methodWithListArgs(captureAny)).captured.single, equals([42])); + expect(verify( + mock.methodWithListArgs(captureAny)).captured.single, + equals([42])); }); test("should captureOut multiple arguments", () { mock.methodWithPositionalArgs(1, 2); From 7f71c5c892f60201bae7db45cfa63fcbeab13761 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 7 Jul 2016 08:54:04 -0700 Subject: [PATCH 036/607] Address feedback, remove type annotations --- pkgs/mockito/CHANGELOG.md | 11 +++ pkgs/mockito/README.md | 19 +++-- pkgs/mockito/lib/src/mock.dart | 87 ++++++++++++----------- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/mockito_test.dart | 104 ++++++++++++---------------- 5 files changed, 110 insertions(+), 113 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 4c1f15064..9534f53eb 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.0.0 + +* Add a new `typed` API that is compatible with Dart Dev Compiler; documented in + README.md. + +## 0.11.1 + +* Move the reflection-based `spy` code into a private source file. Now + `package:mockito/mockito.dart` includes this reflection-based API, and a new + `package:mockito/mockito_no_mirrors.dart` doesn't require mirrors. + ## 0.11.0 * Equality matcher used by default to simplify matching collections as arguments. Should be non-breaking change in most cases, otherwise consider using `argThat(identical(arg))`. diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 883584e27..d5e362ceb 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -190,7 +190,7 @@ Analyzing [lib/cat_test.dart]... This code is not Strong mode-compliant. Let's change it to use `typed`: ```dart -when(cat.eatFood(typed/*>*/(any))) +when(cat.eatFood(typed(any))) ``` ``` @@ -202,32 +202,29 @@ No issues found Great! A little ugly, but it works. Here are some more examples: ```dart -when(cat.eatFood(typed/*>*/(any), typed/*>*/(any))) - .thenReturn(true); -when(cat.eatFood(typed/*>*/(argThat(contains("fish"))))) - .thenReturn(true); +when(cat.eatFood(typed(any), typed(any))).thenReturn(true); +when(cat.eatFood(typed(argThat(contains("fish"))))).thenReturn(true); ``` Named args require one more component: `typed` needs to know what named argument it is being passed into: ```dart -when(cat.walk( - typed/*>*/(any), - gaits: typed/*>*/(any), name: 'gaits')).thenReturn(true); +when(cat.walk(typed(any), gaits: typed(any, named: 'gaits'))) + .thenReturn(true); ``` -Note the `name` argument. Mockito should fail gracefully if you forget to name a `typed` +Note the `named` argument. Mockito should fail gracefully if you forget to name a `typed` call passed in as a named argument, or name the argument incorrectly. One more note about the `typed` API: you cannot mix `typed` arguments with `null` arguments: ```dart -when(cat.eatFood(null, typed/*>*/(any))).thenReturn(true) // Throws! +when(cat.eatFood(null, typed(any))).thenReturn(true); // Throws! when(cat.eatFood( argThat(equals(null)), - typed/*>*/(any))).thenReturn(true); // Works. + typed(any))).thenReturn(true); // Works. ``` [Strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index d4bcfa9b7..c1bf3860c 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -34,7 +34,7 @@ class Mock { dynamic noSuchMethod(Invocation invocation) { if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) { - invocation = _reconstituteInvocation(invocation); + invocation = new _InvocationForTypedArguments(invocation); } if (_whenInProgress) { _whenCall = new _WhenCall(this, invocation); @@ -60,16 +60,9 @@ class Mock { String toString() => _givenName != null ? _givenName : runtimeType.toString(); } -// Return a new [Invocation], reconstituted from [invocation], [_typedArgs], -// and [_typedNamedArgs]. -Invocation _reconstituteInvocation(Invocation invocation) { - var newInvocation = new FakeInvocation(invocation); - return newInvocation; -} - -/// An Invocation implementation that allows all attributes to be passed into -/// the constructor. -class FakeInvocation extends Invocation { +/// An Invocation implementation that takes arguments from [_typedArgs] and +/// [_typedNamedArgs]. +class _InvocationForTypedArguments extends Invocation { final Symbol memberName; final Map namedArguments; final List positionalArguments; @@ -77,9 +70,10 @@ class FakeInvocation extends Invocation { final bool isMethod; final bool isSetter; - factory FakeInvocation(Invocation invocation) { + factory _InvocationForTypedArguments(Invocation invocation) { if (_typedArgs.isEmpty && _typedNamedArgs.isEmpty) { - throw new StateError("FakeInvocation called when no typed calls have been saved."); + throw new StateError( + "_InvocationForTypedArguments called when no typed calls have been saved."); } // Handle named arguments first, so that we can provide useful errors for @@ -92,7 +86,7 @@ class FakeInvocation extends Invocation { _typedArgs.clear(); _typedNamedArgs.clear(); - return new FakeInvocation._( + return new _InvocationForTypedArguments._( invocation.memberName, positionalArguments, namedArguments, @@ -101,9 +95,17 @@ class FakeInvocation extends Invocation { invocation.isSetter); } + // Reconstitutes the named arguments in an invocation from [_typedNamedArgs]. + // + // The namedArguments in [invocation] which are null should be represented + // by a stored value in [_typedNamedArgs]. The null presumably came from + // [typed]. static Map _reconstituteNamedArgs(Invocation invocation) { var namedArguments = {}; var _typedNamedArgSymbols = _typedNamedArgs.keys.map((name) => new Symbol(name)); + + // Iterate through [invocation]'s named args, validate them, and add them + // to the return map. invocation.namedArguments.forEach((name, arg) { if (arg == null) { if (!_typedNamedArgSymbols.contains(name)) { @@ -111,9 +113,9 @@ class FakeInvocation extends Invocation { // `when(obj.fn(a: typed(any)))`. throw new ArgumentError( 'A typed argument was passed in as a named argument named "$name", ' - 'but did not a value for its name. Each typed argument that is ' - 'passed as a named argument needs to specify the `name` argument. ' - 'For example: `when(obj.fn(x: typed(any, name: "x")))`.'); + 'but did not pass a value for `named`. Each typed argument that is ' + 'passed as a named argument needs to specify the `named` argument. ' + 'For example: `when(obj.fn(x: typed(any, named: "x")))`.'); } } else { // Add each real named argument that was _not_ passed with [typed]. @@ -121,22 +123,24 @@ class FakeInvocation extends Invocation { } }); + // Iterate through the stored named args (stored with [typed]), validate + // them, and add them to the return map. _typedNamedArgs.forEach((name, arg) { Symbol nameSymbol = new Symbol(name); if (!invocation.namedArguments.containsKey(nameSymbol)) { - // Incorrect usage of [name], something like: - // `when(obj.fn(typed(any, name: 'a')))`. throw new ArgumentError( - 'A typed argument was declared with name $name, but was not passed ' - 'as an argument named $name.'); + 'A typed argument was declared as named $name, but was not passed ' + 'as an argument named $name.\n\n' + 'BAD: when(obj.fn(typed(any, named: "a")))\n' + 'GOOD: when(obj.fn(a: typed(any, named: "a")))'); } if (invocation.namedArguments[nameSymbol] != null) { - // Incorrect usage of [name], something like: - // `when(obj.fn(a: typed(any, name: 'b'), b: "string"))`. throw new ArgumentError( - 'A typed argument was declared with name $name, but a different ' + 'A typed argument was declared as named $name, but a different ' 'value (${invocation.namedArguments[nameSymbol]}) was passed as ' - '$name.'); + '$name.\n\n' + 'BAD: when(obj.fn(b: typed(any, name: "a")))\n' + 'GOOD: when(obj.fn(b: typed(any, name: "b")))'); } namedArguments[nameSymbol] = arg; }); @@ -153,31 +157,32 @@ class FakeInvocation extends Invocation { 'null arguments are not allowed alongside typed(); use ' '"typed(eq(null))"'); } - int i = 0; - int j = 0; - while (i < _typedArgs.length && j < invocation.positionalArguments.length) { - var arg = _typedArgs[i]; - if (invocation.positionalArguments[j] == null) { + int typedIndex = 0; + int positionalIndex = 0; + while (typedIndex < _typedArgs.length && + positionalIndex < invocation.positionalArguments.length) { + var arg = _typedArgs[typedIndex]; + if (invocation.positionalArguments[positionalIndex] == null) { // [typed] was used; add the [_ArgMatcher] given to [typed]. positionalArguments.add(arg); - i++; - j++; + typedIndex++; + positionalIndex++; } else { // [typed] was not used; add the [_ArgMatcher] from [invocation]. - positionalArguments.add(invocation.positionalArguments[j]); - j++; + positionalArguments.add(invocation.positionalArguments[positionalIndex]); + positionalIndex++; } } - while (j < invocation.positionalArguments.length) { + while (positionalIndex < invocation.positionalArguments.length) { // Some trailing non-[typed] arguments. - positionalArguments.add(invocation.positionalArguments[j]); - j++; + positionalArguments.add(invocation.positionalArguments[positionalIndex]); + positionalIndex++; } return positionalArguments; } - FakeInvocation._( + _InvocationForTypedArguments._( this.memberName, this.positionalArguments, this.namedArguments, @@ -431,11 +436,11 @@ get captureAny => new _ArgMatcher(anything, true); captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); argThat(Matcher matcher) => new _ArgMatcher(matcher, false); -/*=T*/ typed/**/(_ArgMatcher matcher, {String name}) { - if (name == null) { +/*=T*/ typed/**/(_ArgMatcher matcher, {String named}) { + if (named == null) { _typedArgs.add(matcher); } else { - _typedNamedArgs[name] = matcher; + _typedNamedArgs[named] = matcher; } return null; } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 4cdcff5ed..be8576c33 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 0.11.1 +version: 1.0.0 author: Dmitriy Fibulwinter description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 461643f60..fe20df24e 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -11,11 +11,11 @@ class RealClass { String methodWithObjArgs(RealClass x) => "Real"; // "SpecialArgs" here means type-parameterized args. But that makes for a long // method name. - String methodWithSpecialArgs( + String typeParameterizedFn( List w, List x, [List y, List z]) => "Real"; // "SpecialNamedArgs" here means type-parameterized, named args. But that // makes for a long method name. - String methodWithSpecialNamedArgs(List w, List x, {List y, List z}) => + String typeParameterizedNamedFn(List w, List x, {List y, List z}) => "Real"; String get getter => "Real"; void set setter(String arg) { @@ -219,93 +219,78 @@ void main() { expect(mock.methodWithNormalArgs(43), equals("43")); }); test("should mock method with typed arg matchers", () { - when(mock.methodWithSpecialArgs( - typed/*>*/(any), typed/*>*/(any))) + when(mock.typeParameterizedFn(typed(any), typed(any))) .thenReturn("A lot!"); - expect(mock.methodWithSpecialArgs([42], [43]), equals("A lot!")); - expect(mock.methodWithSpecialArgs([43], [44]), equals("A lot!")); + expect(mock.typeParameterizedFn([42], [43]), equals("A lot!")); + expect(mock.typeParameterizedFn([43], [44]), equals("A lot!")); }); test("should mock method with an optional typed arg matcher", () { - when(mock.methodWithSpecialArgs( - typed/*>*/(any), - typed/*>*/(any), - typed/*>*/(any))) + when(mock.typeParameterizedFn(typed(any), typed(any), typed(any))) .thenReturn("A lot!"); - expect(mock.methodWithSpecialArgs([42], [43], [44]), equals("A lot!")); + expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); }); test("should mock method with an optional typed arg matcher and an optional real arg", () { - when(mock.methodWithSpecialArgs( - typed/*>*/(any), - typed/*>*/(any), - [44], - typed/*>*/(any))) + when(mock.typeParameterizedFn(typed(any), typed(any), [44], typed(any))) .thenReturn("A lot!"); - expect(mock.methodWithSpecialArgs([42], [43], [44], [45]), equals("A lot!")); + expect(mock.typeParameterizedFn([42], [43], [44], [45]), equals("A lot!")); }); test("should mock method with only some typed arg matchers", () { - when(mock.methodWithSpecialArgs( - typed/*>*/(any), [43], typed/*>*/(any))) + when(mock.typeParameterizedFn(typed(any), [43], typed(any))) .thenReturn("A lot!"); - expect(mock.methodWithSpecialArgs([42], [43], [44]), equals("A lot!")); - when(mock.methodWithSpecialArgs(typed/*>*/(any), [43])) + expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); + when(mock.typeParameterizedFn(typed(any), [43])) .thenReturn("A bunch!"); - expect(mock.methodWithSpecialArgs([42], [43]), equals("A bunch!")); + expect(mock.typeParameterizedFn([42], [43]), equals("A bunch!")); }); test("should throw when [typed] used alongside [null].", () { - expect(() => when(mock.methodWithSpecialArgs( - typed/*>*/(any), null, typed/*>*/(any))), + expect(() => when(mock.typeParameterizedFn(typed(any), null, typed(any))), throwsArgumentError); - expect(() => when(mock.methodWithSpecialArgs( - typed/*>*/(any), typed/*>*/(any), null)), + expect(() => when(mock.typeParameterizedFn(typed(any), typed(any), null)), throwsArgumentError); }); test("should mock method when [typed] used alongside matched [null].", () { - when(mock.methodWithSpecialArgs( - typed/*>*/(any), argThat(equals(null)), typed/*>*/(any))) + when(mock.typeParameterizedFn( + typed(any), argThat(equals(null)), typed(any))) .thenReturn("A lot!"); - expect(mock.methodWithSpecialArgs([42], null, [44]), equals("A lot!")); + expect(mock.typeParameterizedFn([42], null, [44]), equals("A lot!")); }); test("should mock method with named, typed arg matcher", () { - when(mock.methodWithSpecialNamedArgs( - typed/*>*/(any), [43], y: typed/*>*/(any, name: "y"))) + when(mock.typeParameterizedNamedFn( + typed(any), [43], y: typed(any, named: "y"))) .thenReturn("A lot!"); - expect(mock.methodWithSpecialNamedArgs([42], [43], y: [44]), equals("A lot!")); + expect(mock.typeParameterizedNamedFn([42], [43], y: [44]), equals("A lot!")); }); test("should mock method with named, typed arg matcher and an arg matcher", () { when( - mock.methodWithSpecialNamedArgs( - typed/*>*/(any), - [43], - y: typed/*>*/(any, name: "y"), - z: argThat(contains(45)))) + mock.typeParameterizedNamedFn( + typed(any), [43], + y: typed(any, named: "y"), z: argThat(contains(45)))) .thenReturn("A lot!"); - expect(mock.methodWithSpecialNamedArgs([42], [43], y: [44], z: [45]), + expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); test("should mock method with named, typed arg matcher and a regular arg", () { when( - mock.methodWithSpecialNamedArgs( - typed/*>*/(any), - [43], - y: typed/*>*/(any, name: "y"), - z: [45])) + mock.typeParameterizedNamedFn( + typed(any), [43], + y: typed(any, named: "y"), z: [45])) .thenReturn("A lot!"); - expect(mock.methodWithSpecialNamedArgs([42], [43], y: [44], z: [45]), + expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); - test("should throw when [typed] used as a named arg, without `name:`", () { - expect(() => when(mock.methodWithSpecialNamedArgs( - typed/*>*/(any), [43], y: typed/*>*/(any))), + test("should throw when [typed] used as a named arg, without `named:`", () { + expect(() => when(mock.typeParameterizedNamedFn( + typed(any), [43], y: typed(any))), throwsArgumentError); }); - test("should throw when [typed] used as a positional arg, with `name:`", () { - expect(() => when(mock.methodWithSpecialNamedArgs( - typed/*>*/(any), typed/*>*/(any, name: "y"))), + test("should throw when [typed] used as a positional arg, with `named:`", () { + expect(() => when(mock.typeParameterizedNamedFn( + typed(any), typed(any, named: "y"))), throwsArgumentError); }); - test("should throw when [typed] used as a named arg, with the wrong `name:`", () { - expect(() => when(mock.methodWithSpecialNamedArgs( - typed/*>*/(any), [43], y: typed/*>*/(any, name: "z"))), + test("should throw when [typed] used as a named arg, with the wrong `named:`", () { + expect(() => when(mock.typeParameterizedNamedFn( + typed(any), [43], y: typed(any, named: "z"))), throwsArgumentError); }); }); @@ -424,15 +409,14 @@ void main() { verify(mock.setter = "A"); }); test("should verify method with typed arg matchers", () { - mock.methodWithSpecialArgs([42], [43]); - verify(mock.methodWithSpecialArgs( - typed/*>*/(any), typed/*>*/(any))); + mock.typeParameterizedFn([42], [43]); + verify(mock.typeParameterizedFn(typed(any), typed(any))); }); test("should verify method with argument capturer", () { - mock.methodWithSpecialArgs([50], [17]); - mock.methodWithSpecialArgs([100], [17]); - expect(verify(mock.methodWithSpecialArgs( - typed/*>*/(captureAny), [17])).captured, + mock.typeParameterizedFn([50], [17]); + mock.typeParameterizedFn([100], [17]); + expect(verify(mock.typeParameterizedFn( + typed(captureAny), [17])).captured, equals([[50], [100]])); }); }); From 715ba107f53c80cc1e639a798a6d47d203657d3b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 10 Aug 2016 09:28:47 -0700 Subject: [PATCH 037/607] Remove test/packages --- pkgs/mockito/test/packages | 1 - 1 file changed, 1 deletion(-) delete mode 120000 pkgs/mockito/test/packages diff --git a/pkgs/mockito/test/packages b/pkgs/mockito/test/packages deleted file mode 120000 index a16c40501..000000000 --- a/pkgs/mockito/test/packages +++ /dev/null @@ -1 +0,0 @@ -../packages \ No newline at end of file From a0642b292ad4ed6826e4506582ee7485534232eb Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 22 Sep 2016 19:37:44 -0700 Subject: [PATCH 038/607] Add 'thenThrow' --- pkgs/mockito/README.md | 3 +++ pkgs/mockito/lib/src/mock.dart | 6 ++++++ pkgs/mockito/test/mockito_test.dart | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index d5e362ceb..b1c3a2dbc 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -56,6 +56,9 @@ expect(cat.sound(), "Meow"); //you can stub getters when(cat.lives).thenReturn(9); expect(cat.lives, 9); +//you can stub a method to throw +when(cat.lives).thenThrow(new RangeError('Boo')); +expect(() => cat.lives, throwsRangeError); ``` By default, for all methods that return value, mock returns null. diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index c1bf3860c..654946025 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -209,6 +209,12 @@ class PostExpectation { return _completeWhen((_) => expected); } + thenThrow(throwable) { + return _completeWhen((_) { + throw throwable; + }); + } + thenAnswer(Answering answer) { return _completeWhen(answer); } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index fe20df24e..14faa80d4 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -197,6 +197,10 @@ void main() { expect(mock == mock, isTrue); }); //no need to mock setter, except if we will have spies later... + test("should mock method with thrown result", () { + when(mock.methodWithNormalArgs(any)).thenThrow(new StateError('Boo')); + expect(() => mock.methodWithNormalArgs(42), throwsStateError); + }); test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(any)).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); From 78f149aae22adf9dad4a64c77133f90fc0fb4c3d Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 22 Sep 2016 19:08:48 -0700 Subject: [PATCH 039/607] Add docs for the argument matchers, and thenAnswer() --- pkgs/mockito/README.md | 10 ++++++---- pkgs/mockito/lib/src/mock.dart | 13 ++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index b1c3a2dbc..974fb7d50 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -23,10 +23,7 @@ class Cat { } //Mock class -class MockCat extends Mock implements Cat{ - //this tells Dart analyzer you meant not to implement all methods, and not to hint/warn that methods are missing - noSuchMethod(i) => super.noSuchMethod(i); -} +class MockCat extends Mock implements Cat {} //mock creation var cat = new MockCat(); @@ -59,6 +56,11 @@ expect(cat.lives, 9); //you can stub a method to throw when(cat.lives).thenThrow(new RangeError('Boo')); expect(() => cat.lives, throwsRangeError); +//we can calculate a response at call time: +var responses = ["Purr", "Meow"]; +when(cat.sound()).thenAnswer(() => responses.removeAt(0)); +expect(cat.sound(), "Purr"); +expect(cat.sound(), "Meow"); ``` By default, for all methods that return value, mock returns null. diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 654946025..5ac33fcf6 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -437,11 +437,22 @@ class _ArgMatcher { _ArgMatcher(this._matcher, this._capture); } +/// An argument matcher that matches any argument passed in "this" position. get any => new _ArgMatcher(anything, false); + +/// An argument matcher that matches any argument passed in "this" position, and +/// captures the argument for later access with `captured`. get captureAny => new _ArgMatcher(anything, true); -captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); + +/// An argument matcher that matches an argument that matches [matcher]. argThat(Matcher matcher) => new _ArgMatcher(matcher, false); +/// An argument matcher that matches an argument that matches [matcher], and +/// captures the argument for later access with `captured`. +captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); + +/// A Strong-mode safe argument matcher that wraps other argument matchers. +/// See the README for a full explanation. /*=T*/ typed/**/(_ArgMatcher matcher, {String named}) { if (named == null) { _typedArgs.add(matcher); From a43829da138904ec4292a10e731e00afde9161c3 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 27 Sep 2016 09:48:12 -0700 Subject: [PATCH 040/607] Bumping to 1.0.1 --- pkgs/mockito/CHANGELOG.md | 6 ++++++ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9534f53eb..d20582bd9 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.0.1 + +* Add a new `thenThrow` method to the API. +* Document `thenAnswer` in the README. +* Add more dartdoc. + ## 1.0.0 * Add a new `typed` API that is compatible with Dart Dev Compiler; documented in diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index be8576c33..1223353a2 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 1.0.0 +version: 1.0.1 author: Dmitriy Fibulwinter description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito From aa89e5de5e51128236c265a64033ee4f05049bd4 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 27 Sep 2016 19:57:13 -0700 Subject: [PATCH 041/607] Replace Mock with a documented class --- pkgs/mockito/.gitignore | 7 +++-- pkgs/mockito/lib/src/typed_mock.dart | 42 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 pkgs/mockito/lib/src/typed_mock.dart diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index a0e505d7f..8d4854698 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -4,9 +4,9 @@ .buildlog .packages .project -.pub/ -build/ -**/packages/ +.pub +**/build +**/packages # Files created by dart2js # (Most Dart developers will use pub build to compile Dart, use/modify these @@ -26,4 +26,5 @@ doc/api/ # (Library packages only! Remove pattern if developing an application package) pubspec.lock +*.iml .idea diff --git a/pkgs/mockito/lib/src/typed_mock.dart b/pkgs/mockito/lib/src/typed_mock.dart new file mode 100644 index 000000000..8fed60fcb --- /dev/null +++ b/pkgs/mockito/lib/src/typed_mock.dart @@ -0,0 +1,42 @@ +import 'package:meta/meta.dart'; + +/// Extend or mixin this class to mark the implementation as a [Mock]. +/// +/// A mocked class implements all fields and methods with a default +/// implementation that does not throw a [NoSuchMethodError], and may be further +/// customized at runtime to define how it may behave using [when] or [given]. +/// +/// __Example use__: +/// // Real class. +/// class Cat { +/// String getSound() => 'Meow'; +/// } +/// +/// // Mock class. +/// class MockCat extends Mock implements Cat {} +/// +/// void main() { +/// // Create a new mocked Cat at runtime. +/// var cat = new MockCat(); +/// +/// // When 'getSound' is called, return 'Woof' +/// when(cat.getSound()).thenReturn('Woof'); +/// +/// // Try making a Cat sound... +/// print(cat.getSound()); // Prints 'Woof' +/// } +/// +/// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of +/// runtime reflection, and causes sub-standard code to be generated. As such, +/// [Mock] 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). +class Mock { + @override + @visibleForTesting + noSuchMethod(Invocation inv) { + // noSuchMethod is that 'magic' that allows us to ignore implementing fields + // and methods and instead define them later at compile-time per instance. + // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH + } +} From 8b292e65b0e703501908f0fefbb2c93b42862ede Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 27 Sep 2016 20:24:34 -0700 Subject: [PATCH 042/607] More internal changes --- pkgs/mockito/lib/src/mock.dart | 63 ++++++++++++++++++++++++---- pkgs/mockito/lib/src/typed_mock.dart | 42 ------------------- pkgs/mockito/pubspec.yaml | 9 +++- pkgs/mockito/test/mockito_test.dart | 8 +--- 4 files changed, 65 insertions(+), 57 deletions(-) delete mode 100644 pkgs/mockito/lib/src/typed_mock.dart diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 5ac33fcf6..a359c3381 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -2,6 +2,7 @@ // lib/mockito_no_mirrors.dart, which is used for Dart AOT projects such as // Flutter. +import 'package:meta/meta.dart'; import 'package:test/test.dart'; bool _whenInProgress = false; @@ -14,28 +15,64 @@ final List<_ArgMatcher> _typedArgs = <_ArgMatcher>[]; final Map _typedNamedArgs = {}; // Hidden from the public API, used by spy.dart. -void setDefaultResponse(Mock mock, dynamic defaultResponse) { +void setDefaultResponse(Mock mock, CannedResponse defaultResponse()) { mock._defaultResponse = defaultResponse; } +/// Extend or mixin this class to mark the implementation as a [Mock]. +/// +/// A mocked class implements all fields and methods with a default +/// implementation that does not throw a [NoSuchMethodError], and may be further +/// customized at runtime to define how it may behave using [when]. +/// +/// __Example use__: +/// // Real class. +/// class Cat { +/// String getSound() => 'Meow'; +/// } +/// +/// // Mock class. +/// class MockCat extends Mock implements Cat {} +/// +/// void main() { +/// // Create a new mocked Cat at runtime. +/// var cat = new MockCat(); +/// +/// // When 'getSound' is called, return 'Woof' +/// when(cat.getSound()).thenReturn('Woof'); +/// +/// // Try making a Cat sound... +/// print(cat.getSound()); // Prints 'Woof' +/// } +/// +/// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of +/// runtime reflection, and causes sub-standard code to be generated. As such, +/// [Mock] 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). class Mock { - static var _nullResponse = () => new CannedResponse(null, (_) => null); + static CannedResponse _nullResponse() { + return new CannedResponse(null, (_) => null); + } final List _realCalls = []; final List _responses = []; String _givenName = null; int _givenHashCode = null; - var _defaultResponse = _nullResponse; + _ReturnsCannedResponse _defaultResponse = _nullResponse; void _setExpected(CannedResponse cannedResponse) { _responses.add(cannedResponse); } - dynamic noSuchMethod(Invocation invocation) { - if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) { - invocation = new _InvocationForTypedArguments(invocation); - } + @override + @visibleForTesting + noSuchMethod(Invocation invocation) { + // noSuchMethod is that 'magic' that allows us to ignore implementing fields + // and methods and instead define them later at compile-time per instance. + // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH + invocation = _useTypedInvocationIfSet(invocation); if (_whenInProgress) { _whenCall = new _WhenCall(this, invocation); return null; @@ -60,6 +97,18 @@ class Mock { String toString() => _givenName != null ? _givenName : runtimeType.toString(); } +typedef CannedResponse _ReturnsCannedResponse(); + +// When using the typed() matcher, we transform our invocation to have knowledge +// of which arguments are wrapped with typed() and which ones are not. Otherwise +// we just use the existing invocation object. +Invocation _useTypedInvocationIfSet(Invocation invocation) { + if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) { + invocation = new _InvocationForTypedArguments(invocation); + } + return invocation; +} + /// An Invocation implementation that takes arguments from [_typedArgs] and /// [_typedNamedArgs]. class _InvocationForTypedArguments extends Invocation { diff --git a/pkgs/mockito/lib/src/typed_mock.dart b/pkgs/mockito/lib/src/typed_mock.dart deleted file mode 100644 index 8fed60fcb..000000000 --- a/pkgs/mockito/lib/src/typed_mock.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Extend or mixin this class to mark the implementation as a [Mock]. -/// -/// A mocked class implements all fields and methods with a default -/// implementation that does not throw a [NoSuchMethodError], and may be further -/// customized at runtime to define how it may behave using [when] or [given]. -/// -/// __Example use__: -/// // Real class. -/// class Cat { -/// String getSound() => 'Meow'; -/// } -/// -/// // Mock class. -/// class MockCat extends Mock implements Cat {} -/// -/// void main() { -/// // Create a new mocked Cat at runtime. -/// var cat = new MockCat(); -/// -/// // When 'getSound' is called, return 'Woof' -/// when(cat.getSound()).thenReturn('Woof'); -/// -/// // Try making a Cat sound... -/// print(cat.getSound()); // Prints 'Woof' -/// } -/// -/// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of -/// runtime reflection, and causes sub-standard code to be generated. As such, -/// [Mock] 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). -class Mock { - @override - @visibleForTesting - noSuchMethod(Invocation inv) { - // noSuchMethod is that 'magic' that allows us to ignore implementing fields - // and methods and instead define them later at compile-time per instance. - // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH - } -} diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1223353a2..6e6c74e12 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,9 +1,14 @@ name: mockito -version: 1.0.1 -author: Dmitriy Fibulwinter +version: 1.0.1+1 +authors: + - Dmitriy Fibulwinter + - Ted Sander + - Samuel Rawlins + - Matan Lurey description: A mock framework inspired by Mockito. homepage: https://github.com/fibulwinter/dart-mockito environment: sdk: '>=1.0.0 <2.0.0' dependencies: + meta: test: '>=0.12.0 <0.13.0' diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 14faa80d4..3467de4b7 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -33,13 +33,9 @@ abstract class AbstractFoo implements Foo { String baz(); } -class MockFoo extends AbstractFoo with Mock { - noSuchMethod(i) => super.noSuchMethod(i); -} +class MockFoo extends AbstractFoo with Mock {} -class MockedClass extends Mock implements RealClass { - noSuchMethod(i) => super.noSuchMethod(i); -} +class MockedClass extends Mock implements RealClass {} expectFail(String expectedMessage, expectedToFail()) { try { From 1106e405c7710dcdbc2818dbee480762516dd15b Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 27 Sep 2016 20:36:07 -0700 Subject: [PATCH 043/607] Add version constraints for meta. --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 6e6c74e12..07298d5cc 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -10,5 +10,5 @@ homepage: https://github.com/fibulwinter/dart-mockito environment: sdk: '>=1.0.0 <2.0.0' dependencies: - meta: + meta: '>=1.0.4 <2.0.0' test: '>=0.12.0 <0.13.0' From 4b9115e1052c55936b1afb79b5026e73541e68a6 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Wed, 28 Sep 2016 13:52:02 -0700 Subject: [PATCH 044/607] Add invocation matcher to package:mockito --- pkgs/mockito/lib/src/invocation_matcher.dart | 163 ++++++++++++++++++ pkgs/mockito/pubspec.yaml | 5 +- .../mockito/test/invocation_matcher_test.dart | 162 +++++++++++++++++ pkgs/mockito/tool/travis.sh | 6 +- 4 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 pkgs/mockito/lib/src/invocation_matcher.dart create mode 100644 pkgs/mockito/test/invocation_matcher_test.dart diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart new file mode 100644 index 000000000..d3fe96447 --- /dev/null +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -0,0 +1,163 @@ +import 'package:collection/collection.dart'; +import 'package:matcher/matcher.dart'; +import 'package:meta/meta.dart'; + +/// Returns a matcher that expects an invocation that matches arguments given. +/// +/// Both [positionalArguments] and [namedArguments] can also be [Matcher]s: +/// // Expects an invocation of "foo(String a, bool b)" where "a" must be +/// // the value 'hello' but "b" may be any value. This would match both +/// // foo('hello', true), foo('hello', false), and foo('hello', null). +/// expect(fooInvocation, invokes( +/// #foo, +/// positionalArguments: ['hello', any] +/// )); +/// +/// Suitable for use in mocking libraries, where `noSuchMethod` can be used to +/// get a handle to attempted [Invocation] objects and then compared against +/// what a user expects to be called. +Matcher invokes( + Symbol memberName, { + List positionalArguments: const [], + Map namedArguments: const {}, + bool isGetter: false, + bool isSetter: false, + }) { + if (isGetter && isSetter) { + throw new ArgumentError('Cannot set isGetter and iSetter'); + } + if (positionalArguments == null) { + throw new ArgumentError.notNull('positionalArguments'); + } + if (namedArguments == null) { + throw new ArgumentError.notNull('namedArguments'); + } + return new _InvocationMatcher(new _InvocationSignature( + memberName: memberName, + positionalArguments: positionalArguments, + namedArguments: namedArguments, + isGetter: isGetter, + isSetter: isSetter, + )); +} + +/// Returns a matcher that matches the name and arguments of an [invocation]. +/// +/// To expect the same _signature_ see [invokes]. +Matcher isInvocation(Invocation invocation) => + new _InvocationMatcher(invocation); + +class _InvocationSignature extends Invocation { + @override + final Symbol memberName; + + @override + final List positionalArguments; + + @override + final Map namedArguments; + + @override + final bool isGetter; + + @override + final bool isSetter; + + _InvocationSignature({ + @required this.memberName, + this.positionalArguments: const [], + this.namedArguments: const {}, + this.isGetter: false, + this.isSetter: false, + }); + + @override + bool get isMethod => !isAccessor; +} + +class _InvocationMatcher implements Matcher { + static Description _describeInvocation(Description d, Invocation invocation) { + if (invocation.isAccessor) { + d = d + .add(invocation.isGetter ? 'get ' : 'set ') + .add(_symbolToString(invocation.memberName)); + if (invocation.isSetter) { + d = d.add(' ').addDescriptionOf(invocation.positionalArguments.first); + } + return d; + } + d = d + .add(_symbolToString(invocation.memberName)) + .add('(') + .addAll('', ', ', '', invocation.positionalArguments); + if (invocation.positionalArguments.isNotEmpty && + invocation.namedArguments.isNotEmpty) { + d = d.add(', '); + } + return d.addAll('', ', ', '', _namedArgsAndValues(invocation)).add(')'); + } + + static Iterable _namedArgsAndValues(Invocation invocation) => + invocation.namedArguments.keys.map/**/((name) => + '${_symbolToString(name)}: ${invocation.namedArguments[name]}'); + + // This will give is a mangled symbol in dart2js/aot with minification + // enabled, but it's safe to assume very few people will use the invocation + // matcher in a production test anyway due to noSuchMethod. + static String _symbolToString(Symbol symbol) { + return symbol.toString().split('"')[1]; + } + + final Invocation _invocation; + + _InvocationMatcher(this._invocation) { + if (_invocation == null) { + throw new ArgumentError.notNull(); + } + } + + @override + Description describe(Description d) => _describeInvocation(d, _invocation); + + // TODO(matanl): Better implement describeMismatch and use state from matches. + // Specifically, if a Matcher is passed as an argument, we'd like to get an + // error like "Expected fly(miles: > 10), Actual: fly(miles: 5)". + @override + Description describeMismatch(item, Description d, _, __) { + if (item is Invocation) { + d = d.add('Does not match '); + return _describeInvocation(d, item); + } + return d.add('Is not an Invocation'); + } + + @override + bool matches(item, _) => + item is Invocation && + _invocation.memberName == item.memberName && + _invocation.isSetter == item.isSetter && + _invocation.isGetter == item.isGetter && + const ListEquality(const _MatcherEquality()) + .equals(_invocation.positionalArguments, item.positionalArguments) && + const MapEquality(values: const _MatcherEquality()) + .equals(_invocation.namedArguments, item.namedArguments); +} + +class _MatcherEquality extends DefaultEquality /* */ { + const _MatcherEquality(); + + @override + bool equals(e1, e2) { + if (e1 is Matcher && e2 is! Matcher) { + return e1.matches(e2, const {}); + } + if (e2 is Matcher && e1 is! Matcher) { + return e2.matches(e1, const {}); + } + return super.equals(e1, e2); + } + + // We force collisions on every value so equals() is called. + @override + int hash(_) => 0; +} diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 07298d5cc..cd508e3f9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 1.0.1+1 +version: 1.0.1+2 authors: - Dmitriy Fibulwinter - Ted Sander @@ -10,5 +10,6 @@ homepage: https://github.com/fibulwinter/dart-mockito environment: sdk: '>=1.0.0 <2.0.0' dependencies: - meta: '>=1.0.4 <2.0.0' + matcher: '^0.12.0' + meta: '^1.0.4' test: '>=0.12.0 <0.13.0' diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart new file mode 100644 index 000000000..89b8cc24c --- /dev/null +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -0,0 +1,162 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:mockito/src/invocation_matcher.dart'; +import 'package:test/test.dart'; + +Invocation lastInvocation; + +void main() { + const stub = const Stub(); + + group('$isInvocation', () { + test('positional arguments', () { + var call1 = stub.say('Hello'); + var call2 = stub.say('Hello'); + var call3 = stub.say('Guten Tag'); + shouldPass(call1, isInvocation(call2)); + shouldFail( + call1, + isInvocation(call3), + "Expected: say('Guten Tag') " + "Actual: " + "Which: Does not match say('Hello')", + ); + }); + + test('named arguments', () { + var call1 = stub.eat('Chicken', alsoDrink: true); + var call2 = stub.eat('Chicken', alsoDrink: true); + var call3 = stub.eat('Chicken', alsoDrink: false); + shouldPass(call1, isInvocation(call2)); + shouldFail( + call1, + isInvocation(call3), + "Expected: eat('Chicken', 'alsoDrink: false') " + "Actual: " + "Which: Does not match eat('Chicken', 'alsoDrink: true')", + ); + }); + + test('optional arguments', () { + var call1 = stub.lie(true); + var call2 = stub.lie(true); + var call3 = stub.lie(false); + shouldPass(call1, isInvocation(call2)); + shouldFail( + call1, + isInvocation(call3), + "Expected: lie() " + "Actual: " + "Which: Does not match lie()", + ); + }); + + test('getter', () { + var call1 = stub.value; + var call2 = stub.value; + stub.value = true; + var call3 = Stub.lastInvocation; + shouldPass(call1, isInvocation(call2)); + shouldFail( + call1, + isInvocation(call3), + "Expected: set value= " + "Actual: " + "Which: Does not match get value", + ); + }); + + test('setter', () { + stub.value = true; + var call1 = Stub.lastInvocation; + stub.value = true; + var call2 = Stub.lastInvocation; + stub.value = false; + var call3 = Stub.lastInvocation; + shouldPass(call1, isInvocation(call2)); + shouldFail( + call1, + isInvocation(call3), + "Expected: set value= " + "Actual: " + "Which: Does not match set value= ", + ); + }); + }); + + group('$invokes', () { + test('positional arguments', () { + var call = stub.say('Hello'); + shouldPass(call, invokes(#say, positionalArguments: ['Hello'])); + shouldPass(call, invokes(#say, positionalArguments: [anything])); + shouldFail( + call, + invokes(#say, positionalArguments: [isNull]), + "Expected: say(null) " + "Actual: " + "Which: Does not match say('Hello')", + ); + }); + + test('named arguments', () { + var call = stub.fly(miles: 10); + shouldPass(call, invokes(#fly, namedArguments: {#miles: 10})); + shouldPass(call, invokes(#fly, namedArguments: {#miles: greaterThan(5)})); + shouldFail( + call, + invokes(#fly, namedArguments: {#miles: 11}), + "Expected: fly('miles: 11') " + "Actual: " + "Which: Does not match fly('miles: 10')", + ); + }); + }); +} + +abstract class Interface { + bool get value; + set value(value); + say(String text); + eat(String food, {bool alsoDrink}); + lie([bool facingDown]); + fly({int miles}); +} + +/// An example of a class that captures Invocation objects. +/// +/// Any call always returns an [Invocation]. +class Stub implements Interface { + static Invocation lastInvocation; + + const Stub(); + + @override + noSuchMethod(Invocation invocation) => lastInvocation = invocation; +} + +// Copied from package:test, which doesn't expose it to users. +// https://github.com/dart-lang/matcher/issues/39 +void shouldFail(value, Matcher matcher, expected) { + var failed = false; + try { + expect(value, matcher); + } on TestFailure catch (err) { + failed = true; + + var _errorString = err.message; + + if (expected is String) { + expect(_errorString, equalsIgnoringWhitespace(expected)); + } else { + expect(_errorString.replaceAll('\n', ''), expected); + } + } + + expect(failed, isTrue, reason: 'Expected to fail.'); +} + +void shouldPass(value, Matcher matcher) { + expect(value, matcher); +} diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 5387de554..e22fa9857 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -6,8 +6,10 @@ set -e # Verify that the libraries are error free. dartanalyzer --fatal-warnings \ lib/mockito.dart \ + lib/mockito_no_mirrors.dart \ + lib/src/invocation_matcher.dart \ test/mockito_test.dart # Run the tests. -dart test/mockito_test.dart - +dart -c test/invocation_matcher_test.dart +dart -c test/mockito_test.dart From cfe4ce9ec8c02397acb94bbff216abd5c1be8192 Mon Sep 17 00:00:00 2001 From: Thibault Sottiaux Date: Thu, 29 Sep 2016 13:53:31 +0100 Subject: [PATCH 045/607] Cleanup --- pkgs/mockito/lib/mockito.dart | 4 +- pkgs/mockito/lib/mockito_no_mirrors.dart | 29 +++++- pkgs/mockito/lib/src/mock.dart | 110 +++++++++++------------ pkgs/mockito/test/mockito_test.dart | 4 +- 4 files changed, 87 insertions(+), 60 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index ccb197a95..44a2e01f7 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,2 +1,2 @@ -export 'src/mock.dart' hide setDefaultResponse; -export 'src/spy.dart'; +export 'mockito_no_mirrors.dart'; +export 'src/spy.dart' show spy; diff --git a/pkgs/mockito/lib/mockito_no_mirrors.dart b/pkgs/mockito/lib/mockito_no_mirrors.dart index 17414e3cc..8ba07ffc1 100644 --- a/pkgs/mockito/lib/mockito_no_mirrors.dart +++ b/pkgs/mockito/lib/mockito_no_mirrors.dart @@ -1 +1,28 @@ -export 'src/mock.dart' hide setDefaultResponse; +export 'src/mock.dart' + show + Mock, + named, + + // -- setting behaviour + when, + any, + argThat, + captureAny, + captureThat, + typed, + Answering, + Expectation, + PostExpectation, + + // -- verification + verify, + verifyInOrder, + verifyNever, + verifyNoMoreInteractions, + verifyZeroInteractions, + VerificationResult, + + // -- misc + clearInteractions, + reset, + logInvocations; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index a359c3381..28923e835 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -82,7 +82,8 @@ class Mock { } else { _realCalls.add(new RealCall(this, invocation)); var cannedResponse = _responses.lastWhere( - (cr) => cr.matcher.matches(invocation), orElse: _defaultResponse); + (cr) => cr.matcher.matches(invocation), + orElse: _defaultResponse); var response = cannedResponse.response(invocation); return response; } @@ -149,9 +150,10 @@ class _InvocationForTypedArguments extends Invocation { // The namedArguments in [invocation] which are null should be represented // by a stored value in [_typedNamedArgs]. The null presumably came from // [typed]. - static Map _reconstituteNamedArgs(Invocation invocation) { + static Map _reconstituteNamedArgs(Invocation invocation) { var namedArguments = {}; - var _typedNamedArgSymbols = _typedNamedArgs.keys.map((name) => new Symbol(name)); + var _typedNamedArgSymbols = + _typedNamedArgs.keys.map((name) => new Symbol(name)); // Iterate through [invocation]'s named args, validate them, and add them // to the return map. @@ -218,7 +220,8 @@ class _InvocationForTypedArguments extends Invocation { positionalIndex++; } else { // [typed] was not used; add the [_ArgMatcher] from [invocation]. - positionalArguments.add(invocation.positionalArguments[positionalIndex]); + positionalArguments + .add(invocation.positionalArguments[positionalIndex]); positionalIndex++; } } @@ -231,13 +234,8 @@ class _InvocationForTypedArguments extends Invocation { return positionalArguments; } - _InvocationForTypedArguments._( - this.memberName, - this.positionalArguments, - this.namedArguments, - this.isGetter, - this.isMethod, - this.isSetter); + _InvocationForTypedArguments._(this.memberName, this.positionalArguments, + this.namedArguments, this.isGetter, this.isMethod, this.isSetter); } named(var mock, {String name, int hashCode}) => mock @@ -359,8 +357,6 @@ class InvocationMatcher { bool isMatchingArg(roleArg, actArg) { if (roleArg is _ArgMatcher) { return roleArg._matcher.matches(actArg, {}); -// } else if(roleArg is Mock){ -// return identical(roleArg, actArg); } else { return equals(roleArg).matches(actArg, {}); } @@ -387,47 +383,49 @@ class _TimeStampProvider { } class RealCall { - // This used to use MirrorSystem, which cleans up the Symbol() wrapper. - // Since this toString method is just used in Mockito's own tests, it's not - // a big deal to massage the toString a bit. - // - // Input: Symbol("someMethodName") - static String _symbolToString(Symbol symbol) { - return symbol.toString().split('"')[1]; - } - - DateTime _timeStamp; final Mock mock; final Invocation invocation; + final DateTime timeStamp; + bool verified = false; - RealCall(this.mock, this.invocation) { - _timeStamp = _timer.now(); - } - DateTime get timeStamp => _timeStamp; + RealCall(this.mock, this.invocation) : timeStamp = _timer.now(); String toString() { - var verifiedText = verified ? "[VERIFIED] " : ""; - List posArgs = invocation.positionalArguments + var args = invocation.positionalArguments .map((v) => v == null ? "null" : v.toString()) - .toList(); - List mapArgList = invocation.namedArguments.keys.map((key) { - return "${_symbolToString(key)}: ${invocation.namedArguments[key]}"; - }).toList(growable: false); - if (mapArgList.isNotEmpty) { - posArgs.add("{${mapArgList.join(", ")}}"); + .join(", "); + if (invocation.namedArguments.isNotEmpty) { + var namedArgs = invocation.namedArguments.keys + .map((key) => + "${_symbolToString(key)}: ${invocation.namedArguments[key]}") + .join(", "); + args += ", {$namedArgs}"; } - String args = posArgs.join(", "); - String method = _symbolToString(invocation.memberName); + + var method = _symbolToString(invocation.memberName); if (invocation.isMethod) { - method = ".$method($args)"; + method = "$method($args)"; } else if (invocation.isGetter) { - method = ".$method"; + method = "$method"; + } else if (invocation.isSetter) { + method = "$method=$args"; } else { - method = ".$method=$args"; + throw new StateError( + 'Invocation should be getter, setter or a method call.'); } - return "$verifiedText$mock$method"; + + var verifiedText = verified ? "[VERIFIED] " : ""; + return "$verifiedText$mock.$method"; } + + // This used to use MirrorSystem, which cleans up the Symbol() wrapper. + // Since this toString method is just used in Mockito's own tests, it's not + // a big deal to massage the toString a bit. + // + // Input: Symbol("someMethodName") + static String _symbolToString(Symbol symbol) => + symbol.toString().split('"')[1]; } class _WhenCall { @@ -528,15 +526,15 @@ class VerificationResult { typedef dynamic Answering(Invocation realInvocation); -typedef VerificationResult Verification(matchingInvocations); +typedef VerificationResult _Verification(matchingInvocations); -typedef void InOrderVerification(recordedInvocations); +typedef void _InOrderVerification(List recordedInvocations); -Verification get verifyNever => _makeVerify(true); +_Verification get verifyNever => _makeVerify(true); -Verification get verify => _makeVerify(false); +_Verification get verify => _makeVerify(false); -Verification _makeVerify(bool never) { +_Verification _makeVerify(bool never) { if (_verifyCalls.isNotEmpty) { throw new StateError(_verifyCalls.join()); } @@ -546,7 +544,7 @@ Verification _makeVerify(bool never) { if (_verifyCalls.length == 1) { _VerifyCall verifyCall = _verifyCalls.removeLast(); var result = - new VerificationResult(verifyCall.matchingInvocations.length); + new VerificationResult(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; } else { @@ -555,12 +553,12 @@ Verification _makeVerify(bool never) { }; } -InOrderVerification get verifyInOrder { +_InOrderVerification get verifyInOrder { if (_verifyCalls.isNotEmpty) { throw new StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (verifyCalls) { + return (List _) { _verificationInProgress = false; DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = new List.from(_verifyCalls); @@ -573,9 +571,9 @@ InOrderVerification get verifyInOrder { dt = matched.timeStamp; } else { Set mocks = - tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); + tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); List allInvocations = - mocks.expand((m) => m._realCalls).toList(growable: false); + mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); String otherCalls = ""; @@ -586,9 +584,9 @@ InOrderVerification get verifyInOrder { "Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); } } - matchedCalls.forEach((rc) { - rc.verified = true; - }); + for (var call in matchedCalls) { + call.verified = true; + } }; } @@ -618,14 +616,14 @@ Expectation get when { void logInvocations(List mocks) { List allInvocations = - mocks.expand((m) => m._realCalls).toList(growable: false); + mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); allInvocations.forEach((inv) { print(inv.toString()); }); } -/// Should only be used during Mockito testing. +/// Only for mockito testing. void resetMockitoState() { _whenInProgress = false; _verificationInProgress = false; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 3467de4b7..2e07913c7 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -1,5 +1,7 @@ import 'package:test/test.dart'; -import 'package:mockito/mockito.dart'; + +import 'package:mockito/src/mock.dart'; +import 'package:mockito/src/spy.dart'; class RealClass { String methodWithoutArgs() => "Real"; From b0b0d0f35c2a13e83ca88f219930394ee4efcd64 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 1 Oct 2016 07:52:44 -0700 Subject: [PATCH 046/607] Dartfmt, license, comments. --- pkgs/mockito/lib/src/invocation_matcher.dart | 54 ++++---- pkgs/mockito/lib/src/mock.dart | 28 ++--- .../mockito/test/invocation_matcher_test.dart | 88 +++++++------ pkgs/mockito/test/mockito_test.dart | 118 ++++++++++-------- 4 files changed, 151 insertions(+), 137 deletions(-) diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index d3fe96447..c176a1b8c 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -17,12 +17,12 @@ import 'package:meta/meta.dart'; /// get a handle to attempted [Invocation] objects and then compared against /// what a user expects to be called. Matcher invokes( - Symbol memberName, { - List positionalArguments: const [], - Map namedArguments: const {}, - bool isGetter: false, - bool isSetter: false, - }) { + Symbol memberName, { + List positionalArguments: const [], + Map namedArguments: const {}, + bool isGetter: false, + bool isSetter: false, +}) { if (isGetter && isSetter) { throw new ArgumentError('Cannot set isGetter and iSetter'); } @@ -33,12 +33,12 @@ Matcher invokes( throw new ArgumentError.notNull('namedArguments'); } return new _InvocationMatcher(new _InvocationSignature( - memberName: memberName, - positionalArguments: positionalArguments, - namedArguments: namedArguments, - isGetter: isGetter, - isSetter: isSetter, - )); + memberName: memberName, + positionalArguments: positionalArguments, + namedArguments: namedArguments, + isGetter: isGetter, + isSetter: isSetter, + )); } /// Returns a matcher that matches the name and arguments of an [invocation]. @@ -64,11 +64,11 @@ class _InvocationSignature extends Invocation { final bool isSetter; _InvocationSignature({ - @required this.memberName, - this.positionalArguments: const [], - this.namedArguments: const {}, - this.isGetter: false, - this.isSetter: false, + @required this.memberName, + this.positionalArguments: const [], + this.namedArguments: const {}, + this.isGetter: false, + this.isSetter: false, }); @override @@ -77,6 +77,7 @@ class _InvocationSignature extends Invocation { class _InvocationMatcher implements Matcher { static Description _describeInvocation(Description d, Invocation invocation) { + // For a getter or a setter, just return get or set . if (invocation.isAccessor) { d = d .add(invocation.isGetter ? 'get ' : 'set ') @@ -86,6 +87,7 @@ class _InvocationMatcher implements Matcher { } return d; } + // For a method, return (). d = d .add(_symbolToString(invocation.memberName)) .add('(') @@ -94,12 +96,14 @@ class _InvocationMatcher implements Matcher { invocation.namedArguments.isNotEmpty) { d = d.add(', '); } + // Also added named arguments, if any. return d.addAll('', ', ', '', _namedArgsAndValues(invocation)).add(')'); } + // Returns named arguments as an iterable of ': '. static Iterable _namedArgsAndValues(Invocation invocation) => invocation.namedArguments.keys.map/**/((name) => - '${_symbolToString(name)}: ${invocation.namedArguments[name]}'); + '${_symbolToString(name)}: ${invocation.namedArguments[name]}'); // This will give is a mangled symbol in dart2js/aot with minification // enabled, but it's safe to assume very few people will use the invocation @@ -134,13 +138,13 @@ class _InvocationMatcher implements Matcher { @override bool matches(item, _) => item is Invocation && - _invocation.memberName == item.memberName && - _invocation.isSetter == item.isSetter && - _invocation.isGetter == item.isGetter && - const ListEquality(const _MatcherEquality()) - .equals(_invocation.positionalArguments, item.positionalArguments) && - const MapEquality(values: const _MatcherEquality()) - .equals(_invocation.namedArguments, item.namedArguments); + _invocation.memberName == item.memberName && + _invocation.isSetter == item.isSetter && + _invocation.isGetter == item.isGetter && + const ListEquality(const _MatcherEquality()) + .equals(_invocation.positionalArguments, item.positionalArguments) && + const MapEquality(values: const _MatcherEquality()) + .equals(_invocation.namedArguments, item.namedArguments); } class _MatcherEquality extends DefaultEquality /* */ { diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index a359c3381..21f1f5fe6 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -82,7 +82,8 @@ class Mock { } else { _realCalls.add(new RealCall(this, invocation)); var cannedResponse = _responses.lastWhere( - (cr) => cr.matcher.matches(invocation), orElse: _defaultResponse); + (cr) => cr.matcher.matches(invocation), + orElse: _defaultResponse); var response = cannedResponse.response(invocation); return response; } @@ -149,9 +150,10 @@ class _InvocationForTypedArguments extends Invocation { // The namedArguments in [invocation] which are null should be represented // by a stored value in [_typedNamedArgs]. The null presumably came from // [typed]. - static Map _reconstituteNamedArgs(Invocation invocation) { + static Map _reconstituteNamedArgs(Invocation invocation) { var namedArguments = {}; - var _typedNamedArgSymbols = _typedNamedArgs.keys.map((name) => new Symbol(name)); + var _typedNamedArgSymbols = + _typedNamedArgs.keys.map((name) => new Symbol(name)); // Iterate through [invocation]'s named args, validate them, and add them // to the return map. @@ -218,7 +220,8 @@ class _InvocationForTypedArguments extends Invocation { positionalIndex++; } else { // [typed] was not used; add the [_ArgMatcher] from [invocation]. - positionalArguments.add(invocation.positionalArguments[positionalIndex]); + positionalArguments + .add(invocation.positionalArguments[positionalIndex]); positionalIndex++; } } @@ -231,13 +234,8 @@ class _InvocationForTypedArguments extends Invocation { return positionalArguments; } - _InvocationForTypedArguments._( - this.memberName, - this.positionalArguments, - this.namedArguments, - this.isGetter, - this.isMethod, - this.isSetter); + _InvocationForTypedArguments._(this.memberName, this.positionalArguments, + this.namedArguments, this.isGetter, this.isMethod, this.isSetter); } named(var mock, {String name, int hashCode}) => mock @@ -546,7 +544,7 @@ Verification _makeVerify(bool never) { if (_verifyCalls.length == 1) { _VerifyCall verifyCall = _verifyCalls.removeLast(); var result = - new VerificationResult(verifyCall.matchingInvocations.length); + new VerificationResult(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; } else { @@ -573,9 +571,9 @@ InOrderVerification get verifyInOrder { dt = matched.timeStamp; } else { Set mocks = - tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); + tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); List allInvocations = - mocks.expand((m) => m._realCalls).toList(growable: false); + mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); String otherCalls = ""; @@ -618,7 +616,7 @@ Expectation get when { void logInvocations(List mocks) { List allInvocations = - mocks.expand((m) => m._realCalls).toList(growable: false); + mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); allInvocations.forEach((inv) { print(inv.toString()); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 89b8cc24c..d148196de 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -1,7 +1,3 @@ -// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - import 'package:mockito/src/invocation_matcher.dart'; import 'package:test/test.dart'; @@ -17,12 +13,12 @@ void main() { var call3 = stub.say('Guten Tag'); shouldPass(call1, isInvocation(call2)); shouldFail( - call1, - isInvocation(call3), - "Expected: say('Guten Tag') " - "Actual: " - "Which: Does not match say('Hello')", - ); + call1, + isInvocation(call3), + "Expected: say('Guten Tag') " + "Actual: " + "Which: Does not match say('Hello')", + ); }); test('named arguments', () { @@ -31,12 +27,12 @@ void main() { var call3 = stub.eat('Chicken', alsoDrink: false); shouldPass(call1, isInvocation(call2)); shouldFail( - call1, - isInvocation(call3), - "Expected: eat('Chicken', 'alsoDrink: false') " - "Actual: " - "Which: Does not match eat('Chicken', 'alsoDrink: true')", - ); + call1, + isInvocation(call3), + "Expected: eat('Chicken', 'alsoDrink: false') " + "Actual: " + "Which: Does not match eat('Chicken', 'alsoDrink: true')", + ); }); test('optional arguments', () { @@ -45,12 +41,12 @@ void main() { var call3 = stub.lie(false); shouldPass(call1, isInvocation(call2)); shouldFail( - call1, - isInvocation(call3), - "Expected: lie() " - "Actual: " - "Which: Does not match lie()", - ); + call1, + isInvocation(call3), + "Expected: lie() " + "Actual: " + "Which: Does not match lie()", + ); }); test('getter', () { @@ -60,12 +56,12 @@ void main() { var call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( - call1, - isInvocation(call3), - "Expected: set value= " - "Actual: " - "Which: Does not match get value", - ); + call1, + isInvocation(call3), + "Expected: set value= " + "Actual: " + "Which: Does not match get value", + ); }); test('setter', () { @@ -77,12 +73,12 @@ void main() { var call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( - call1, - isInvocation(call3), - "Expected: set value= " - "Actual: " - "Which: Does not match set value= ", - ); + call1, + isInvocation(call3), + "Expected: set value= " + "Actual: " + "Which: Does not match set value= ", + ); }); }); @@ -92,12 +88,12 @@ void main() { shouldPass(call, invokes(#say, positionalArguments: ['Hello'])); shouldPass(call, invokes(#say, positionalArguments: [anything])); shouldFail( - call, - invokes(#say, positionalArguments: [isNull]), - "Expected: say(null) " - "Actual: " - "Which: Does not match say('Hello')", - ); + call, + invokes(#say, positionalArguments: [isNull]), + "Expected: say(null) " + "Actual: " + "Which: Does not match say('Hello')", + ); }); test('named arguments', () { @@ -105,12 +101,12 @@ void main() { shouldPass(call, invokes(#fly, namedArguments: {#miles: 10})); shouldPass(call, invokes(#fly, namedArguments: {#miles: greaterThan(5)})); shouldFail( - call, - invokes(#fly, namedArguments: {#miles: 11}), - "Expected: fly('miles: 11') " - "Actual: " - "Which: Does not match fly('miles: 10')", - ); + call, + invokes(#fly, namedArguments: {#miles: 11}), + "Expected: fly('miles: 11') " + "Actual: " + "Which: Does not match fly('miles: 10')", + ); }); }); } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 3467de4b7..42927f129 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -11,11 +11,13 @@ class RealClass { String methodWithObjArgs(RealClass x) => "Real"; // "SpecialArgs" here means type-parameterized args. But that makes for a long // method name. - String typeParameterizedFn( - List w, List x, [List y, List z]) => "Real"; + String typeParameterizedFn(List w, List x, + [List y, List z]) => + "Real"; // "SpecialNamedArgs" here means type-parameterized, named args. But that // makes for a long method name. - String typeParameterizedNamedFn(List w, List x, {List y, List z}) => + String typeParameterizedNamedFn(List w, List x, + {List y, List z}) => "Real"; String get getter => "Real"; void set setter(String arg) { @@ -83,7 +85,6 @@ void main() { }); }); - group("mixin support", () { test("should work", () { var foo = new MockFoo(); @@ -141,16 +142,16 @@ void main() { expect(mock.methodWithListArgs([42]), equals("A lot!")); expect(mock.methodWithListArgs([43]), equals("A lot!")); }); - test("should mock method with multiple named args and matchers", (){ + test("should mock method with multiple named args and matchers", () { when(mock.methodWithTwoNamedArgs(any, y: any)).thenReturn("x y"); when(mock.methodWithTwoNamedArgs(any, z: any)).thenReturn("x z"); expect(mock.methodWithTwoNamedArgs(42), isNull); - expect(mock.methodWithTwoNamedArgs(42, y:18), equals("x y")); - expect(mock.methodWithTwoNamedArgs(42, z:17), equals("x z")); - expect(mock.methodWithTwoNamedArgs(42, y:18, z:17), isNull); + expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); + expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); + expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); when(mock.methodWithTwoNamedArgs(any, y: any, z: any)) .thenReturn("x y z"); - expect(mock.methodWithTwoNamedArgs(42, y:18, z:17), equals("x y z")); + expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); }); test("should mock method with mix of argument matchers and real things", () { @@ -229,17 +230,19 @@ void main() { .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); }); - test("should mock method with an optional typed arg matcher and an optional real arg", () { + test( + "should mock method with an optional typed arg matcher and an optional real arg", + () { when(mock.typeParameterizedFn(typed(any), typed(any), [44], typed(any))) .thenReturn("A lot!"); - expect(mock.typeParameterizedFn([42], [43], [44], [45]), equals("A lot!")); + expect( + mock.typeParameterizedFn([42], [43], [44], [45]), equals("A lot!")); }); test("should mock method with only some typed arg matchers", () { when(mock.typeParameterizedFn(typed(any), [43], typed(any))) .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); - when(mock.typeParameterizedFn(typed(any), [43])) - .thenReturn("A bunch!"); + when(mock.typeParameterizedFn(typed(any), [43])).thenReturn("A bunch!"); expect(mock.typeParameterizedFn([42], [43]), equals("A bunch!")); }); test("should throw when [typed] used alongside [null].", () { @@ -250,47 +253,51 @@ void main() { }); test("should mock method when [typed] used alongside matched [null].", () { when(mock.typeParameterizedFn( - typed(any), argThat(equals(null)), typed(any))) + typed(any), argThat(equals(null)), typed(any))) .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], null, [44]), equals("A lot!")); }); test("should mock method with named, typed arg matcher", () { - when(mock.typeParameterizedNamedFn( - typed(any), [43], y: typed(any, named: "y"))) + when(mock.typeParameterizedNamedFn(typed(any), [43], + y: typed(any, named: "y"))) .thenReturn("A lot!"); - expect(mock.typeParameterizedNamedFn([42], [43], y: [44]), equals("A lot!")); + expect( + mock.typeParameterizedNamedFn([42], [43], y: [44]), equals("A lot!")); }); - test("should mock method with named, typed arg matcher and an arg matcher", () { - when( - mock.typeParameterizedNamedFn( - typed(any), [43], + test("should mock method with named, typed arg matcher and an arg matcher", + () { + when(mock.typeParameterizedNamedFn(typed(any), [43], y: typed(any, named: "y"), z: argThat(contains(45)))) .thenReturn("A lot!"); expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); - test("should mock method with named, typed arg matcher and a regular arg", () { - when( - mock.typeParameterizedNamedFn( - typed(any), [43], - y: typed(any, named: "y"), z: [45])) - .thenReturn("A lot!"); + test("should mock method with named, typed arg matcher and a regular arg", + () { + when(mock.typeParameterizedNamedFn(typed(any), [43], + y: typed(any, named: "y"), z: [45])).thenReturn("A lot!"); expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); test("should throw when [typed] used as a named arg, without `named:`", () { - expect(() => when(mock.typeParameterizedNamedFn( - typed(any), [43], y: typed(any))), + expect( + () => when( + mock.typeParameterizedNamedFn(typed(any), [43], y: typed(any))), throwsArgumentError); }); - test("should throw when [typed] used as a positional arg, with `named:`", () { - expect(() => when(mock.typeParameterizedNamedFn( - typed(any), typed(any, named: "y"))), + test("should throw when [typed] used as a positional arg, with `named:`", + () { + expect( + () => when(mock.typeParameterizedNamedFn( + typed(any), typed(any, named: "y"))), throwsArgumentError); }); - test("should throw when [typed] used as a named arg, with the wrong `named:`", () { - expect(() => when(mock.typeParameterizedNamedFn( - typed(any), [43], y: typed(any, named: "z"))), + test( + "should throw when [typed] used as a named arg, with the wrong `named:`", + () { + expect( + () => when(mock.typeParameterizedNamedFn(typed(any), [43], + y: typed(any, named: "z"))), throwsArgumentError); }); }); @@ -342,9 +349,9 @@ void main() { mock.methodWithObjArgs(m1); expectFail( "No matching calls. All calls: MockedClass.methodWithObjArgs(m1)", - () { - verify(mock.methodWithObjArgs(new MockedClass())); - }); + () { + verify(mock.methodWithObjArgs(new MockedClass())); + }); verify(mock.methodWithObjArgs(m1)); }); test("should mock method with list args", () { @@ -374,10 +381,15 @@ void main() { test("should mock method with argument matcher and capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); - expect(verify(mock.methodWithNormalArgs( - captureThat(greaterThan(75)))).captured.single, equals(100)); - expect(verify(mock - .methodWithNormalArgs(captureThat(lessThan(75)))).captured.single, + expect( + verify(mock.methodWithNormalArgs(captureThat(greaterThan(75)))) + .captured + .single, + equals(100)); + expect( + verify(mock.methodWithNormalArgs(captureThat(lessThan(75)))) + .captured + .single, equals(50)); }); test("should mock method with mix of argument matchers and real things", @@ -415,9 +427,12 @@ void main() { test("should verify method with argument capturer", () { mock.typeParameterizedFn([50], [17]); mock.typeParameterizedFn([100], [17]); - expect(verify(mock.typeParameterizedFn( - typed(captureAny), [17])).captured, - equals([[50], [100]])); + expect( + verify(mock.typeParameterizedFn(typed(captureAny), [17])).captured, + equals([ + [50], + [100] + ])); }); }); group("verify() qualifies", () { @@ -578,21 +593,22 @@ void main() { }); test("should captureOut list arguments", () { mock.methodWithListArgs([42]); - expect(verify( - mock.methodWithListArgs(captureAny)).captured.single, + expect(verify(mock.methodWithListArgs(captureAny)).captured.single, equals([42])); }); test("should captureOut multiple arguments", () { mock.methodWithPositionalArgs(1, 2); - expect(verify( - mock.methodWithPositionalArgs(captureAny, captureAny)).captured, + expect( + verify(mock.methodWithPositionalArgs(captureAny, captureAny)) + .captured, equals([1, 2])); }); test("should captureOut with matching arguments", () { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); - expect(verify( - mock.methodWithPositionalArgs(captureAny, captureAny)).captured, + expect( + verify(mock.methodWithPositionalArgs(captureAny, captureAny)) + .captured, equals([2, 3])); }); test("should captureOut multiple invocations", () { From ad9dbb42f0c8b40e23973ec8353f0560d939429a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 3 Oct 2016 10:34:51 -0700 Subject: [PATCH 047/607] Document clearInteractions, reset, and logInvocations --- pkgs/mockito/README.md | 24 ++++++++++++++++++++++++ pkgs/mockito/lib/src/mock.dart | 3 +++ 2 files changed, 27 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 974fb7d50..09aec06f5 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -121,12 +121,14 @@ Verification in order is flexible - you don't have to verify all interactions on ```dart verifyZeroInteractions(cat); ``` + ## Finding redundant invocations ```dart cat.sound(); verify(cat.sound()); verifyNoMoreInteractions(cat); ``` + ## Capturing arguments for further assertions ```dart //simple capture @@ -141,6 +143,22 @@ cat.eatFood("Milk"); cat.eatFood("Fish"); expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); ``` + +## Resetting mocks +```dart +//clearing collected interactions +cat.eatFood("Fish"); +clearInteractions(cat); +cat.eatFood("Fish"); +verify(cat.eatFood("Fish")).called(1); +//resetting stubs and collected interactions +when(cat.eatFood("Fish")).thenReturn(true); +cat.eatFood("Fish"); +reset(cat); +when(cat.eatFood(any)).thenReturn(false); +expect(cat.eatFood("Fish"), false); +``` + ## Spy ```dart //spy creation @@ -153,6 +171,12 @@ expect(cat.sound(), "Purr"); expect(cat.lives, 9); ``` +## Debugging +```dart +//printing all collected invocations of any mock methods of a list of mock objects +logInvocations([catOne, catTwo]); +``` + ## Strong mode compliance Unfortunately, the use of the arg matchers in mock method calls (like `cat.eatFood(any)`) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 21f1f5fe6..4adf245d8 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -242,11 +242,13 @@ named(var mock, {String name, int hashCode}) => mock .._givenName = name .._givenHashCode = hashCode; +/// Clear stubs of, and collected interactions with [mock]. void reset(var mock) { mock._realCalls.clear(); mock._responses.clear(); } +/// Clear the collected interactions with [mock]. void clearInteractions(var mock) { mock._realCalls.clear(); } @@ -614,6 +616,7 @@ Expectation get when { }; } +/// Print all collected invocations of any mock methods of [mocks]. void logInvocations(List mocks) { List allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); From bff6291f0e873d78e3641f806192e4d358753221 Mon Sep 17 00:00:00 2001 From: Thibault Sottiaux Date: Mon, 3 Oct 2016 19:58:59 +0100 Subject: [PATCH 048/607] Expose Verification --- pkgs/mockito/lib/mockito_no_mirrors.dart | 1 + pkgs/mockito/lib/src/mock.dart | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/mockito_no_mirrors.dart b/pkgs/mockito/lib/mockito_no_mirrors.dart index 8ba07ffc1..eaecec01a 100644 --- a/pkgs/mockito/lib/mockito_no_mirrors.dart +++ b/pkgs/mockito/lib/mockito_no_mirrors.dart @@ -21,6 +21,7 @@ export 'src/mock.dart' verifyNoMoreInteractions, verifyZeroInteractions, VerificationResult, + Verification, // -- misc clearInteractions, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 28923e835..68ba1e4dc 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -526,15 +526,15 @@ class VerificationResult { typedef dynamic Answering(Invocation realInvocation); -typedef VerificationResult _Verification(matchingInvocations); +typedef VerificationResult Verification(matchingInvocations); typedef void _InOrderVerification(List recordedInvocations); -_Verification get verifyNever => _makeVerify(true); +Verification get verifyNever => _makeVerify(true); -_Verification get verify => _makeVerify(false); +Verification get verify => _makeVerify(false); -_Verification _makeVerify(bool never) { +Verification _makeVerify(bool never) { if (_verifyCalls.isNotEmpty) { throw new StateError(_verifyCalls.join()); } From caa00ee1ba60687727d67d3ce9b835a42e492f40 Mon Sep 17 00:00:00 2001 From: Thibault Sottiaux Date: Mon, 3 Oct 2016 20:18:38 +0100 Subject: [PATCH 049/607] Bump version to 2.0.0-dev --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index cd508e3f9..1d88f356a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 1.0.1+2 +version: 2.0.0-dev authors: - Dmitriy Fibulwinter - Ted Sander From b820e2dc29de7cf55accef6498e613b0eeea0b98 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 6 Oct 2016 21:02:07 -0700 Subject: [PATCH 050/607] Deprecate spy(), make the default no-mirrors. --- pkgs/mockito/CHANGELOG.md | 7 +++++ pkgs/mockito/lib/deprecated.dart | 5 ++++ pkgs/mockito/lib/mockito.dart | 31 ++++++++++++++++++-- pkgs/mockito/lib/mockito_no_mirrors.dart | 31 ++------------------ pkgs/mockito/lib/src/spy.dart | 14 +++++++++ pkgs/mockito/test/mockito_test.dart | 23 ++------------- pkgs/mockito/test/spy_test.dart | 37 ++++++++++++++++++++++++ 7 files changed, 97 insertions(+), 51 deletions(-) create mode 100644 pkgs/mockito/lib/deprecated.dart create mode 100644 pkgs/mockito/test/spy_test.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index d20582bd9..162335dc8 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.0-dev + +* Deprecated usage of `spy` and any `dart:mirrors` based API. Users may + import as `package:mockito/deprecated.dart` until the 2.0.0 final + release. +* Deprecated `mockito_no_mirrors.dart`; replace with `mockito.dart`. + ## 1.0.1 * Add a new `thenThrow` method to the API. diff --git a/pkgs/mockito/lib/deprecated.dart b/pkgs/mockito/lib/deprecated.dart new file mode 100644 index 000000000..f15817147 --- /dev/null +++ b/pkgs/mockito/lib/deprecated.dart @@ -0,0 +1,5 @@ +@Deprecated('Using spy() is deprecated as we drop dart:mirrors usage') +library mockito.deprecated; + +export 'mockito.dart'; +export 'src/spy.dart' show spy; diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 44a2e01f7..f204c69cf 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,2 +1,29 @@ -export 'mockito_no_mirrors.dart'; -export 'src/spy.dart' show spy; +export 'src/mock.dart' + show + Mock, + named, + + // -- setting behaviour + when, + any, + argThat, + captureAny, + captureThat, + typed, + Answering, + Expectation, + PostExpectation, + + // -- verification + verify, + verifyInOrder, + verifyNever, + verifyNoMoreInteractions, + verifyZeroInteractions, + VerificationResult, + Verification, + + // -- misc + clearInteractions, + reset, + logInvocations; diff --git a/pkgs/mockito/lib/mockito_no_mirrors.dart b/pkgs/mockito/lib/mockito_no_mirrors.dart index eaecec01a..878e67e9a 100644 --- a/pkgs/mockito/lib/mockito_no_mirrors.dart +++ b/pkgs/mockito/lib/mockito_no_mirrors.dart @@ -1,29 +1,4 @@ -export 'src/mock.dart' - show - Mock, - named, +@Deprecated('Import "mockito.dart" instead.') +library mockito.mockito_no_mirrors; - // -- setting behaviour - when, - any, - argThat, - captureAny, - captureThat, - typed, - Answering, - Expectation, - PostExpectation, - - // -- verification - verify, - verifyInOrder, - verifyNever, - verifyNoMoreInteractions, - verifyZeroInteractions, - VerificationResult, - Verification, - - // -- misc - clearInteractions, - reset, - logInvocations; +export 'mockito.dart'; diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart index d7fbccddf..bc872d515 100644 --- a/pkgs/mockito/lib/src/spy.dart +++ b/pkgs/mockito/lib/src/spy.dart @@ -4,6 +4,20 @@ import 'dart:mirrors'; import 'mock.dart' show CannedResponse, setDefaultResponse; +@Deprecated( + 'Mockito is dropping support for spy(). Instead it is recommended to come ' + 'up with a code-generation technique or just hand-coding a Stub object that ' + 'forwards to a delegate. For example:\n\n' + 'class SpyAnimal implements Animal {\n' + ' final Animal _delegate;\n' + ' FakeAnimal(this._delegate);\n' + '\n' + ' @override\n' + ' void eat() {\n' + ' _delegate.eat();\n' + ' }\n' + '}' +) dynamic spy(dynamic mock, dynamic spiedObject) { var mirror = reflect(spiedObject); setDefaultResponse( diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 2dd4b9bbf..b203bca6f 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -1,8 +1,7 @@ +import 'package:mockito/mockito.dart'; +import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:test/test.dart'; -import 'package:mockito/src/mock.dart'; -import 'package:mockito/src/spy.dart'; - class RealClass { String methodWithoutArgs() => "Real"; String methodWithNormalArgs(int x) => "Real"; @@ -69,24 +68,6 @@ void main() { resetMockitoState(); }); - group("spy", () { - setUp(() { - mock = spy(new MockedClass(), new RealClass()); - }); - - test("should delegate to real object by default", () { - expect(mock.methodWithoutArgs(), 'Real'); - }); - test("should record interactions delegated to real object", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - test("should behave as mock when expectation are set", () { - when(mock.methodWithoutArgs()).thenReturn('Spied'); - expect(mock.methodWithoutArgs(), 'Spied'); - }); - }); - group("mixin support", () { test("should work", () { var foo = new MockFoo(); diff --git a/pkgs/mockito/test/spy_test.dart b/pkgs/mockito/test/spy_test.dart new file mode 100644 index 000000000..93e51e0ea --- /dev/null +++ b/pkgs/mockito/test/spy_test.dart @@ -0,0 +1,37 @@ +import 'package:mockito/src/mock.dart' show resetMockitoState; +import 'package:mockito/deprecated.dart'; +import 'package:test/test.dart'; + +import 'mockito_test.dart' show MockedClass, RealClass; + +void main() { + RealClass mock; + + setUp(() { + mock = new MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group("spy", () { + setUp(() { + mock = spy(new MockedClass(), new RealClass()); + }); + + test("should delegate to real object by default", () { + expect(mock.methodWithoutArgs(), 'Real'); + }); + test("should record interactions delegated to real object", () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + test("should behave as mock when expectation are set", () { + when(mock.methodWithoutArgs()).thenReturn('Spied'); + expect(mock.methodWithoutArgs(), 'Spied'); + }); + }); +} From e03f5756603f44f683aa116e6b78124d4bafded3 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Fri, 7 Oct 2016 08:50:26 -0700 Subject: [PATCH 051/607] Address feedback, keep mirrors around. --- pkgs/mockito/CHANGELOG.md | 6 ++-- pkgs/mockito/lib/deprecated.dart | 5 ---- pkgs/mockito/lib/mirrors.dart | 2 ++ pkgs/mockito/lib/mockito_no_mirrors.dart | 2 +- pkgs/mockito/lib/src/mock.dart | 3 +- pkgs/mockito/lib/src/spy.dart | 28 +++++++------------ .../mockito/test/invocation_matcher_test.dart | 2 +- pkgs/mockito/test/spy_test.dart | 4 +-- 8 files changed, 20 insertions(+), 32 deletions(-) delete mode 100644 pkgs/mockito/lib/deprecated.dart create mode 100644 pkgs/mockito/lib/mirrors.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 162335dc8..89117a59f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,8 @@ ## 2.0.0-dev -* Deprecated usage of `spy` and any `dart:mirrors` based API. Users may - import as `package:mockito/deprecated.dart` until the 2.0.0 final - release. +* Remove export of `spy` and any `dart:mirrors` based API from + `mockito.dart`. Users may import as `package:mockito/mirrors.dart` + going forward. * Deprecated `mockito_no_mirrors.dart`; replace with `mockito.dart`. ## 1.0.1 diff --git a/pkgs/mockito/lib/deprecated.dart b/pkgs/mockito/lib/deprecated.dart deleted file mode 100644 index f15817147..000000000 --- a/pkgs/mockito/lib/deprecated.dart +++ /dev/null @@ -1,5 +0,0 @@ -@Deprecated('Using spy() is deprecated as we drop dart:mirrors usage') -library mockito.deprecated; - -export 'mockito.dart'; -export 'src/spy.dart' show spy; diff --git a/pkgs/mockito/lib/mirrors.dart b/pkgs/mockito/lib/mirrors.dart new file mode 100644 index 000000000..18a7952f2 --- /dev/null +++ b/pkgs/mockito/lib/mirrors.dart @@ -0,0 +1,2 @@ +export 'mockito.dart'; +export 'src/spy.dart' show spy; diff --git a/pkgs/mockito/lib/mockito_no_mirrors.dart b/pkgs/mockito/lib/mockito_no_mirrors.dart index 878e67e9a..c6f7da4d3 100644 --- a/pkgs/mockito/lib/mockito_no_mirrors.dart +++ b/pkgs/mockito/lib/mockito_no_mirrors.dart @@ -1,4 +1,4 @@ -@Deprecated('Import "mockito.dart" instead.') +@Deprecated('Import "package:mockito/mockito.dart" instead.') library mockito.mockito_no_mirrors; export 'mockito.dart'; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index c19aca90e..6091ba427 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -1,6 +1,5 @@ // Warning: Do not import dart:mirrors in this library, as it's exported via -// lib/mockito_no_mirrors.dart, which is used for Dart AOT projects such as -// Flutter. +// lib/mockito.dart, which is used for Dart AOT projects such as Flutter. import 'package:meta/meta.dart'; import 'package:test/test.dart'; diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart index bc872d515..08966aee3 100644 --- a/pkgs/mockito/lib/src/spy.dart +++ b/pkgs/mockito/lib/src/spy.dart @@ -1,25 +1,17 @@ // This file is intentionally separated from 'mock.dart' in order to avoid -// bringing in the mirrors dependency into mockito_no_mirrors.dart. +// bringing in the mirrors dependency into mockito.dart. import 'dart:mirrors'; -import 'mock.dart' show CannedResponse, setDefaultResponse; +import 'mock.dart' show CannedResponse, Mock, setDefaultResponse; -@Deprecated( - 'Mockito is dropping support for spy(). Instead it is recommended to come ' - 'up with a code-generation technique or just hand-coding a Stub object that ' - 'forwards to a delegate. For example:\n\n' - 'class SpyAnimal implements Animal {\n' - ' final Animal _delegate;\n' - ' FakeAnimal(this._delegate);\n' - '\n' - ' @override\n' - ' void eat() {\n' - ' _delegate.eat();\n' - ' }\n' - '}' -) -dynamic spy(dynamic mock, dynamic spiedObject) { - var mirror = reflect(spiedObject); +/// Sets the default response of [mock] to be delegated to [spyOn]. +/// +/// __Example use__: +/// var mockAnimal = new MockAnimal(); +/// var realAnimal = new RealAnimal(); +/// spy(mockAnimal, realAnimal); +/*=E*/ spy/**/(Mock mock, Object /*=E*/ spyOn) { + var mirror = reflect(spyOn); setDefaultResponse( mock, () => new CannedResponse(null, diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index d148196de..31ed88d10 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -54,7 +54,7 @@ void main() { var call2 = stub.value; stub.value = true; var call3 = Stub.lastInvocation; - shouldPass(call1, isInvocation(call2)); + shouldPass(call1, isInvocation(call2 as Invocation)); shouldFail( call1, isInvocation(call3), diff --git a/pkgs/mockito/test/spy_test.dart b/pkgs/mockito/test/spy_test.dart index 93e51e0ea..4ebfa6051 100644 --- a/pkgs/mockito/test/spy_test.dart +++ b/pkgs/mockito/test/spy_test.dart @@ -1,5 +1,5 @@ import 'package:mockito/src/mock.dart' show resetMockitoState; -import 'package:mockito/deprecated.dart'; +import 'package:mockito/mirrors.dart'; import 'package:test/test.dart'; import 'mockito_test.dart' show MockedClass, RealClass; @@ -19,7 +19,7 @@ void main() { group("spy", () { setUp(() { - mock = spy(new MockedClass(), new RealClass()); + mock = spy/**/(new MockedClass(), new RealClass()); }); test("should delegate to real object by default", () { From 63c48dd8a1dedf55fc791ee4b1fa6c3989104c2b Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 7 Nov 2016 16:29:19 -0800 Subject: [PATCH 052/607] Updates for ownership transfer to Dart team --- pkgs/mockito/CONTRIBUTING.md | 29 +++++++++++++++++++ pkgs/mockito/LICENSE | 28 +++++++----------- pkgs/mockito/README.md | 2 ++ pkgs/mockito/lib/mirrors.dart | 14 +++++++++ pkgs/mockito/lib/mockito.dart | 14 +++++++++ pkgs/mockito/lib/mockito_no_mirrors.dart | 14 +++++++++ pkgs/mockito/lib/src/invocation_matcher.dart | 14 +++++++++ pkgs/mockito/lib/src/mock.dart | 14 +++++++++ pkgs/mockito/lib/src/spy.dart | 14 +++++++++ pkgs/mockito/pubspec.yaml | 6 ++-- .../mockito/test/invocation_matcher_test.dart | 14 +++++++++ pkgs/mockito/test/mockito_test.dart | 14 +++++++++ pkgs/mockito/test/spy_test.dart | 14 +++++++++ pkgs/mockito/tool/travis.sh | 14 +++++++++ 14 files changed, 183 insertions(+), 22 deletions(-) create mode 100644 pkgs/mockito/CONTRIBUTING.md diff --git a/pkgs/mockito/CONTRIBUTING.md b/pkgs/mockito/CONTRIBUTING.md new file mode 100644 index 000000000..e8cd89080 --- /dev/null +++ b/pkgs/mockito/CONTRIBUTING.md @@ -0,0 +1,29 @@ +Want to contribute? Great! First, read this page (including the small print at the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement] +(https://cla.developers.google.com/about/google-individual) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. + +### The small print +Contributions made by corporations are covered by a different agreement than +the one above, the +[Software Grant and Corporate Contributor License Agreement] +(https://cla.developers.google.com/about/google-corporate). + +**NOTE:** This is not an official Google product diff --git a/pkgs/mockito/LICENSE b/pkgs/mockito/LICENSE index f351d4bf2..f7345eb9d 100644 --- a/pkgs/mockito/LICENSE +++ b/pkgs/mockito/LICENSE @@ -1,21 +1,13 @@ -The MIT License (MIT) +Copyright 2016 Dart Mockito authors -Copyright (c) 2014 fibulwinter +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + http://www.apache.org/licenses/LICENSE-2.0 -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 09aec06f5..07bbe989d 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -306,3 +306,5 @@ values anymore but their matchers. So `mockUtils.stringUtils` will *not* return You can look at the `when` and `Mock.noSuchMethod` implementations to see how it's done. It's very straightforward. + +**NOTE:** This is not an official Google product diff --git a/pkgs/mockito/lib/mirrors.dart b/pkgs/mockito/lib/mirrors.dart index 18a7952f2..10fa9f63b 100644 --- a/pkgs/mockito/lib/mirrors.dart +++ b/pkgs/mockito/lib/mirrors.dart @@ -1,2 +1,16 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + export 'mockito.dart'; export 'src/spy.dart' show spy; diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index f204c69cf..4518c48ad 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + export 'src/mock.dart' show Mock, diff --git a/pkgs/mockito/lib/mockito_no_mirrors.dart b/pkgs/mockito/lib/mockito_no_mirrors.dart index c6f7da4d3..7c3566554 100644 --- a/pkgs/mockito/lib/mockito_no_mirrors.dart +++ b/pkgs/mockito/lib/mockito_no_mirrors.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @Deprecated('Import "package:mockito/mockito.dart" instead.') library mockito.mockito_no_mirrors; diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index c176a1b8c..b3ff2feda 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'package:collection/collection.dart'; import 'package:matcher/matcher.dart'; import 'package:meta/meta.dart'; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 6091ba427..3da51c4d9 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // Warning: Do not import dart:mirrors in this library, as it's exported via // lib/mockito.dart, which is used for Dart AOT projects such as Flutter. diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart index 08966aee3..785483100 100644 --- a/pkgs/mockito/lib/src/spy.dart +++ b/pkgs/mockito/lib/src/spy.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // This file is intentionally separated from 'mock.dart' in order to avoid // bringing in the mirrors dependency into mockito.dart. import 'dart:mirrors'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1d88f356a..1157fa739 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -2,11 +2,9 @@ name: mockito version: 2.0.0-dev authors: - Dmitriy Fibulwinter - - Ted Sander - - Samuel Rawlins - - Matan Lurey + - Dart Team description: A mock framework inspired by Mockito. -homepage: https://github.com/fibulwinter/dart-mockito +homepage: https://github.com/dart-lang/mockito environment: sdk: '>=1.0.0 <2.0.0' dependencies: diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 31ed88d10..bd96900fd 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'package:mockito/src/invocation_matcher.dart'; import 'package:test/test.dart'; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index b203bca6f..7b95a4bfc 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'package:mockito/mockito.dart'; import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:test/test.dart'; diff --git a/pkgs/mockito/test/spy_test.dart b/pkgs/mockito/test/spy_test.dart index 4ebfa6051..83a7229ca 100644 --- a/pkgs/mockito/test/spy_test.dart +++ b/pkgs/mockito/test/spy_test.dart @@ -1,3 +1,17 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:mockito/mirrors.dart'; import 'package:test/test.dart'; diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index e22fa9857..4ca803275 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -1,3 +1,17 @@ +# Copyright 2016 Dart Mockito authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + #!/bin/bash # Fast fail the script on failures. From f731a7d46addada0b8fe1a569fa0bc853aba0c3b Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Dec 2016 10:14:38 -0800 Subject: [PATCH 053/607] dartfmt --- pkgs/mockito/lib/mockito.dart | 48 +++++++++++++++++------------------ pkgs/mockito/lib/src/spy.dart | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 4518c48ad..4ece809f4 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -14,30 +14,30 @@ export 'src/mock.dart' show - Mock, - named, + Mock, + named, - // -- setting behaviour - when, - any, - argThat, - captureAny, - captureThat, - typed, - Answering, - Expectation, - PostExpectation, + // -- setting behaviour + when, + any, + argThat, + captureAny, + captureThat, + typed, + Answering, + Expectation, + PostExpectation, - // -- verification - verify, - verifyInOrder, - verifyNever, - verifyNoMoreInteractions, - verifyZeroInteractions, - VerificationResult, - Verification, + // -- verification + verify, + verifyInOrder, + verifyNever, + verifyNoMoreInteractions, + verifyZeroInteractions, + VerificationResult, + Verification, - // -- misc - clearInteractions, - reset, - logInvocations; + // -- misc + clearInteractions, + reset, + logInvocations; diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart index 785483100..79a364d45 100644 --- a/pkgs/mockito/lib/src/spy.dart +++ b/pkgs/mockito/lib/src/spy.dart @@ -24,7 +24,7 @@ import 'mock.dart' show CannedResponse, Mock, setDefaultResponse; /// var mockAnimal = new MockAnimal(); /// var realAnimal = new RealAnimal(); /// spy(mockAnimal, realAnimal); -/*=E*/ spy/**/(Mock mock, Object /*=E*/ spyOn) { +/*=E*/ spy/**/(Mock mock, Object/*=E*/ spyOn) { var mirror = reflect(spyOn); setDefaultResponse( mock, From a9a7acabe68ab67dcc2db8819a17ac6f561b2479 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Dec 2016 10:16:31 -0800 Subject: [PATCH 054/607] standardize travis --- pkgs/mockito/.travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index b1279b7e4..99484d913 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -1,3 +1,9 @@ language: dart -script: ./tool/travis.sh sudo: false +dart: + - stable + - dev + - 1.21.0 + - 1.20.1 + - 1.19.1 +script: ./tool/travis.sh From d26d26891b8e16a63aed0261e899872676255896 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Dec 2016 10:16:38 -0800 Subject: [PATCH 055/607] tweak readme --- pkgs/mockito/README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 07bbe989d..c8b1c512b 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,7 +1,7 @@ Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). [![Pub](https://img.shields.io/pub/v/mockito.svg)]() -[![Build Status](https://travis-ci.org/fibulwinter/dart-mockito.svg?branch=master)](https://travis-ci.org/fibulwinter/dart-mockito) +[![Build Status](https://travis-ci.org/dart-lang/dart-mockito.svg?branch=master)](https://travis-ci.org/dart-lang/dart-mockito) Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. @@ -36,7 +36,8 @@ cat.sound(); //verify interaction verify(cat.sound()); ``` -Once created, mock will remember all interactions. Then you can selectively verify whatever interaction you are interested in. +Once created, mock will remember all interactions. Then you can selectively verify whatever interaction you are +interested in. ## How about some stubbing? ```dart @@ -64,9 +65,12 @@ expect(cat.sound(), "Meow"); ``` By default, for all methods that return value, mock returns null. -Stubbing can be overridden: for example common stubbing can go to fixture setup but the test methods can override it. Please note that overridding stubbing is a potential code smell that points out too much stubbing. +Stubbing can be overridden: for example common stubbing can go to fixture setup but the test methods can override it. +Please note that overridding stubbing is a potential code smell that points out too much stubbing. Once stubbed, the method will always return stubbed value regardless of how many times it is called. -Last stubbing is more important - when you stubbed the same method with the same arguments many times. Other words: the order of stubbing matters but it is only meaningful rarely, e.g. when stubbing exactly the same method calls or sometimes when argument matchers are used, etc. +Last stubbing is more important - when you stubbed the same method with the same arguments many times. Other words: the +order of stubbing matters but it is only meaningful rarely, e.g. when stubbing exactly the same method calls or +sometimes when argument matchers are used, etc. ## Argument matchers ```dart @@ -90,7 +94,8 @@ verify(cat.eatFood(argThat(contains("food")))); cat.lives = 9; verify(cat.lives=9); ``` -By default equals matcher is used to argument matching (since 0.11.0). It simplifies matching for collections as arguments. If you need more strict matching consider use `argThat(identical(arg))`. +By default equals matcher is used to argument matching (since 0.11.0). It simplifies matching for collections as +arguments. If you need more strict matching consider use `argThat(identical(arg))`. Argument matchers allow flexible verification or stubbing ## Verifying exact number of invocations / at least x / never @@ -115,7 +120,8 @@ verifyInOrder([ cat.eatFood("Fish") ]); ``` -Verification in order is flexible - you don't have to verify all interactions one-by-one but only those that you are interested in testing in order. +Verification in order is flexible - you don't have to verify all interactions one-by-one but only those that you are +interested in testing in order. ## Making sure interaction(s) never happened on mock ```dart From f019745217147e2c5ec85ba751e66bd0988e2103 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Dec 2016 10:19:30 -0800 Subject: [PATCH 056/607] analysis options and use generic methods Requires Dart 1.21 --- pkgs/mockito/.analysis_options | 29 +++++++++++++++++++++++++++++ pkgs/mockito/.travis.yml | 2 -- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/mock.dart | 16 +++++++++++++--- pkgs/mockito/lib/src/spy.dart | 4 ++-- pkgs/mockito/pubspec.yaml | 4 ++-- pkgs/mockito/test/mockito_test.dart | 3 ++- 7 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 pkgs/mockito/.analysis_options diff --git a/pkgs/mockito/.analysis_options b/pkgs/mockito/.analysis_options new file mode 100644 index 000000000..092773bea --- /dev/null +++ b/pkgs/mockito/.analysis_options @@ -0,0 +1,29 @@ +analyzer: + strong-mode: true +linter: + rules: + # Errors + - avoid_empty_else + - comment_references + - control_flow_in_finally + - empty_statements + - hash_and_equals + - test_types_in_equals + - throw_in_finally + - unrelated_type_equality_checks + - valid_regexps + + # Style + - annotate_overrides + - avoid_init_to_null + - avoid_return_types_on_setters + - await_only_futures + - camel_case_types + - empty_catches + - empty_constructor_bodies + - library_names + - library_prefixes + - non_constant_identifier_names + - prefer_is_not_empty + - slash_for_doc_comments + - type_init_formals diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 99484d913..6a7185dc8 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -4,6 +4,4 @@ dart: - stable - dev - 1.21.0 - - 1.20.1 - - 1.19.1 script: ./tool/travis.sh diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 89117a59f..476fd92d7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -4,6 +4,7 @@ `mockito.dart`. Users may import as `package:mockito/mirrors.dart` going forward. * Deprecated `mockito_no_mirrors.dart`; replace with `mockito.dart`. +* Require Dart SDK `>=1.21.0 <2.0.0` to use generic methods. ## 1.0.1 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 3da51c4d9..c5bfd816e 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -20,7 +20,7 @@ import 'package:test/test.dart'; bool _whenInProgress = false; bool _verificationInProgress = false; -_WhenCall _whenCall = null; +_WhenCall _whenCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; final _TimeStampProvider _timer = new _TimeStampProvider(); final List _capturedArgs = []; @@ -70,8 +70,8 @@ class Mock { final List _realCalls = []; final List _responses = []; - String _givenName = null; - int _givenHashCode = null; + String _givenName; + int _givenHashCode; _ReturnsCannedResponse _defaultResponse = _nullResponse; @@ -102,12 +102,15 @@ class Mock { } } + @override int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; + @override bool operator ==(other) => (_givenHashCode != null && other is Mock) ? _givenHashCode == other._givenHashCode : identical(this, other); + @override String toString() => _givenName != null ? _givenName : runtimeType.toString(); } @@ -126,11 +129,17 @@ Invocation _useTypedInvocationIfSet(Invocation invocation) { /// An Invocation implementation that takes arguments from [_typedArgs] and /// [_typedNamedArgs]. class _InvocationForTypedArguments extends Invocation { + @override final Symbol memberName; + @override final Map namedArguments; + @override final List positionalArguments; + @override final bool isGetter; + @override final bool isMethod; + @override final bool isSetter; factory _InvocationForTypedArguments(Invocation invocation) { @@ -406,6 +415,7 @@ class RealCall { RealCall(this.mock, this.invocation) : timeStamp = _timer.now(); + @override String toString() { var args = invocation.positionalArguments .map((v) => v == null ? "null" : v.toString()) diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart index 79a364d45..3abe37d71 100644 --- a/pkgs/mockito/lib/src/spy.dart +++ b/pkgs/mockito/lib/src/spy.dart @@ -24,11 +24,11 @@ import 'mock.dart' show CannedResponse, Mock, setDefaultResponse; /// var mockAnimal = new MockAnimal(); /// var realAnimal = new RealAnimal(); /// spy(mockAnimal, realAnimal); -/*=E*/ spy/**/(Mock mock, Object/*=E*/ spyOn) { +E spy(Mock mock, E spyOn) { var mirror = reflect(spyOn); setDefaultResponse( mock, () => new CannedResponse(null, (Invocation realInvocation) => mirror.delegate(realInvocation))); - return mock; + return mock as E; } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1157fa739..b5ab85530 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,12 +1,12 @@ name: mockito -version: 2.0.0-dev +version: 2.0.0 authors: - Dmitriy Fibulwinter - Dart Team description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=1.0.0 <2.0.0' + sdk: '>=1.21.0 <2.0.0' dependencies: matcher: '^0.12.0' meta: '^1.0.4' diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 7b95a4bfc..2f709586f 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -35,7 +35,7 @@ class RealClass { {List y, List z}) => "Real"; String get getter => "Real"; - void set setter(String arg) { + set setter(String arg) { throw new StateError("I must be mocked"); } } @@ -45,6 +45,7 @@ abstract class Foo { } abstract class AbstractFoo implements Foo { + @override String bar() => baz(); String baz(); From 99291c8a5b9f27ed794c955d609b0fdb999ee340 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Dec 2016 10:34:42 -0800 Subject: [PATCH 057/607] cleanup ignore files --- pkgs/mockito/.gitignore | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index 8d4854698..04fd6ee54 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -1,30 +1,6 @@ -# See https://www.dartlang.org/tools/private-files.html +# See https://www.dartlang.org/guides/libraries/private-files # Files and directories created by pub -.buildlog .packages -.project .pub -**/build -**/packages - -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - -# Directory created by dartdoc -doc/api/ - -# Don't commit pubspec lock file -# (Library packages only! Remove pattern if developing an application package) pubspec.lock - -*.iml -.idea From 3323bdc790535f5641e047aa21cb5016b46d5c24 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Dec 2016 10:34:51 -0800 Subject: [PATCH 058/607] cleanup travis test script --- pkgs/mockito/tool/travis.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 4ca803275..60fca55ef 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -18,12 +18,6 @@ set -e # Verify that the libraries are error free. -dartanalyzer --fatal-warnings \ - lib/mockito.dart \ - lib/mockito_no_mirrors.dart \ - lib/src/invocation_matcher.dart \ - test/mockito_test.dart +find . -maxdepth 2 -name *.dart | xargs dartanalyzer --fatal-warnings -# Run the tests. -dart -c test/invocation_matcher_test.dart -dart -c test/mockito_test.dart +pub run test From 3b1a1329261cc8ea6ca2863a9be0c8e72b90f963 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 3 Jan 2017 00:45:49 -0500 Subject: [PATCH 059/607] Cleanup for v2.0.0 (dart-lang/mockito#49) * Cleanup for v2.0.0 * CHANGELOG --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/mockito_no_mirrors.dart | 18 --------------- pkgs/mockito/tool/travis.sh | 28 ++++++++++++++++++++---- 3 files changed, 28 insertions(+), 22 deletions(-) delete mode 100644 pkgs/mockito/lib/mockito_no_mirrors.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 476fd92d7..a06437228 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0 + +* Removed `mockito_no_mirrors.dart` + ## 2.0.0-dev * Remove export of `spy` and any `dart:mirrors` based API from diff --git a/pkgs/mockito/lib/mockito_no_mirrors.dart b/pkgs/mockito/lib/mockito_no_mirrors.dart deleted file mode 100644 index 7c3566554..000000000 --- a/pkgs/mockito/lib/mockito_no_mirrors.dart +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2016 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@Deprecated('Import "package:mockito/mockito.dart" instead.') -library mockito.mockito_no_mirrors; - -export 'mockito.dart'; diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 60fca55ef..4d2387ab6 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -14,10 +14,30 @@ #!/bin/bash -# Fast fail the script on failures. -set -e +# Make sure dartfmt is run on everything +# This assumes you have dart_style as a dev_dependency +echo "Checking dartfmt..." +NEEDS_DARTFMT="$(find lib test -name "*.dart" | xargs dartfmt -n)" +if [[ ${NEEDS_DARTFMT} != "" ]] +then + echo "FAILED" + echo "${NEEDS_DARTFMT}" + exit 1 +fi +echo "PASSED" + +# Make sure we pass the analyzer +echo "Checking dartanalyzer..." +FAILS_ANALYZER="$(find lib test -name "*.dart" | xargs dartanalyzer --options .analysis_options)" +if [[ $FAILS_ANALYZER == *"[error]"* ]] +then + echo "FAILED" + echo "${FAILS_ANALYZER}" + exit 1 +fi +echo "PASSED" -# Verify that the libraries are error free. -find . -maxdepth 2 -name *.dart | xargs dartanalyzer --fatal-warnings +# Fail on anything that fails going forward. +set -e pub run test From 07836afd3b9c920280b072e87037a6eea10558b1 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 3 Jan 2017 14:24:09 -0800 Subject: [PATCH 060/607] Add new throwOnMissingStub method (dart-lang/mockito#48) * Add new throwOnMissingStub method * Bump to 2.0.1 for this fix --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/README.md | 4 +++- pkgs/mockito/lib/src/mock.dart | 17 ++++++++++++++--- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/mockito_test.dart | 18 +++++++++++++++++- 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a06437228..f5a663315 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.1 + +* Add a new `throwOnMissingStub` method to the API. + ## 2.0.0 * Removed `mockito_no_mirrors.dart` diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index c8b1c512b..7a33508ac 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -179,8 +179,10 @@ expect(cat.lives, 9); ## Debugging ```dart -//printing all collected invocations of any mock methods of a list of mock objects +//print all collected invocations of any mock methods of a list of mock objects logInvocations([catOne, catTwo]); +//throw every time that a mock method is called without a stub being matched +throwOnMissingStub(cat); ``` ## Strong mode compliance diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index c5bfd816e..5af92aad9 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -32,6 +32,10 @@ void setDefaultResponse(Mock mock, CannedResponse defaultResponse()) { mock._defaultResponse = defaultResponse; } +throwOnMissingStub(Mock mock) { + mock._defaultResponse = Mock._throwResponse; +} + /// Extend or mixin this class to mark the implementation as a [Mock]. /// /// A mocked class implements all fields and methods with a default @@ -64,9 +68,16 @@ void setDefaultResponse(Mock mock, CannedResponse defaultResponse()) { /// used within the context of Dart for Web (dart2js/ddc) and Dart for Mobile /// (flutter). class Mock { - static CannedResponse _nullResponse() { - return new CannedResponse(null, (_) => null); - } + static CannedResponse _nullResponse() => + new CannedResponse(null, (_) => null); + + static CannedResponse _throwResponse() => new CannedResponse( + null, + (Invocation inv) => + throw new UnimplementedError('''No stub for invocation: + member name: ${inv.memberName} + positional arguments (${inv.positionalArguments.length}): ${inv.positionalArguments} + named arguments (${inv.namedArguments.length}): ${inv.namedArguments}''')); final List _realCalls = []; final List _responses = []; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b5ab85530..43ff510a6 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 2.0.0 +version: 2.0.1 authors: - Dmitriy Fibulwinter - Dart Team diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 2f709586f..c69f950d1 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -13,7 +13,8 @@ // limitations under the License. import 'package:mockito/mockito.dart'; -import 'package:mockito/src/mock.dart' show resetMockitoState; +import 'package:mockito/src/mock.dart' + show resetMockitoState, throwOnMissingStub; import 'package:test/test.dart'; class RealClass { @@ -616,4 +617,19 @@ void main() { equals([1, 2])); }); }); + + group("throwOnMissingStub", () { + test("should throw when a mock was called without a matching stub", () { + throwOnMissingStub(mock as Mock); + when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); + expect(() => (mock as MockedClass).methodWithoutArgs(), + throwsUnimplementedError); + }); + + test("should not throw when a mock was called with a matching stub", () { + throwOnMissingStub(mock as Mock); + when(mock.methodWithoutArgs()).thenReturn("A"); + expect(() => mock.methodWithoutArgs(), returnsNormally); + }); + }); } From cfa94c3a61d0398fbdfa6f846d886a300b40746c Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Wed, 1 Feb 2017 19:16:15 -0800 Subject: [PATCH 061/607] Use new matcher (dart-lang/mockito#52) * Start using the new InvocationMatcher * Some fixes. --- pkgs/mockito/CHANGELOG.md | 7 ++ pkgs/mockito/lib/src/call_pair.dart | 36 +++++++++ pkgs/mockito/lib/src/invocation_matcher.dart | 16 +++- pkgs/mockito/lib/src/mock.dart | 85 ++++++++++---------- pkgs/mockito/lib/src/spy.dart | 10 ++- pkgs/mockito/test/mockito_test.dart | 6 +- 6 files changed, 107 insertions(+), 53 deletions(-) create mode 100644 pkgs/mockito/lib/src/call_pair.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f5a663315..972d940df 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.2 + +* Start using the new `InvocationMatcher` instead of the old matcher. +* Change `throwOnMissingStub` back to invoking `Object.noSuchMethod`: + * It was never documented what the thrown type should be expected as + * You can now just rely on `throwsNoSuchMethodError` if you want to catch it + ## 2.0.1 * Add a new `throwOnMissingStub` method to the API. diff --git a/pkgs/mockito/lib/src/call_pair.dart b/pkgs/mockito/lib/src/call_pair.dart new file mode 100644 index 000000000..bae33ebed --- /dev/null +++ b/pkgs/mockito/lib/src/call_pair.dart @@ -0,0 +1,36 @@ +// Copyright 2017 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:matcher/matcher.dart'; + +/// Returns a value dependent on the details of an [invocation]. +typedef T Answer(Invocation invocation); + +/// A captured method or property accessor -> a function that returns a value. +class CallPair { + /// A captured method or property accessor. + final Matcher call; + + /// Result function that should be invoked. + final Answer response; + + // TODO: Rename to `Expectation` in 3.0.0. + const CallPair(this.call, this.response); + + const CallPair.allInvocations(this.response) + : call = const isInstanceOf(); + + @override + String toString() => '$CallPair {$call -> $response}'; +} diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index b3ff2feda..eebd2978a 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -15,6 +15,7 @@ import 'package:collection/collection.dart'; import 'package:matcher/matcher.dart'; import 'package:meta/meta.dart'; +import 'package:mockito/src/mock.dart'; /// Returns a matcher that expects an invocation that matches arguments given. /// @@ -161,16 +162,25 @@ class _InvocationMatcher implements Matcher { .equals(_invocation.namedArguments, item.namedArguments); } -class _MatcherEquality extends DefaultEquality /* */ { +// Uses both DeepCollectionEquality and custom matching for invocation matchers. +class _MatcherEquality extends DeepCollectionEquality /* */ { const _MatcherEquality(); @override bool equals(e1, e2) { + // All argument matches are wrapped in `ArgMatcher`, so we have to unwrap + // them into the raw `Matcher` type in order to finish our equality checks. + if (e1 is ArgMatcher) { + e1 = e1.matcher; + } + if (e2 is ArgMatcher) { + e2 = e2.matcher; + } if (e1 is Matcher && e2 is! Matcher) { - return e1.matches(e2, const {}); + return e1.matches(e2, {}); } if (e2 is Matcher && e1 is! Matcher) { - return e2.matches(e1, const {}); + return e2.matches(e1, {}); } return super.equals(e1, e2); } diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 5af92aad9..f9c4632bb 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -16,6 +16,8 @@ // lib/mockito.dart, which is used for Dart AOT projects such as Flutter. import 'package:meta/meta.dart'; +import 'package:mockito/src/call_pair.dart'; +import 'package:mockito/src/invocation_matcher.dart'; import 'package:test/test.dart'; bool _whenInProgress = false; @@ -24,16 +26,19 @@ _WhenCall _whenCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; final _TimeStampProvider _timer = new _TimeStampProvider(); final List _capturedArgs = []; -final List<_ArgMatcher> _typedArgs = <_ArgMatcher>[]; -final Map _typedNamedArgs = {}; +final List _typedArgs = []; +final Map _typedNamedArgs = {}; // Hidden from the public API, used by spy.dart. -void setDefaultResponse(Mock mock, CannedResponse defaultResponse()) { +void setDefaultResponse(Mock mock, CallPair defaultResponse()) { mock._defaultResponse = defaultResponse; } -throwOnMissingStub(Mock mock) { - mock._defaultResponse = Mock._throwResponse; +/// Opt-into [Mock] throwing [NoSuchMethodError] for unimplemented methods. +/// +/// The default behavior when not using this is to always return `null`. +void throwOnMissingStub(Mock mock) { + mock._defaultResponse = () => new CallPair.allInvocations(mock._noSuchMethod); } /// Extend or mixin this class to mark the implementation as a [Mock]. @@ -68,25 +73,19 @@ throwOnMissingStub(Mock mock) { /// used within the context of Dart for Web (dart2js/ddc) and Dart for Mobile /// (flutter). class Mock { - static CannedResponse _nullResponse() => - new CannedResponse(null, (_) => null); - - static CannedResponse _throwResponse() => new CannedResponse( - null, - (Invocation inv) => - throw new UnimplementedError('''No stub for invocation: - member name: ${inv.memberName} - positional arguments (${inv.positionalArguments.length}): ${inv.positionalArguments} - named arguments (${inv.namedArguments.length}): ${inv.namedArguments}''')); - - final List _realCalls = []; - final List _responses = []; + static _answerNull(_) => null; + + static const _nullResponse = const CallPair.allInvocations(_answerNull); + + final _realCalls = []; + final _responses = []; + String _givenName; int _givenHashCode; - _ReturnsCannedResponse _defaultResponse = _nullResponse; + _ReturnsCannedResponse _defaultResponse = () => _nullResponse; - void _setExpected(CannedResponse cannedResponse) { + void _setExpected(CallPair cannedResponse) { _responses.add(cannedResponse); } @@ -106,13 +105,16 @@ class Mock { } else { _realCalls.add(new RealCall(this, invocation)); var cannedResponse = _responses.lastWhere( - (cr) => cr.matcher.matches(invocation), + (cr) => cr.call.matches(invocation, {}), orElse: _defaultResponse); var response = cannedResponse.response(invocation); return response; } } + _noSuchMethod(Invocation invocation) => + const Object().noSuchMethod(invocation); + @override int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; @@ -125,7 +127,7 @@ class Mock { String toString() => _givenName != null ? _givenName : runtimeType.toString(); } -typedef CannedResponse _ReturnsCannedResponse(); +typedef CallPair _ReturnsCannedResponse(); // When using the typed() matcher, we transform our invocation to have knowledge // of which arguments are wrapped with typed() and which ones are not. Otherwise @@ -340,7 +342,7 @@ class InvocationMatcher { int index = 0; for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; - if (roleArg is _ArgMatcher && roleArg._capture) { + if (roleArg is ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } index++; @@ -348,8 +350,8 @@ class InvocationMatcher { for (var roleKey in roleInvocation.namedArguments.keys) { var roleArg = roleInvocation.namedArguments[roleKey]; var actArg = invocation.namedArguments[roleKey]; - if (roleArg is _ArgMatcher) { - if (roleArg is _ArgMatcher && roleArg._capture) { + if (roleArg is ArgMatcher) { + if (roleArg is ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } } @@ -390,21 +392,14 @@ class InvocationMatcher { } bool isMatchingArg(roleArg, actArg) { - if (roleArg is _ArgMatcher) { - return roleArg._matcher.matches(actArg, {}); + if (roleArg is ArgMatcher) { + return roleArg.matcher.matches(actArg, {}); } else { return equals(roleArg).matches(actArg, {}); } } } -class CannedResponse { - InvocationMatcher matcher; - Answering response; - - CannedResponse(this.matcher, this.response); -} - class _TimeStampProvider { int _now = 0; DateTime now() { @@ -470,8 +465,7 @@ class _WhenCall { _WhenCall(this.mock, this.whenInvocation); void _setExpected(Answering answer) { - mock._setExpected( - new CannedResponse(new InvocationMatcher(whenInvocation), answer)); + mock._setExpected(new CallPair(isInvocation(whenInvocation), answer)); } } @@ -513,30 +507,33 @@ class _VerifyCall { } } -class _ArgMatcher { - final Matcher _matcher; +class ArgMatcher { + final Matcher matcher; final bool _capture; - _ArgMatcher(this._matcher, this._capture); + ArgMatcher(this.matcher, this._capture); + + @override + String toString() => '$ArgMatcher {$matcher: $_capture}'; } /// An argument matcher that matches any argument passed in "this" position. -get any => new _ArgMatcher(anything, false); +get any => new ArgMatcher(anything, false); /// An argument matcher that matches any argument passed in "this" position, and /// captures the argument for later access with `captured`. -get captureAny => new _ArgMatcher(anything, true); +get captureAny => new ArgMatcher(anything, true); /// An argument matcher that matches an argument that matches [matcher]. -argThat(Matcher matcher) => new _ArgMatcher(matcher, false); +argThat(Matcher matcher) => new ArgMatcher(matcher, false); /// An argument matcher that matches an argument that matches [matcher], and /// captures the argument for later access with `captured`. -captureThat(Matcher matcher) => new _ArgMatcher(matcher, true); +captureThat(Matcher matcher) => new ArgMatcher(matcher, true); /// A Strong-mode safe argument matcher that wraps other argument matchers. /// See the README for a full explanation. -/*=T*/ typed/**/(_ArgMatcher matcher, {String named}) { +/*=T*/ typed/**/(ArgMatcher matcher, {String named}) { if (named == null) { _typedArgs.add(matcher); } else { diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart index 3abe37d71..2f9b6c1f5 100644 --- a/pkgs/mockito/lib/src/spy.dart +++ b/pkgs/mockito/lib/src/spy.dart @@ -16,7 +16,9 @@ // bringing in the mirrors dependency into mockito.dart. import 'dart:mirrors'; -import 'mock.dart' show CannedResponse, Mock, setDefaultResponse; +import 'mock.dart' show Mock, setDefaultResponse; + +import 'package:mockito/src/call_pair.dart'; /// Sets the default response of [mock] to be delegated to [spyOn]. /// @@ -27,8 +29,8 @@ import 'mock.dart' show CannedResponse, Mock, setDefaultResponse; E spy(Mock mock, E spyOn) { var mirror = reflect(spyOn); setDefaultResponse( - mock, - () => new CannedResponse(null, - (Invocation realInvocation) => mirror.delegate(realInvocation))); + mock, + () => new CallPair.allInvocations(mirror.delegate), + ); return mock as E; } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index c69f950d1..49a4edc50 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -622,8 +622,10 @@ void main() { test("should throw when a mock was called without a matching stub", () { throwOnMissingStub(mock as Mock); when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); - expect(() => (mock as MockedClass).methodWithoutArgs(), - throwsUnimplementedError); + expect( + () => (mock as MockedClass).methodWithoutArgs(), + throwsNoSuchMethodError, + ); }); test("should not throw when a mock was called with a matching stub", () { From 2f63f2e05f54a4a2707849d7d3075cb7db26edef Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 8 Feb 2017 22:08:43 -0800 Subject: [PATCH 062/607] Fix travis badge --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 7a33508ac..c56a4df80 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,7 +1,7 @@ Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). [![Pub](https://img.shields.io/pub/v/mockito.svg)]() -[![Build Status](https://travis-ci.org/dart-lang/dart-mockito.svg?branch=master)](https://travis-ci.org/dart-lang/dart-mockito) +[![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. From 4f8fc0b69691ac7713462c73a4d380957fdd7347 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 9 Feb 2017 07:56:26 -0800 Subject: [PATCH 063/607] Update to latest stable release --- pkgs/mockito/.travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 6a7185dc8..6abd11c03 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -3,5 +3,5 @@ sudo: false dart: - stable - dev - - 1.21.0 + - 1.21.1 script: ./tool/travis.sh From 0f6eb059f65e50524b58e6d7f4d2dfa8e70227de Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 27 Feb 2017 10:27:53 -0800 Subject: [PATCH 064/607] Throw when using when within when (dart-lang/mockito#57) --- pkgs/mockito/lib/src/mock.dart | 3 +++ pkgs/mockito/test/mockito_test.dart | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index f9c4632bb..368f310d3 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -640,6 +640,9 @@ void verifyZeroInteractions(var mock) { typedef PostExpectation Expectation(x); Expectation get when { + if (_whenCall != null) { + throw new StateError('Cannot call `when` within a stub response'); + } _whenInProgress = true; return (_) { _whenInProgress = false; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 49a4edc50..427816caa 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -18,6 +18,7 @@ import 'package:mockito/src/mock.dart' import 'package:test/test.dart'; class RealClass { + RealClass innerObj; String methodWithoutArgs() => "Real"; String methodWithNormalArgs(int x) => "Real"; String methodWithListArgs(List x) => "Real"; @@ -218,6 +219,18 @@ void main() { when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); + test("should throw if `when` is called while stubbing", () { + expect(() { + var responseHelper = () { + var mock2 = new MockedClass(); + when(mock2.getter).thenReturn("A"); + return mock2; + }; + when(mock.innerObj).thenReturn(responseHelper()); + }, throwsStateError); + }); + + // [typed] API test("should mock method with typed arg matchers", () { when(mock.typeParameterizedFn(typed(any), typed(any))) .thenReturn("A lot!"); From 2846ab6757272ba15faf0a28b9ca35200290a4de Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 10 Mar 2017 10:35:25 -0800 Subject: [PATCH 065/607] Oops update pubspec to 2.0.2 as well --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 43ff510a6..4737bdee9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 2.0.1 +version: 2.0.2 authors: - Dmitriy Fibulwinter - Dart Team From 156f6f778890c291fd33db7fb7f825a921a4d135 Mon Sep 17 00:00:00 2001 From: Shams Zakhour Date: Mon, 3 Apr 2017 13:39:32 -0700 Subject: [PATCH 066/607] Adding a "mockito + package:test" example. (dart-lang/mockito#59) * Adding a mockito + package:test example. * Removing errant space. * Incorporating Sam's feedback. * Fixing the import. --- pkgs/mockito/test/example/iss/README.md | 83 ++++++++++++++++++++ pkgs/mockito/test/example/iss/iss.dart | 84 +++++++++++++++++++++ pkgs/mockito/test/example/iss/iss_test.dart | 73 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 pkgs/mockito/test/example/iss/README.md create mode 100644 pkgs/mockito/test/example/iss/iss.dart create mode 100644 pkgs/mockito/test/example/iss/iss_test.dart diff --git a/pkgs/mockito/test/example/iss/README.md b/pkgs/mockito/test/example/iss/README.md new file mode 100644 index 000000000..daf39bd57 --- /dev/null +++ b/pkgs/mockito/test/example/iss/README.md @@ -0,0 +1,83 @@ +# International Space Station (ISS) library + +This library accesses the International Space Station's APIs +(using [package:http](https://pub.dartlang.org/packages/http)) +to fetch the space station's current location. + +The unit tests for this library use package:mockito to generate +preset values for the space station's location, +and package:test to create reproducible scenarios to verify the +expected outcome. + +The ISS library, `iss.dart`, consists of two classes: + +**IssLocator** +: Fetches the current GPS position directly under the space station. + +**IssSpotter** +: Performs calculations from the observer's location on earth. + +--- + +The unit test, `iss_dart.test`, mocks the IssLocator class: + +``` +@proxy +class MockIssLocator extends Mock implements IssLocator {} +``` +The tests check for two scenarios: + +**Spherical distance** +: Given two predefined points on earth, verify the calculated distance +between them. + +``` + group('Spherical distance', () { + test('London - Paris', () { + Point london = new Point(51.5073, -0.1277); + Point paris = new Point(48.8566, 2.3522); + double d = sphericalDistanceKm(london, paris); + expect(d, closeTo(343.5, 0.1)); + }); + + test('San Francisco - Mountain View', () { + Point sf = new Point(37.783333, -122.416667); + Point mtv = new Point(37.389444, -122.081944); + double d = sphericalDistanceKm(sf, mtv); + expect(d, closeTo(52.8, 0.1)); + }); + }); +``` + +**ISS Spotter** +: Stubs `IssLocator.currentPosition` using `when().thenReturn()`. +Evaluate whether the space station (using a predefined location) +is visible from a second predefined location. +This test runs asynchronously. + +``` + group('ISS spotter', () { + test('ISS visible', () async { + Point sf = new Point(37.783333, -122.416667); + Point mtv = new Point(37.389444, -122.081944); + IssLocator locator = new MockIssLocator(); + // Mountain View should be visible from San Francisco. + when(locator.currentPosition).thenReturn(sf); + + var spotter = new IssSpotter(locator, mtv); + expect(spotter.isVisible, true); + }); + + test('ISS not visible', () async { + Point london = new Point(51.5073, -0.1277); + Point mtv = new Point(37.389444, -122.081944); + IssLocator locator = new MockIssLocator(); + // London should not be visible from Mountain View. + when(locator.currentPosition).thenReturn(london); + + var spotter = new IssSpotter(locator, mtv); + expect(spotter.isVisible, false); + }); + }); +``` + diff --git a/pkgs/mockito/test/example/iss/iss.dart b/pkgs/mockito/test/example/iss/iss.dart new file mode 100644 index 000000000..71c860579 --- /dev/null +++ b/pkgs/mockito/test/example/iss/iss.dart @@ -0,0 +1,84 @@ +// Copyright 2017 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; + +import 'package:http/http.dart'; + +/// Provides the International Space Station's current GPS position. +class IssLocator { + final Client client; + + Point _position; + Future _ongoingRequest; + + IssLocator(this.client); + + Point get currentPosition => _position; + + /// Returns the current GPS position in [latitude, longitude] format. + Future update() async { + if (_ongoingRequest == null) { + _ongoingRequest = _doUpdate(); + } + await _ongoingRequest; + _ongoingRequest = null; + } + + Future _doUpdate() async { + // Returns the point on the earth directly under the space station + // at this moment. + Response rs = await client.get('http://api.open-notify.org/iss-now.json'); + Map data = JSON.decode(rs.body); + double latitude = double.parse(data['iss_position']['latitude']); + double longitude = double.parse(data['iss_position']['longitude']); + _position = new Point(latitude, longitude); + } +} + +// Performs calculations from the observer's location on earth. +class IssSpotter { + final IssLocator locator; + final Point observer; + final String label; + + IssSpotter(this.locator, this.observer, {this.label}); + + // The ISS is defined to be visible if the distance from the observer to + // the point on the earth directly under the space station is less than 80km. + bool get isVisible { + double distance = sphericalDistanceKm(locator.currentPosition, observer); + return distance < 80.0; + } +} + +// Returns the distance, in kilometers, between p1 and p2 along the earth's +// curved surface. +double sphericalDistanceKm(Point p1, Point p2) { + double dLat = _toRadian(p1.x - p2.x); + double sLat = pow(sin(dLat / 2), 2); + double dLng = _toRadian(p1.y - p2.y); + double sLng = pow(sin(dLng / 2), 2); + double cosALat = cos(_toRadian(p1.x)); + double cosBLat = cos(_toRadian(p2.x)); + double x = sLat + cosALat * cosBLat * sLng; + double d = 2 * atan2(sqrt(x), sqrt(1 - x)) * _radiusOfEarth; + return d; +} + +/// Radius of the earth in km. +const int _radiusOfEarth = 6371; +double _toRadian(num degree) => degree * PI / 180.0; diff --git a/pkgs/mockito/test/example/iss/iss_test.dart b/pkgs/mockito/test/example/iss/iss_test.dart new file mode 100644 index 000000000..c56679a8c --- /dev/null +++ b/pkgs/mockito/test/example/iss/iss_test.dart @@ -0,0 +1,73 @@ +// Copyright 2017 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:math'; + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +import 'iss.dart'; + +// The Mock class uses noSuchMethod to catch all method invocations. +// The @proxy annotation indicates that noSuchMethod calls should be +// handled gracefully. For more info, see the readme for package:mockito. +@proxy +class MockIssLocator extends Mock implements IssLocator {} + +void main() { + // Given two predefined points on earth, + // verify the calculated distance between them. + group('Spherical distance', () { + test('London - Paris', () { + Point london = new Point(51.5073, -0.1277); + Point paris = new Point(48.8566, 2.3522); + double d = sphericalDistanceKm(london, paris); + expect(d, closeTo(343.5, 0.1)); + }); + + test('San Francisco - Mountain View', () { + Point sf = new Point(37.783333, -122.416667); + Point mtv = new Point(37.389444, -122.081944); + double d = sphericalDistanceKm(sf, mtv); + expect(d, closeTo(52.8, 0.1)); + }); + }); + + // Stubs IssLocator.currentPosition() using when().thenReturn(). + // Calling currentPosition() then returns the predefined location + // for the space station. + // Evaluate whether the space station is visible from a + // second predefined location. This test runs asynchronously. + group('ISS spotter', () { + test('ISS visible', () async { + Point sf = new Point(37.783333, -122.416667); + Point mtv = new Point(37.389444, -122.081944); + IssLocator locator = new MockIssLocator(); + when(locator.currentPosition).thenReturn(sf); + + var spotter = new IssSpotter(locator, mtv); + expect(spotter.isVisible, true); + }); + + test('ISS not visible', () async { + Point london = new Point(51.5073, -0.1277); + Point mtv = new Point(37.389444, -122.081944); + IssLocator locator = new MockIssLocator(); + when(locator.currentPosition).thenReturn(london); + + var spotter = new IssSpotter(locator, mtv); + expect(spotter.isVisible, false); + }); + }); +} From 0d6db392ebfc9a6958ec8b43bbcddba23b8aab9e Mon Sep 17 00:00:00 2001 From: Shams Zakhour Date: Mon, 3 Apr 2017 13:51:44 -0700 Subject: [PATCH 067/607] Making sure comments are added to readme and source. (dart-lang/mockito#60) --- pkgs/mockito/test/example/iss/README.md | 3 +++ pkgs/mockito/test/example/iss/iss_test.dart | 2 ++ 2 files changed, 5 insertions(+) diff --git a/pkgs/mockito/test/example/iss/README.md b/pkgs/mockito/test/example/iss/README.md index daf39bd57..153efc34c 100644 --- a/pkgs/mockito/test/example/iss/README.md +++ b/pkgs/mockito/test/example/iss/README.md @@ -22,6 +22,9 @@ The ISS library, `iss.dart`, consists of two classes: The unit test, `iss_dart.test`, mocks the IssLocator class: ``` +// The Mock class uses noSuchMethod to catch all method invocations. +// The @proxy annotation indicates that noSuchMethod calls should be +// handled gracefully. For more info, see the readme for package:mockito. @proxy class MockIssLocator extends Mock implements IssLocator {} ``` diff --git a/pkgs/mockito/test/example/iss/iss_test.dart b/pkgs/mockito/test/example/iss/iss_test.dart index c56679a8c..c3baa64af 100644 --- a/pkgs/mockito/test/example/iss/iss_test.dart +++ b/pkgs/mockito/test/example/iss/iss_test.dart @@ -54,6 +54,7 @@ void main() { Point sf = new Point(37.783333, -122.416667); Point mtv = new Point(37.389444, -122.081944); IssLocator locator = new MockIssLocator(); + // Mountain View should be visible from San Francisco. when(locator.currentPosition).thenReturn(sf); var spotter = new IssSpotter(locator, mtv); @@ -64,6 +65,7 @@ void main() { Point london = new Point(51.5073, -0.1277); Point mtv = new Point(37.389444, -122.081944); IssLocator locator = new MockIssLocator(); + // London should not be visible from Mountain View. when(locator.currentPosition).thenReturn(london); var spotter = new IssSpotter(locator, mtv); From 6d0d58fbb0d35e9c1a2ee7fe47577791e3f3d419 Mon Sep 17 00:00:00 2001 From: Shams Zakhour Date: Tue, 4 Apr 2017 17:44:18 -0700 Subject: [PATCH 068/607] Adding direct links to the files from the README. (dart-lang/mockito#61) * Adding direct links to the files from the README. * Fixing the markdown. * Tweak * Fixing typo. --- pkgs/mockito/test/example/iss/README.md | 17 +++++++++++++++++ pkgs/mockito/test/example/iss/iss_test.dart | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/pkgs/mockito/test/example/iss/README.md b/pkgs/mockito/test/example/iss/README.md index 153efc34c..53a2daee2 100644 --- a/pkgs/mockito/test/example/iss/README.md +++ b/pkgs/mockito/test/example/iss/README.md @@ -19,6 +19,8 @@ The ISS library, `iss.dart`, consists of two classes: --- +## Testing + The unit test, `iss_dart.test`, mocks the IssLocator class: ``` @@ -40,6 +42,8 @@ between them. Point london = new Point(51.5073, -0.1277); Point paris = new Point(48.8566, 2.3522); double d = sphericalDistanceKm(london, paris); + // London should be approximately 343.5km + // (+/- 0.1km) from Paris. expect(d, closeTo(343.5, 0.1)); }); @@ -47,6 +51,8 @@ between them. Point sf = new Point(37.783333, -122.416667); Point mtv = new Point(37.389444, -122.081944); double d = sphericalDistanceKm(sf, mtv); + // San Francisco should be approximately 52.8km + // (+/- 0.1km) from Mountain View. expect(d, closeTo(52.8, 0.1)); }); }); @@ -84,3 +90,14 @@ This test runs asynchronously. }); ``` +--- + +## Files + +* [iss.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss.dart) +: International space station API library + +* [iss_test.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss_test.dart) +: Unit tests for iss.dart library + + diff --git a/pkgs/mockito/test/example/iss/iss_test.dart b/pkgs/mockito/test/example/iss/iss_test.dart index c3baa64af..f21658c91 100644 --- a/pkgs/mockito/test/example/iss/iss_test.dart +++ b/pkgs/mockito/test/example/iss/iss_test.dart @@ -33,6 +33,8 @@ void main() { Point london = new Point(51.5073, -0.1277); Point paris = new Point(48.8566, 2.3522); double d = sphericalDistanceKm(london, paris); + // London should be approximately 343.5km + // (+/- 0.1km) from Paris. expect(d, closeTo(343.5, 0.1)); }); @@ -40,6 +42,8 @@ void main() { Point sf = new Point(37.783333, -122.416667); Point mtv = new Point(37.389444, -122.081944); double d = sphericalDistanceKm(sf, mtv); + // San Francisco should be approximately 52.8km + // (+/- 0.1km) from Mountain View. expect(d, closeTo(52.8, 0.1)); }); }); From f3dc38e2b6ab66b3997daf952dabd4f412d13480 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 18 May 2017 14:27:17 -0700 Subject: [PATCH 069/607] Add docs for when, verify, resetMockitoState; expose throwOnMissingStub, resetMockitoState (dart-lang/mockito#65) --- pkgs/mockito/lib/mockito.dart | 2 + pkgs/mockito/lib/src/mock.dart | 79 ++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 4ece809f4..c151683fd 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -38,6 +38,8 @@ export 'src/mock.dart' Verification, // -- misc + throwOnMissingStub, clearInteractions, reset, + resetMockitoState, logInvocations; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 368f310d3..66f00945f 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -48,30 +48,31 @@ void throwOnMissingStub(Mock mock) { /// customized at runtime to define how it may behave using [when]. /// /// __Example use__: -/// // Real class. -/// class Cat { -/// String getSound() => 'Meow'; -/// } /// -/// // Mock class. -/// class MockCat extends Mock implements Cat {} +/// // Real class. +/// class Cat { +/// String getSound() => 'Meow'; +/// } /// -/// void main() { -/// // Create a new mocked Cat at runtime. -/// var cat = new MockCat(); +/// // Mock class. +/// class MockCat extends Mock implements Cat {} /// -/// // When 'getSound' is called, return 'Woof' -/// when(cat.getSound()).thenReturn('Woof'); +/// void main() { +/// // Create a new mocked Cat at runtime. +/// var cat = new MockCat(); /// -/// // Try making a Cat sound... -/// print(cat.getSound()); // Prints 'Woof' -/// } +/// // When 'getSound' is called, return 'Woof' +/// when(cat.getSound()).thenReturn('Woof'); +/// +/// // Try making a Cat sound... +/// print(cat.getSound()); // Prints 'Woof' +/// } /// /// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of /// runtime reflection, and causes sub-standard code to be generated. As such, /// [Mock] 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). +/// used within the context of Dart for Web (dart2js, DDC) and Dart for Mobile +/// (Flutter). class Mock { static _answerNull(_) => null; @@ -565,6 +566,25 @@ typedef void _InOrderVerification(List recordedInvocations); Verification get verifyNever => _makeVerify(true); +/// Verify that a method on a mock object was called with given arguments. +/// +/// Call a method on a mock object within the call to `verify`. For example: +/// +/// ```dart +/// verify(cat.eatFood("fish")); +/// ``` +/// +/// Mockito will fail the current test case if `cat.eatFood` has not been called +/// with `"fish"`. Optionally, call `called` on the result, to verify that the +/// method was called a certain number of times. For example: +/// +/// ```dart +/// verify(cat.eatFood("fish")).called(2); +/// verify(cat.eatFood("fish")).called(greaterThan(3)); +/// ``` +/// +/// See also: [verifyNever], [verifyInOrder], [verifyZeroInteractions], and +/// [verifyNoMoreInteractions]. Verification get verify => _makeVerify(false); Verification _makeVerify(bool never) { @@ -639,6 +659,23 @@ void verifyZeroInteractions(var mock) { typedef PostExpectation Expectation(x); +/// Create a stub method response. +/// +/// Call a method on a mock object within the call to `when`, and call a +/// canned response method on the result. For example: +/// +/// ```dart +/// when(cat.eatFood("fish")).thenReturn(true); +/// ``` +/// +/// Mockito will store the fake call to `cat.eatFood`, and pair the exact +/// arguments given with the response. When `cat.eatFood` is called outside a +/// `when` or `verify` context (a call "for real"), Mockito will respond with +/// the stored canned response, if it can match the mock method parameters. +/// +/// The response generators include [thenReturn], [thenAnswer], and [thenThrow]. +/// +/// See the README for more information. Expectation get when { if (_whenCall != null) { throw new StateError('Cannot call `when` within a stub response'); @@ -660,7 +697,15 @@ void logInvocations(List mocks) { }); } -/// Only for mockito testing. +/// Reset the state of Mockito, typically for use between tests. +/// +/// For example, when using the test package, mock methods may accumulate calls +/// in a `setUp` method, making it hard to verify method calls that were made +/// _during_ an individual test. Or, there may be unverified calls from previous +/// test cases that should not affect later test cases. +/// +/// In these cases, [resetMockitoState] might be called at the end of `setUp`, +/// or in `tearDown`. void resetMockitoState() { _whenInProgress = false; _verificationInProgress = false; From afdc71547a11b3f7a2912ea09a26e5fe898382df Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 17 Jul 2017 08:11:09 -0700 Subject: [PATCH 070/607] Switch from comment-based to real generics syntax (dart-lang/mockito#67) --- pkgs/mockito/lib/src/mock.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 66f00945f..487a71552 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -534,7 +534,7 @@ captureThat(Matcher matcher) => new ArgMatcher(matcher, true); /// A Strong-mode safe argument matcher that wraps other argument matchers. /// See the README for a full explanation. -/*=T*/ typed/**/(ArgMatcher matcher, {String named}) { +T typed(ArgMatcher matcher, {String named}) { if (named == null) { _typedArgs.add(matcher); } else { From cc0c7f5140fe6282fe2f3c7a11fa90c7c02308ed Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 31 Jul 2017 14:58:13 -0700 Subject: [PATCH 071/607] Address the verify(...).called(0) issue. Also: (dart-lang/mockito#69) * Document verifyNever * Improve the error message of verifications * Add some verification tests --- pkgs/mockito/lib/src/mock.dart | 34 +++-- pkgs/mockito/test/mockito_test.dart | 187 +++++++++++++++++++++++----- 2 files changed, 185 insertions(+), 36 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 487a71552..82d838355 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -491,12 +491,16 @@ class _VerifyCall { void _checkWith(bool never) { if (!never && matchingInvocations.isEmpty) { - var otherCallsText = ""; - if (mock._realCalls.isNotEmpty) { - otherCallsText = " All calls: "; + var message; + if (mock._realCalls.isEmpty) { + message = "No matching calls (actually, no calls at all)."; + } else { + var otherCalls = mock._realCalls.join(", "); + message = "No matching calls. All calls: $otherCalls"; } - var calls = mock._realCalls.join(", "); - fail("No matching calls.$otherCallsText$calls"); + fail("$message\n" + "(If you called `verify(...).called(0);`, please instead use " + "`verifyNever(...);`.)"); } if (never && matchingInvocations.isNotEmpty) { var calls = mock._realCalls.join(", "); @@ -564,13 +568,26 @@ typedef VerificationResult Verification(matchingInvocations); typedef void _InOrderVerification(List recordedInvocations); +/// Verify that a method on a mock object was never called with the given +/// arguments. +/// +/// Call a method on a mock object within a `verifyNever` call. For example: +/// +/// ```dart +/// cat.eatFood("chicken"); +/// verifyNever(cat.eatFood("fish")); +/// ``` +/// +/// Mockito will pass the current test case, as `cat.eatFood` has not been +/// called with `"chicken"`. Verification get verifyNever => _makeVerify(true); -/// Verify that a method on a mock object was called with given arguments. +/// Verify that a method on a mock object was called with the given arguments. /// /// Call a method on a mock object within the call to `verify`. For example: /// /// ```dart +/// cat.eatFood("chicken"); /// verify(cat.eatFood("fish")); /// ``` /// @@ -583,6 +600,9 @@ Verification get verifyNever => _makeVerify(true); /// verify(cat.eatFood("fish")).called(greaterThan(3)); /// ``` /// +/// Note: because of an unintended limitation, `verify(...).called(0);` will +/// not work as expected. Please use `verifyNever(...);` instead. +/// /// See also: [verifyNever], [verifyInOrder], [verifyZeroInteractions], and /// [verifyNoMoreInteractions]. Verification get verify => _makeVerify(false); @@ -601,7 +621,7 @@ Verification _makeVerify(bool never) { verifyCall._checkWith(never); return result; } else { - fail("Used on non-mockito"); + fail("Used on a non-mockito object"); } }; } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 427816caa..074632a0c 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -72,6 +72,9 @@ expectFail(String expectedMessage, expectedToFail()) { } } +String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " + "please instead use `verifyNever(...);`.)"; + void main() { RealClass mock; @@ -98,50 +101,59 @@ void main() { when(mock.methodWithoutArgs()).thenReturn("A"); expect(mock.methodWithoutArgs(), equals("A")); }); + test("should mock method with normal args", () { when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); expect(mock.methodWithNormalArgs(43), isNull); expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); }); + test("should mock method with mock args", () { var m1 = new MockedClass(); when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); expect(mock.methodWithObjArgs(new MockedClass()), isNull); expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); }); + test("should mock method with positional args", () { when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); expect(mock.methodWithPositionalArgs(42), isNull); expect(mock.methodWithPositionalArgs(42, 18), isNull); expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); }); + test("should mock method with named args", () { when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); expect(mock.methodWithNamedArgs(42), isNull); expect(mock.methodWithNamedArgs(42, y: 18), isNull); expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); }); + test("should mock method with List args", () { when(mock.methodWithListArgs([42])).thenReturn("Ultimate answer"); expect(mock.methodWithListArgs([43]), isNull); expect(mock.methodWithListArgs([42]), equals("Ultimate answer")); }); + test("should mock method with argument matcher", () { when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) .thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), isNull); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); + test("should mock method with any argument matcher", () { when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), equals("A lot!")); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); + test("should mock method with any list argument matcher", () { when(mock.methodWithListArgs(any)).thenReturn("A lot!"); expect(mock.methodWithListArgs([42]), equals("A lot!")); expect(mock.methodWithListArgs([43]), equals("A lot!")); }); + test("should mock method with multiple named args and matchers", () { when(mock.methodWithTwoNamedArgs(any, y: any)).thenReturn("x y"); when(mock.methodWithTwoNamedArgs(any, z: any)).thenReturn("x z"); @@ -153,6 +165,7 @@ void main() { .thenReturn("x y z"); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); }); + test("should mock method with mix of argument matchers and real things", () { when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) @@ -161,64 +174,78 @@ void main() { expect(mock.methodWithPositionalArgs(101, 18), isNull); expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); }); + test("should mock getter", () { when(mock.getter).thenReturn("A"); expect(mock.getter, equals("A")); }); + test("should mock hashCode", () { named(mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); + test("should have hashCode when it is not mocked", () { expect(mock.hashCode, isNotNull); }); + // test("should n't mock toString", (){ // when(mock.toString()).thenReturn("meow"); // expect(mock.toString(), equals("meow")); // }); + test("should have default toString when it is not mocked", () { expect(mock.toString(), equals("MockedClass")); }); + test("should have toString as name when it is not mocked", () { named(mock, name: "Cat"); expect(mock.toString(), equals("Cat")); }); + test("should mock equals between mocks when givenHashCode is equals", () { var anotherMock = named(new MockedClass(), hashCode: 42); named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); }); + test("should use identical equality between it is not mocked", () { var anotherMock = new MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); + //no need to mock setter, except if we will have spies later... test("should mock method with thrown result", () { when(mock.methodWithNormalArgs(any)).thenThrow(new StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(any)).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); expect(mock.methodWithNormalArgs(43), equals("43")); expect(mock.methodWithNormalArgs(42), equals("42")); }); + test("should return mock to make simple oneline mocks", () { RealClass mockWithSetup = when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); }); + test("should use latest matching when definition", () { when(mock.methodWithoutArgs()).thenReturn("A"); when(mock.methodWithoutArgs()).thenReturn("B"); expect(mock.methodWithoutArgs(), equals("B")); }); + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); + test("should throw if `when` is called while stubbing", () { expect(() { var responseHelper = () { @@ -237,11 +264,13 @@ void main() { expect(mock.typeParameterizedFn([42], [43]), equals("A lot!")); expect(mock.typeParameterizedFn([43], [44]), equals("A lot!")); }); + test("should mock method with an optional typed arg matcher", () { when(mock.typeParameterizedFn(typed(any), typed(any), typed(any))) .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); }); + test( "should mock method with an optional typed arg matcher and an optional real arg", () { @@ -250,6 +279,7 @@ void main() { expect( mock.typeParameterizedFn([42], [43], [44], [45]), equals("A lot!")); }); + test("should mock method with only some typed arg matchers", () { when(mock.typeParameterizedFn(typed(any), [43], typed(any))) .thenReturn("A lot!"); @@ -257,18 +287,21 @@ void main() { when(mock.typeParameterizedFn(typed(any), [43])).thenReturn("A bunch!"); expect(mock.typeParameterizedFn([42], [43]), equals("A bunch!")); }); + test("should throw when [typed] used alongside [null].", () { expect(() => when(mock.typeParameterizedFn(typed(any), null, typed(any))), throwsArgumentError); expect(() => when(mock.typeParameterizedFn(typed(any), typed(any), null)), throwsArgumentError); }); + test("should mock method when [typed] used alongside matched [null].", () { when(mock.typeParameterizedFn( typed(any), argThat(equals(null)), typed(any))) .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], null, [44]), equals("A lot!")); }); + test("should mock method with named, typed arg matcher", () { when(mock.typeParameterizedNamedFn(typed(any), [43], y: typed(any, named: "y"))) @@ -276,6 +309,7 @@ void main() { expect( mock.typeParameterizedNamedFn([42], [43], y: [44]), equals("A lot!")); }); + test("should mock method with named, typed arg matcher and an arg matcher", () { when(mock.typeParameterizedNamedFn(typed(any), [43], @@ -284,6 +318,7 @@ void main() { expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); + test("should mock method with named, typed arg matcher and a regular arg", () { when(mock.typeParameterizedNamedFn(typed(any), [43], @@ -291,12 +326,14 @@ void main() { expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); }); + test("should throw when [typed] used as a named arg, without `named:`", () { expect( () => when( mock.typeParameterizedNamedFn(typed(any), [43], y: typed(any))), throwsArgumentError); }); + test("should throw when [typed] used as a positional arg, with `named:`", () { expect( @@ -304,6 +341,7 @@ void main() { typed(any), typed(any, named: "y"))), throwsArgumentError); }); + test( "should throw when [typed] used as a named arg, with the wrong `named:`", () { @@ -319,77 +357,85 @@ void main() { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); }); + test("should verify method with normal args", () { mock.methodWithNormalArgs(42); expectFail( - "No matching calls. All calls: MockedClass.methodWithNormalArgs(42)", - () { + "No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithNormalArgs(43)); }); verify(mock.methodWithNormalArgs(42)); }); + test("should mock method with positional args", () { mock.methodWithPositionalArgs(42, 17); expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", - () { + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithPositionalArgs(42)); }); expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)", - () { + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithPositionalArgs(42, 18)); }); verify(mock.methodWithPositionalArgs(42, 17)); }); + test("should mock method with named args", () { mock.methodWithNamedArgs(42, y: 17); expectFail( - "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", - () { + "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithNamedArgs(42)); }); expectFail( - "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})", - () { + "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithNamedArgs(42, y: 18)); }); verify(mock.methodWithNamedArgs(42, y: 17)); }); + test("should mock method with mock args", () { var m1 = named(new MockedClass(), name: "m1"); mock.methodWithObjArgs(m1); expectFail( - "No matching calls. All calls: MockedClass.methodWithObjArgs(m1)", - () { + "No matching calls. All calls: MockedClass.methodWithObjArgs(m1)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithObjArgs(new MockedClass())); }); verify(mock.methodWithObjArgs(m1)); }); + test("should mock method with list args", () { mock.methodWithListArgs([42]); expectFail( - "No matching calls. All calls: MockedClass.methodWithListArgs([42])", - () { + "No matching calls. All calls: MockedClass.methodWithListArgs([42])\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithListArgs([43])); }); verify(mock.methodWithListArgs([42])); }); + test("should mock method with argument matcher", () { mock.methodWithNormalArgs(100); expectFail( - "No matching calls. All calls: MockedClass.methodWithNormalArgs(100)", - () { + "No matching calls. All calls: MockedClass.methodWithNormalArgs(100)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); }); verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100)))); }); + test("should mock method with argument capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([50, 100])); }); + test("should mock method with argument matcher and capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); @@ -404,38 +450,45 @@ void main() { .single, equals(50)); }); + test("should mock method with mix of argument matchers and real things", () { mock.methodWithPositionalArgs(100, 17); expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", - () { + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithPositionalArgs( argThat(greaterThanOrEqualTo(100)), 18)); }); expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)", - () { + "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); }); verify(mock.methodWithPositionalArgs( argThat(greaterThanOrEqualTo(100)), 17)); }); + test("should mock getter", () { mock.getter; verify(mock.getter); }); + test("should mock setter", () { mock.setter = "A"; - expectFail("No matching calls. All calls: MockedClass.setter==A", () { + expectFail( + "No matching calls. All calls: MockedClass.setter==A\n" + "$noMatchingCallsFooter", () { verify(mock.setter = "B"); }); verify(mock.setter = "A"); }); + test("should verify method with typed arg matchers", () { mock.typeParameterizedFn([42], [43]); verify(mock.typeParameterizedFn(typed(any), typed(any))); }); + test("should verify method with argument capturer", () { mock.typeParameterizedFn([50], [17]); mock.typeParameterizedFn([100], [17]); @@ -447,34 +500,44 @@ void main() { ])); }); }); + group("verify() qualifies", () { group("unqualified as at least one", () { test("zero fails", () { - expectFail("No matching calls.", () { + expectFail( + "No matching calls (actually, no calls at all).\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithoutArgs()); }); }); + test("one passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); }); + test("more than one passes", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); }); }); - group("counts calls", () { - test("zero fails", () { - expectFail("No matching calls.", () { + + group("expecting one call", () { + test("zero actual calls fails", () { + expectFail( + "No matching calls (actually, no calls at all).\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithoutArgs()).called(1); }); }); - test("one passes", () { + + test("one actual call passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()).called(1); }); - test("more than one fails", () { + + test("more than one actual call fails", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", @@ -483,10 +546,60 @@ void main() { }); }); }); + + group("expecting more than two calls", () { + test("zero actual calls fails", () { + expectFail( + "No matching calls (actually, no calls at all).\n" + "$noMatchingCallsFooter", () { + verify(mock.methodWithoutArgs()).called(greaterThan(2)); + }); + }); + + test("one actual call fails", () { + mock.methodWithoutArgs(); + expectFail( + "Expected: a value greater than <2>\n" + " Actual: <1>\n" + " Which: is not a value greater than <2>\n" + "Unexpected number of calls\n", () { + verify(mock.methodWithoutArgs()).called(greaterThan(2)); + }); + }); + + test("three actual calls passes", () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()).called(greaterThan(2)); + }); + }); + + group("expecting zero calls", () { + test("zero actual calls passes", () { + expectFail( + "No matching calls (actually, no calls at all).\n" + "$noMatchingCallsFooter", () { + verify(mock.methodWithoutArgs()).called(0); + }); + }); + + test("one actual call fails", () { + mock.methodWithoutArgs(); + expectFail( + "Expected: <0>\n" + " Actual: <1>\n" + "Unexpected number of calls\n", () { + verify(mock.methodWithoutArgs()).called(0); + }); + }); + }); + group("verifyNever", () { test("zero passes", () { verifyNever(mock.methodWithoutArgs()); }); + test("one fails", () { mock.methodWithoutArgs(); expectFail( @@ -495,16 +608,18 @@ void main() { }); }); }); + group("doesn't count already verified again", () { test("fail case", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); expectFail( - "No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()", - () { + "No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()\n" + "$noMatchingCallsFooter", () { verify(mock.methodWithoutArgs()); }); }); + test("pass case", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); @@ -518,6 +633,7 @@ void main() { test("never touched pass", () { verifyZeroInteractions(mock); }); + test("any touch fails", () { mock.methodWithoutArgs(); expectFail( @@ -526,6 +642,7 @@ void main() { verifyZeroInteractions(mock); }); }); + test("verifired call fails", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); @@ -536,10 +653,12 @@ void main() { }); }); }); + group("verifyNoMoreInteractions()", () { test("never touched pass", () { verifyNoMoreInteractions(mock); }); + test("any unverified touch fails", () { mock.methodWithoutArgs(); expectFail( @@ -548,18 +667,21 @@ void main() { verifyNoMoreInteractions(mock); }); }); + test("verified touch passes", () { mock.methodWithoutArgs(); verify(mock.methodWithoutArgs()); verifyNoMoreInteractions(mock); }); }); + group("verifyInOrder()", () { test("right order passes", () { mock.methodWithoutArgs(); mock.getter; verifyInOrder([mock.methodWithoutArgs(), mock.getter]); }); + test("wrong order fails", () { mock.methodWithoutArgs(); mock.getter; @@ -569,6 +691,7 @@ void main() { verifyInOrder([mock.getter, mock.methodWithoutArgs()]); }); }); + test("uncomplete fails", () { mock.methodWithoutArgs(); expectFail( @@ -577,6 +700,7 @@ void main() { verifyInOrder([mock.methodWithoutArgs(), mock.getter]); }); }); + test("methods can be called again and again", () { mock.methodWithoutArgs(); mock.getter; @@ -584,6 +708,7 @@ void main() { verifyInOrder( [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); }); + test("methods can be called again and again - fail case", () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); @@ -603,11 +728,13 @@ void main() { expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, equals(42)); }); + test("should captureOut list arguments", () { mock.methodWithListArgs([42]); expect(verify(mock.methodWithListArgs(captureAny)).captured.single, equals([42])); }); + test("should captureOut multiple arguments", () { mock.methodWithPositionalArgs(1, 2); expect( @@ -615,6 +742,7 @@ void main() { .captured, equals([1, 2])); }); + test("should captureOut with matching arguments", () { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); @@ -623,6 +751,7 @@ void main() { .captured, equals([2, 3])); }); + test("should captureOut multiple invocations", () { mock.methodWithNormalArgs(1); mock.methodWithNormalArgs(2); From 45938c4793c8a33e40f754ba27a81e6ea183a01e Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 31 Jul 2017 15:11:00 -0700 Subject: [PATCH 072/607] Bump to 2.1.0; CHANGELOG; SDK ceiling bump (dart-lang/mockito#70) * Bump to 2.1.0; CHANGELOG; SDK ceiling bump --- pkgs/mockito/CHANGELOG.md | 13 +++++++++++-- .../{.analysis_options => analysis_options.yaml} | 0 pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) rename pkgs/mockito/{.analysis_options => analysis_options.yaml} (100%) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 972d940df..eb1384e34 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,9 +1,18 @@ +## 2.1.0 + +* Add documentation for `when`, `verify`, `verifyNever`, `resetMockitoState`. +* Expose `throwOnMissingStub`, `resetMockitoState`. +* Improve failure message for `verify`. +* SDK version ceiling bumped to `<2.0.0-dev.infinity` to support Dart 2.0 + development testing. +* Add a Mockito + test package example at `test/example/iss`. + ## 2.0.2 * Start using the new `InvocationMatcher` instead of the old matcher. * Change `throwOnMissingStub` back to invoking `Object.noSuchMethod`: - * It was never documented what the thrown type should be expected as - * You can now just rely on `throwsNoSuchMethodError` if you want to catch it + * It was never documented what the thrown type should be expected as. + * You can now just rely on `throwsNoSuchMethodError` if you want to catch it. ## 2.0.1 diff --git a/pkgs/mockito/.analysis_options b/pkgs/mockito/analysis_options.yaml similarity index 100% rename from pkgs/mockito/.analysis_options rename to pkgs/mockito/analysis_options.yaml diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 4737bdee9..793d1d169 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,12 +1,12 @@ name: mockito -version: 2.0.2 +version: 2.1.0 authors: - Dmitriy Fibulwinter - Dart Team description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=1.21.0 <2.0.0' + sdk: '>=1.21.0 <2.0.0-dev.infinity' dependencies: matcher: '^0.12.0' meta: '^1.0.4' From 8ee9abdb470a697546e50b8e1690b804e79d972e Mon Sep 17 00:00:00 2001 From: jdicker Date: Thu, 10 Aug 2017 11:44:44 -0700 Subject: [PATCH 073/607] Add ability to wait for an invocation --- pkgs/mockito/README.md | 10 ++ pkgs/mockito/lib/mockito.dart | 3 +- pkgs/mockito/lib/src/mock.dart | 58 +++++++ pkgs/mockito/test/mockito_test.dart | 233 ++++++++++++++++++++++++++++ 4 files changed, 303 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index c56a4df80..8c8ce9742 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -150,6 +150,16 @@ cat.eatFood("Fish"); expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); ``` +## Waiting for an interaction +```dart +//waiting for a call +cat.eatFood("Fish"); +await untilCalled(cat.chew()); //completes when cat.chew() is called +//waiting for a call that has already happened +cat.eatFood("Fish"); +await untilCalled(cat.eatFood(any)); //will complete immediately +``` + ## Resetting mocks ```dart //clearing collected interactions diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index c151683fd..3aaadc239 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -42,4 +42,5 @@ export 'src/mock.dart' clearInteractions, reset, resetMockitoState, - logInvocations; + logInvocations, + untilCalled; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 82d838355..fe8895eb8 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -15,14 +15,18 @@ // Warning: Do not import dart:mirrors in this library, as it's exported via // lib/mockito.dart, which is used for Dart AOT projects such as Flutter. +import 'dart:async'; + import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; import 'package:test/test.dart'; bool _whenInProgress = false; +bool _untilCalledInProgress = false; bool _verificationInProgress = false; _WhenCall _whenCall; +_UntilCall _untilCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; final _TimeStampProvider _timer = new _TimeStampProvider(); final List _capturedArgs = []; @@ -78,6 +82,8 @@ class Mock { static const _nullResponse = const CallPair.allInvocations(_answerNull); + final StreamController _invocationStreamController = + new StreamController.broadcast(); final _realCalls = []; final _responses = []; @@ -103,8 +109,12 @@ class Mock { } else if (_verificationInProgress) { _verifyCalls.add(new _VerifyCall(this, invocation)); return null; + } else if (_untilCalledInProgress) { + _untilCall = new _UntilCall(this, invocation); + return null; } else { _realCalls.add(new RealCall(this, invocation)); + _invocationStreamController.add(invocation); var cannedResponse = _responses.lastWhere( (cr) => cr.call.matches(invocation, {}), orElse: _defaultResponse); @@ -470,6 +480,29 @@ class _WhenCall { } } +class _UntilCall { + final InvocationMatcher _invocationMatcher; + final Mock _mock; + + _UntilCall(this._mock, Invocation invocation) + : _invocationMatcher = new InvocationMatcher(invocation); + + bool _matchesInvocation(RealCall realCall) => + _invocationMatcher.matches(realCall.invocation); + + List get _realCalls => _mock._realCalls; + + Future get invocationFuture { + if (_realCalls.any(_matchesInvocation)) { + return new Future.value( + _realCalls.firstWhere(_matchesInvocation).invocation); + } + + return _mock._invocationStreamController.stream + .firstWhere(_invocationMatcher.matches); + } +} + class _VerifyCall { final Mock mock; final Invocation verifyInvocation; @@ -707,6 +740,29 @@ Expectation get when { }; } +typedef Future InvocationLoader(_); + +/// Returns a future [Invocation] that will complete upon the first occurrence +/// of the given invocation. +/// +/// Usage of this is as follows: +/// +/// ```dart +/// cat.eatFood("fish"); +/// await untilCalled(cat.chew()); +/// ``` +/// +/// In the above example, the untilCalled(cat.chew()) will complete only when +/// that method is called. If the given invocation has already been called, the +/// future will return immediately. +InvocationLoader get untilCalled { + _untilCalledInProgress = true; + return (_) { + _untilCalledInProgress = false; + return _untilCall.invocationFuture; + }; +} + /// Print all collected invocations of any mock methods of [mocks]. void logInvocations(List mocks) { List allInvocations = @@ -728,8 +784,10 @@ void logInvocations(List mocks) { /// or in `tearDown`. void resetMockitoState() { _whenInProgress = false; + _untilCalledInProgress = false; _verificationInProgress = false; _whenCall = null; + _untilCall = null; _verifyCalls.clear(); _capturedArgs.clear(); _typedArgs.clear(); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 074632a0c..eb586f476 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:async'; + import 'package:mockito/mockito.dart'; import 'package:mockito/src/mock.dart' show resetMockitoState, throwOnMissingStub; @@ -42,6 +44,33 @@ class RealClass { } } +class CallMethodsEvent {} + +/// Listens on a stream and upon any event calls all methods in [RealClass]. +class RealClassController { + final RealClass _realClass; + + RealClassController( + this._realClass, StreamController streamController) { + streamController.stream.listen(_callAllMethods); + } + + Future _callAllMethods(_) async { + _realClass + ..methodWithoutArgs() + ..methodWithNormalArgs(1) + ..methodWithListArgs([1, 2]) + ..methodWithPositionalArgs(1, 2) + ..methodWithNamedArgs(1, y: 2) + ..methodWithTwoNamedArgs(1, y: 2, z: 3) + ..methodWithObjArgs(new RealClass()) + ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) + ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) + ..getter + ..setter = "A"; + } +} + abstract class Foo { String bar(); } @@ -352,6 +381,210 @@ void main() { }); }); + group("untilCalled", () { + StreamController streamController = + new StreamController.broadcast(); + + group("on methods already called", () { + test("waits for method without args", () async { + mock.methodWithoutArgs(); + + await untilCalled(mock.methodWithoutArgs()); + + verify(mock.methodWithoutArgs()).called(1); + }); + + test("waits for method with normal args", () async { + mock.methodWithNormalArgs(1); + + await untilCalled(mock.methodWithNormalArgs(any)); + + verify(mock.methodWithNormalArgs(any)).called(1); + }); + + test("waits for method with list args", () async { + mock.methodWithListArgs([1]); + + await untilCalled(mock.methodWithListArgs(any)); + + verify(mock.methodWithListArgs(any)).called(1); + }); + + test("waits for method with positional args", () async { + mock.methodWithPositionalArgs(1, 2); + + await untilCalled(mock.methodWithPositionalArgs(any, any)); + + verify(mock.methodWithPositionalArgs(any, any)).called(1); + }); + + test("waits for method with named args", () async { + mock.methodWithNamedArgs(1, y: 2); + + await untilCalled(mock.methodWithNamedArgs(any, y: any)); + + verify(mock.methodWithNamedArgs(any, y: any)).called(1); + }); + + test("waits for method with two named args", () async { + mock.methodWithTwoNamedArgs(1, y: 2, z: 3); + + await untilCalled(mock.methodWithTwoNamedArgs(any, y: any, z: any)); + + verify(mock.methodWithTwoNamedArgs(any, y: any, z: any)).called(1); + }); + + test("waits for method with obj args", () async { + mock.methodWithObjArgs(new RealClass()); + + await untilCalled(mock.methodWithObjArgs(any)); + + verify(mock.methodWithObjArgs(any)).called(1); + }); + + test("waits for function with positional parameters", () async { + mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]); + + await untilCalled(mock.typeParameterizedFn(any, any, any, any)); + + verify(mock.typeParameterizedFn(any, any, any, any)).called(1); + }); + + test("waits for function with named parameters", () async { + mock.typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]); + + await untilCalled( + mock.typeParameterizedNamedFn(any, any, y: any, z: any)); + + verify(mock.typeParameterizedNamedFn(any, any, y: any, z: any)) + .called(1); + }); + + test("waits for getter", () async { + mock.getter; + + await untilCalled(mock.getter); + + verify(mock.getter).called(1); + }); + + test("waits for setter", () async { + mock.setter = "A"; + + await untilCalled(mock.setter = "A"); + + verify(mock.setter = "A").called(1); + }); + }); + + group("on methods not yet called", () { + setUp(() { + new RealClassController(mock, streamController); + }); + + test("waits for method without args", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithoutArgs()); + + await untilCalled(mock.methodWithoutArgs()); + + verify(mock.methodWithoutArgs()).called(1); + }); + + test("waits for method with normal args", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithNormalArgs(any)); + + await untilCalled(mock.methodWithNormalArgs(any)); + + verify(mock.methodWithNormalArgs(any)).called(1); + }); + + test("waits for method with list args", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithListArgs(any)); + + await untilCalled(mock.methodWithListArgs(any)); + + verify(mock.methodWithListArgs(any)).called(1); + }); + + test("waits for method with positional args", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithPositionalArgs(any, any)); + + await untilCalled(mock.methodWithPositionalArgs(any, any)); + + verify(mock.methodWithPositionalArgs(any, any)).called(1); + }); + + test("waits for method with named args", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithNamedArgs(any, y: any)); + + await untilCalled(mock.methodWithNamedArgs(any, y: any)); + + verify(mock.methodWithNamedArgs(any, y: any)).called(1); + }); + + test("waits for method with two named args", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithTwoNamedArgs(any, y: any, z: any)); + + await untilCalled(mock.methodWithTwoNamedArgs(any, y: any, z: any)); + + verify(mock.methodWithTwoNamedArgs(any, y: any, z: any)).called(1); + }); + + test("waits for method with obj args", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithObjArgs(any)); + + await untilCalled(mock.methodWithObjArgs(any)); + + verify(mock.methodWithObjArgs(any)).called(1); + }); + + test("waits for function with positional parameters", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.typeParameterizedFn(any, any, any, any)); + + await untilCalled(mock.typeParameterizedFn(any, any, any, any)); + + verify(mock.typeParameterizedFn(any, any, any, any)).called(1); + }); + + test("waits for function with named parameters", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.typeParameterizedNamedFn(any, any, y: any, z: any)); + + await untilCalled( + mock.typeParameterizedNamedFn(any, any, y: any, z: any)); + + verify(mock.typeParameterizedNamedFn(any, any, y: any, z: any)) + .called(1); + }); + + test("waits for getter", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.getter); + + await untilCalled(mock.getter); + + verify(mock.getter).called(1); + }); + + test("waits for setter", () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.setter = "A"); + + await untilCalled(mock.setter = "A"); + + verify(mock.setter = "A").called(1); + }); + }); + }); + group("verify()", () { test("should verify method without args", () { mock.methodWithoutArgs(); From 31fe6d7a2e177d93a18bb15d5ad1f8880cbafa3a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 15 Aug 2017 15:53:13 -0700 Subject: [PATCH 074/607] Bump to 2.2.0; add collection dep (dart-lang/mockito#73) --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/pubspec.yaml | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index eb1384e34..4f4bc0aae 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.2.0 + +* Add new feature to wait for an interaction: `untilCalled`. See the README for + documentation. + ## 2.1.0 * Add documentation for `when`, `verify`, `verifyNever`, `resetMockitoState`. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 793d1d169..279241e5f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 2.1.0 +version: 2.2.0 authors: - Dmitriy Fibulwinter - Dart Team @@ -8,6 +8,7 @@ homepage: https://github.com/dart-lang/mockito environment: sdk: '>=1.21.0 <2.0.0-dev.infinity' dependencies: + collection: '^1.1.0' matcher: '^0.12.0' meta: '^1.0.4' test: '>=0.12.0 <0.13.0' From 0f5c4f916d28381bd3f04db9cafb219fc3b0b334 Mon Sep 17 00:00:00 2001 From: retlat Date: Thu, 31 Aug 2017 22:37:10 +0900 Subject: [PATCH 075/607] Update the link of Mockito for Java in the README. (dart-lang/mockito#75) --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 8c8ce9742..288f6ea56 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,4 +1,4 @@ -Mock library for Dart inspired by [Mockito](https://code.google.com/p/mockito/). +Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). [![Pub](https://img.shields.io/pub/v/mockito.svg)]() [![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) From f6801e0a7ca031331a6cea5947aeb800ffd1fb51 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 18 Sep 2017 15:43:11 -0700 Subject: [PATCH 076/607] Modify example to show how to use argument for when. (dart-lang/mockito#76) Each time it takes me long time to find actual problem and solution, when I run into this problem. --- pkgs/mockito/lib/src/mock.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index fe8895eb8..f31360afb 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -55,7 +55,7 @@ void throwOnMissingStub(Mock mock) { /// /// // Real class. /// class Cat { -/// String getSound() => 'Meow'; +/// String getSound(String suffix) => 'Meow$suffix'; /// } /// /// // Mock class. @@ -66,10 +66,10 @@ void throwOnMissingStub(Mock mock) { /// var cat = new MockCat(); /// /// // When 'getSound' is called, return 'Woof' -/// when(cat.getSound()).thenReturn('Woof'); +/// when(cat.getSound(typed(any))).thenReturn('Woof'); /// /// // Try making a Cat sound... -/// print(cat.getSound()); // Prints 'Woof' +/// print(cat.getSound('foo')); // Prints 'Woof' /// } /// /// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of From 8102a2cce6892137a4f4c41920c9cdd7e2528b7f Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 21 Sep 2017 10:11:11 -0700 Subject: [PATCH 077/607] Fix SDK constraint (dart-lang/mockito#77) --- pkgs/mockito/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 279241e5f..362758ecc 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,12 +1,12 @@ name: mockito -version: 2.2.0 +version: 2.2.1-dev authors: - Dmitriy Fibulwinter - Dart Team description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=1.21.0 <2.0.0-dev.infinity' + sdk: '>=1.21.0 <2.0.0' dependencies: collection: '^1.1.0' matcher: '^0.12.0' From 1001c9c3d0c09a1eb8369305345e8ba6dd781463 Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Tue, 24 Oct 2017 21:46:19 +0200 Subject: [PATCH 078/607] cleanup (dart-lang/mockito#81) --- pkgs/mockito/lib/src/invocation_matcher.dart | 2 +- pkgs/mockito/lib/src/mock.dart | 2 +- pkgs/mockito/test/spy_test.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index eebd2978a..e09d9d70a 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -117,7 +117,7 @@ class _InvocationMatcher implements Matcher { // Returns named arguments as an iterable of ': '. static Iterable _namedArgsAndValues(Invocation invocation) => - invocation.namedArguments.keys.map/**/((name) => + invocation.namedArguments.keys.map((name) => '${_symbolToString(name)}: ${invocation.namedArguments[name]}'); // This will give is a mangled symbol in dart2js/aot with minification diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index f31360afb..4a6bfb847 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -726,7 +726,7 @@ typedef PostExpectation Expectation(x); /// `when` or `verify` context (a call "for real"), Mockito will respond with /// the stored canned response, if it can match the mock method parameters. /// -/// The response generators include [thenReturn], [thenAnswer], and [thenThrow]. +/// The response generators include `thenReturn`, `thenAnswer`, and `thenThrow`. /// /// See the README for more information. Expectation get when { diff --git a/pkgs/mockito/test/spy_test.dart b/pkgs/mockito/test/spy_test.dart index 83a7229ca..e45c1e2e7 100644 --- a/pkgs/mockito/test/spy_test.dart +++ b/pkgs/mockito/test/spy_test.dart @@ -33,7 +33,7 @@ void main() { group("spy", () { setUp(() { - mock = spy/**/(new MockedClass(), new RealClass()); + mock = spy(new MockedClass(), new RealClass()); }); test("should delegate to real object by default", () { From d95923c5408243624fddbf063619a62a4e81d22d Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 16 Nov 2017 13:21:12 -0800 Subject: [PATCH 079/607] Clean up README: (dart-lang/mockito#84) * Blank lines after headings * Blank lines around code blocks * Blank lines before comments in code * Space between `//` and text in a comment * More capitalization in comments * Wrap at 80 chars --- pkgs/mockito/README.md | 162 +++++++++++++++++++++++++++-------------- 1 file changed, 107 insertions(+), 55 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 288f6ea56..9d94fc87d 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -3,113 +3,146 @@ Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). [![Pub](https://img.shields.io/pub/v/mockito.svg)]() [![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) -Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: - * Poor refactoring support: rename method and you need manually search/replace it's usage in when/verify clauses. - * Poor support from IDE: no code-completion, no hints on argument types, can't jump to definition +Current mock libraries suffer from specifying method names as strings, which +cause a lot of problems: -Dart-mockito fixes it - stubbing and verifying are first-class citizens. +* Poor refactoring support: rename method and you need manually search/replace + it's usage in when/verify clauses. +* Poor support from IDE: no code-completion, no hints on argument types, can't + jump to definition + +Dart's mockito package fixes these issues - stubbing and verifying are +first-class citizens. ## Let's create mocks + ```dart import 'package:mockito/mockito.dart'; -//Real class +// Real class class Cat { String sound() => "Meow"; bool eatFood(String food, {bool hungry}) => true; int walk(List places); - void sleep(){} + void sleep() {} + void hunt(String place, String prey) {} int lives = 9; } -//Mock class +// Mock class class MockCat extends Mock implements Cat {} -//mock creation +// mock creation var cat = new MockCat(); ``` ## Let's verify some behaviour! + ```dart //using mock object cat.sound(); //verify interaction verify(cat.sound()); ``` -Once created, mock will remember all interactions. Then you can selectively verify whatever interaction you are -interested in. + +Once created, mock will remember all interactions. Then you can selectively +verify whatever interaction you are interested in. ## How about some stubbing? + ```dart -//unstubbed methods return null +// Unstubbed methods return null: expect(cat.sound(), nullValue); -//stubbing - before execution + +// Stubbing - before execution: when(cat.sound()).thenReturn("Purr"); expect(cat.sound(), "Purr"); -//you can call it again + +// You can call it again: expect(cat.sound(), "Purr"); -//let's change stub + +// Let's change the stub: when(cat.sound()).thenReturn("Meow"); expect(cat.sound(), "Meow"); -//you can stub getters + +// You can stub getters: when(cat.lives).thenReturn(9); expect(cat.lives, 9); -//you can stub a method to throw + +// You can stub a method to throw: when(cat.lives).thenThrow(new RangeError('Boo')); expect(() => cat.lives, throwsRangeError); -//we can calculate a response at call time: + +// We can calculate a response at call time: var responses = ["Purr", "Meow"]; when(cat.sound()).thenAnswer(() => responses.removeAt(0)); expect(cat.sound(), "Purr"); expect(cat.sound(), "Meow"); ``` -By default, for all methods that return value, mock returns null. -Stubbing can be overridden: for example common stubbing can go to fixture setup but the test methods can override it. -Please note that overridding stubbing is a potential code smell that points out too much stubbing. -Once stubbed, the method will always return stubbed value regardless of how many times it is called. -Last stubbing is more important - when you stubbed the same method with the same arguments many times. Other words: the -order of stubbing matters but it is only meaningful rarely, e.g. when stubbing exactly the same method calls or -sometimes when argument matchers are used, etc. +By default, for all methods that return a value, `mock` returns `null`. +Stubbing can be overridden: for example common stubbing can go to fixture setup +but the test methods can override it. Please note that overridding stubbing is +a potential code smell that points out too much stubbing. Once stubbed, the +method will always return stubbed value regardless of how many times it is +called. Last stubbing is more important, when you stubbed the same method with +the same arguments many times. In other words: the order of stubbing matters, +but it is meaningful rarely, e.g. when stubbing exactly the same method calls +or sometimes when argument matchers are used, etc. ## Argument matchers + ```dart -//you can use arguments itself... +// You can use arguments itself: when(cat.eatFood("fish")).thenReturn(true); -//..or collections + +// ... or collections: when(cat.walk(["roof","tree"])).thenReturn(2); -//..or matchers + +// ... or matchers: when(cat.eatFood(argThat(startsWith("dry"))).thenReturn(false); -//..or mix aguments with matchers + +// ... or mix aguments with matchers: when(cat.eatFood(argThat(startsWith("dry")), true).thenReturn(true); expect(cat.eatFood("fish"), isTrue); expect(cat.walk(["roof","tree"]), equals(2)); expect(cat.eatFood("dry food"), isFalse); expect(cat.eatFood("dry food", hungry: true), isTrue); -//you can also verify using an argument matcher + +// You can also verify using an argument matcher: verify(cat.eatFood("fish")); verify(cat.walk(["roof","tree"])); verify(cat.eatFood(argThat(contains("food")))); -//you can verify setters + +// You can verify setters: cat.lives = 9; verify(cat.lives=9); ``` -By default equals matcher is used to argument matching (since 0.11.0). It simplifies matching for collections as -arguments. If you need more strict matching consider use `argThat(identical(arg))`. -Argument matchers allow flexible verification or stubbing + +If an argument other than an ArgMatcher (like `any`, `anyNamed()`, `argThat`, +`captureArg`, etc.) is passed to a mock method, then the `equals` matcher is +used for argument matching. If you need more strict matching consider use +`argThat(identical(arg))`. + ## Verifying exact number of invocations / at least x / never + ```dart cat.sound(); cat.sound(); -//exact number of invocations + +// Exact number of invocations: verify(cat.sound()).called(2); -//or using matcher + +// Or using matcher: verify(cat.sound()).called(greaterThan(1)); -//or never called + +// Or never called: verifyNever(cat.eatFood(any)); ``` + ## Verification in order + ```dart cat.eatFood("Milk"); cat.sound(); @@ -120,15 +153,18 @@ verifyInOrder([ cat.eatFood("Fish") ]); ``` -Verification in order is flexible - you don't have to verify all interactions one-by-one but only those that you are -interested in testing in order. + +Verification in order is flexible - you don't have to verify all interactions +one-by-one but only those that you are interested in testing in order. ## Making sure interaction(s) never happened on mock + ```dart verifyZeroInteractions(cat); ``` ## Finding redundant invocations + ```dart cat.sound(); verify(cat.sound()); @@ -136,38 +172,45 @@ verifyNoMoreInteractions(cat); ``` ## Capturing arguments for further assertions + ```dart -//simple capture +// Simple capture: cat.eatFood("Fish"); expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); -//capture multiple calls + +// Capture multiple calls: cat.eatFood("Milk"); cat.eatFood("Fish"); expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); -//conditional capture + +// Conditional capture: cat.eatFood("Milk"); cat.eatFood("Fish"); expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); ``` ## Waiting for an interaction + ```dart -//waiting for a call +// Waiting for a call: cat.eatFood("Fish"); await untilCalled(cat.chew()); //completes when cat.chew() is called -//waiting for a call that has already happened + +// Waiting for a call that has already happened: cat.eatFood("Fish"); await untilCalled(cat.eatFood(any)); //will complete immediately ``` ## Resetting mocks + ```dart -//clearing collected interactions +// Clearing collected interactions: cat.eatFood("Fish"); clearInteractions(cat); cat.eatFood("Fish"); verify(cat.eatFood("Fish")).called(1); -//resetting stubs and collected interactions + +// Resetting stubs and collected interactions: when(cat.eatFood("Fish")).thenReturn(true); cat.eatFood("Fish"); reset(cat); @@ -176,22 +219,28 @@ expect(cat.eatFood("Fish"), false); ``` ## Spy + ```dart -//spy creation +// Spy creation: var cat = spy(new MockCat(), new Cat()); -//stubbing - before execution + +// Stubbing - before execution: when(cat.sound()).thenReturn("Purr"); -//using mocked interaction -expect(cat.sound(), "Purr"); -//using real object -expect(cat.lives, 9); + +// Using mocked interaction: +expect(cat.sound(), "Purr"); + +// Using a real object: +expect(cat.lives, 9); ``` ## Debugging + ```dart -//print all collected invocations of any mock methods of a list of mock objects +// Print all collected invocations of any mock methods of a list of mock objects: logInvocations([catOne, catTwo]); -//throw every time that a mock method is called without a stub being matched + +// Throw every time that a mock method is called without a stub being matched: throwOnMissingStub(cat); ``` @@ -277,6 +326,7 @@ when(cat.eatFood( [Strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md ## How it works + The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to catch all method invocations, and returns the value that you have configured beforehand with `when()` calls. @@ -284,9 +334,10 @@ all method invocations, and returns the value that you have configured beforehan The implementation of `when()` is a bit more tricky. Take this example: ```dart -//unstubbed methods return null +// Unstubbed methods return null: expect(cat.sound(), nullValue); -//stubbing - before execution + +// Stubbing - before execution: when(cat.sound()).thenReturn("Purr"); ``` @@ -306,6 +357,7 @@ The same goes for "chaining" mock objects in a test call. This will fail: ```dart var mockUtils = new MockUtils(); var mockStringUtils = new MockStringUtils(); + // Setting up mockUtils.stringUtils to return a mock StringUtils implementation when(mockUtils.stringUtils).thenReturn(mockStringUtils); From 2d623a600719db19598a9f9c21cf080b354c88f4 Mon Sep 17 00:00:00 2001 From: Ted Sander Date: Thu, 16 Nov 2017 14:10:20 -0800 Subject: [PATCH 080/607] Remove Dev from the pubspec version --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 362758ecc..b417673c4 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 2.2.1-dev +version: 2.2.1 authors: - Dmitriy Fibulwinter - Dart Team From 8dfb31401c876393e28ff639d0ff122fc96eeca6 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 20 Nov 2017 20:28:27 -0800 Subject: [PATCH 081/607] Fix uses_dynamic_as_bottom error (dart-lang/mockito#86) --- pkgs/mockito/lib/src/mock.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 4a6bfb847..1ee223f23 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -667,7 +667,7 @@ _InOrderVerification get verifyInOrder { return (List _) { _verificationInProgress = false; DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); - var tmpVerifyCalls = new List.from(_verifyCalls); + var tmpVerifyCalls = new List<_VerifyCall>.from(_verifyCalls); _verifyCalls.clear(); List matchedCalls = []; for (_VerifyCall verifyCall in tmpVerifyCalls) { From 5092bdaae732b185c04be22b7e05b9df04b28b83 Mon Sep 17 00:00:00 2001 From: Alan Russian Date: Tue, 12 Dec 2017 10:09:42 -0800 Subject: [PATCH 082/607] Throw an ArgumentError if `thenReturn` is called with futures or streams. (dart-lang/mockito#88) * Assert that `thenReturn` isn't called with futures nor streams. Also, documented this behavior in the README. This fixes dart-lang/mockito#79. --- pkgs/mockito/CHANGELOG.md | 6 +++++ pkgs/mockito/README.md | 40 +++++++++++++++++++++++++++++ pkgs/mockito/lib/src/mock.dart | 10 ++++++++ pkgs/mockito/test/mockito_test.dart | 30 ++++++++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 4f4bc0aae..73d5b0f96 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.3.0 + +* `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` + is provided. `thenReturn` calls with futures and streams should be changed to + `thenAnswer`. See the README for more information. + ## 2.2.0 * Add new feature to wait for an interaction: `untilCalled`. See the README for diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 9d94fc87d..c33390a3b 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -90,6 +90,46 @@ the same arguments many times. In other words: the order of stubbing matters, but it is meaningful rarely, e.g. when stubbing exactly the same method calls or sometimes when argument matchers are used, etc. +### A quick word on async stubbing + +**Using `thenReturn` to return a `Future` or `Stream` will throw an +`ArgumentError`.** This is because it can lead to unexpected behaviors. For +example: + +* If the method is stubbed in a different zone than the zone that consumes the + `Future`, unexpected behavior could occur. +* If the method is stubbed to return a failed `Future` or `Stream` and it + doesn't get consumed in the same run loop, it might get consumed by the + global exception handler instead of an exception handler the consumer applies. + +Instead, use `thenAnswer` to stub methods that return a `Future` or `Stream`. + +``` +// BAD +when(mock.methodThatReturnsAFuture()) + .thenReturn(new Future.value('Stub')); +when(mock.methodThatReturnsAStream()) + .thenReturn(new Stream.fromIterable(['Stub'])); + +// GOOD +when(mock.methodThatReturnsAFuture()) + .thenAnswer((_) => new Future.value('Stub')); +when(mock.methodThatReturnsAStream()) + .thenAnswer((_) => new Stream.fromIterable(['Stub'])); + +```` + +If, for some reason, you desire the behavior of `thenReturn`, you can return a +pre-defined instance. + +``` +// Use the above method unless you're sure you want to create the Future ahead +// of time. +final future = new Future.value('Stub'); +when(mock.methodThatReturnsAFuture()).thenAnswer((_) => future); +``` + + ## Argument matchers ```dart diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 1ee223f23..5f1c7d956 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -301,6 +301,16 @@ void clearInteractions(var mock) { class PostExpectation { thenReturn(expected) { + if (expected is Future) { + throw new ArgumentError( + '`thenReturn` should not be used to return a Future. ' + 'Instead, use `thenAnswer((_) => future)`.'); + } + if (expected is Stream) { + throw new ArgumentError( + '`thenReturn` should not be used to return a Stream. ' + 'Instead, use `thenAnswer((_) => stream)`.'); + } return _completeWhen((_) => expected); } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index eb586f476..4cc31badd 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -28,6 +28,8 @@ class RealClass { String methodWithNamedArgs(int x, {int y}) => "Real"; String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; String methodWithObjArgs(RealClass x) => "Real"; + Future methodReturningFuture() => new Future.value("Real"); + Stream methodReturningStream() => new Stream.fromIterable(["Real"]); // "SpecialArgs" here means type-parameterized args. But that makes for a long // method name. String typeParameterizedFn(List w, List x, @@ -286,6 +288,34 @@ void main() { }, throwsStateError); }); + test("thenReturn throws if provided Future", () { + expect( + () => when(mock.methodReturningFuture()) + .thenReturn(new Future.value("stub")), + throwsArgumentError); + }); + + test("thenReturn throws if provided Stream", () { + expect( + () => when(mock.methodReturningStream()) + .thenReturn(new Stream.fromIterable(["stub"])), + throwsArgumentError); + }); + + test("thenAnswer supports stubbing method returning a Future", () async { + when(mock.methodReturningFuture()) + .thenAnswer((_) => new Future.value("stub")); + + expect(await mock.methodReturningFuture(), "stub"); + }); + + test("thenAnswer supports stubbing method returning a Stream", () async { + when(mock.methodReturningStream()) + .thenAnswer((_) => new Stream.fromIterable(["stub"])); + + expect(await mock.methodReturningStream().toList(), ["stub"]); + }); + // [typed] API test("should mock method with typed arg matchers", () { when(mock.typeParameterizedFn(typed(any), typed(any))) From e39f8059b8d9fce2477d7f8e7a78ab74664a3e2e Mon Sep 17 00:00:00 2001 From: Ted Sander Date: Tue, 12 Dec 2017 11:18:33 -0800 Subject: [PATCH 083/607] Bump version to 3.0.0 (dart-lang/mockito#89) * Bump version to 3.0.0 Bump version to 3.0.0 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 73d5b0f96..335b05b3b 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.3.0 +## 3.0.0 * `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` is provided. `thenReturn` calls with futures and streams should be changed to diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b417673c4..839a09608 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 2.2.1 +version: 3.0.0 authors: - Dmitriy Fibulwinter - Dart Team From 6df3938bfc0580e392e736cffa2cb42961c664a1 Mon Sep 17 00:00:00 2001 From: Ted Sander Date: Wed, 13 Dec 2017 14:55:38 -0800 Subject: [PATCH 084/607] Tag the version as 3.0.0-alpha Tag the version as 3.0.0-alpha as we aren't done with changes that we want done with 3.0.0 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 839a09608..afad5bf39 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0 +version: 3.0.0-alpha authors: - Dmitriy Fibulwinter - Dart Team From 266a1523861c9ce7a162af3514694f7e0c9a752e Mon Sep 17 00:00:00 2001 From: Michael R Fairhurst Date: Mon, 29 Jan 2018 19:55:07 -0800 Subject: [PATCH 085/607] Capture T in `verify`+ so T may be `void` in dart 2: `verify(m.void())` (dart-lang/mockito#90) Capture T in `verify`+ so T may be `void` in dart 2: `verify(m.void())` While the semantics here are not yet fully decided, it is likely that dart 2 will disallow passing void into dynamic. The most backwards compatible solution here is to parameterize meta function types over T so that the type `void` is inferred, *possibly* allowing the result of a void method to be passed in. This change unblocks us for incrementally rolling out the new void semantics. It may in fact require a change to specify the `void` type directly, rather than allowing `T` to be void for any `T`, but for now that's not required and would hurt backwards compatibility since `void` didn't used to be allowed there. It also may not be allowed at all under any case, in which case we'd be in for a fun ride. --- pkgs/mockito/.travis.yml | 2 -- pkgs/mockito/lib/src/mock.dart | 16 ++++++++-------- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 6abd11c03..103a8a058 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -1,7 +1,5 @@ language: dart sudo: false dart: - - stable - dev - - 1.21.1 script: ./tool/travis.sh diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 5f1c7d956..3d552dddf 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -607,9 +607,9 @@ class VerificationResult { typedef dynamic Answering(Invocation realInvocation); -typedef VerificationResult Verification(matchingInvocations); +typedef Verification = VerificationResult Function(T matchingInvocations); -typedef void _InOrderVerification(List recordedInvocations); +typedef _InOrderVerification = void Function(List recordedInvocations); /// Verify that a method on a mock object was never called with the given /// arguments. @@ -655,7 +655,7 @@ Verification _makeVerify(bool never) { throw new StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (mock) { + return (T mock) { _verificationInProgress = false; if (_verifyCalls.length == 1) { _VerifyCall verifyCall = _verifyCalls.removeLast(); @@ -674,7 +674,7 @@ _InOrderVerification get verifyInOrder { throw new StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (List _) { + return (List _) { _verificationInProgress = false; DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = new List<_VerifyCall>.from(_verifyCalls); @@ -720,7 +720,7 @@ void verifyZeroInteractions(var mock) { } } -typedef PostExpectation Expectation(x); +typedef Expectation = PostExpectation Function(T x); /// Create a stub method response. /// @@ -744,13 +744,13 @@ Expectation get when { throw new StateError('Cannot call `when` within a stub response'); } _whenInProgress = true; - return (_) { + return (T _) { _whenInProgress = false; return new PostExpectation(); }; } -typedef Future InvocationLoader(_); +typedef InvocationLoader = Future Function(T _); /// Returns a future [Invocation] that will complete upon the first occurrence /// of the given invocation. @@ -767,7 +767,7 @@ typedef Future InvocationLoader(_); /// future will return immediately. InvocationLoader get untilCalled { _untilCalledInProgress = true; - return (_) { + return (T _) { _untilCalledInProgress = false; return _untilCall.invocationFuture; }; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index afad5bf39..8254c25df 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,7 +6,7 @@ authors: description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=1.21.0 <2.0.0' + sdk: '>=2.0.0-dev.16.0 <2.0.0' dependencies: collection: '^1.1.0' matcher: '^0.12.0' From 0d702ef25f54d1b61b75503d89ecee0048c17a47 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 8 Feb 2018 11:07:03 -0800 Subject: [PATCH 086/607] Remove mirrors implementation (dart-lang/mockito#91) --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/mirrors.dart | 16 ----------- pkgs/mockito/lib/src/mock.dart | 3 -- pkgs/mockito/lib/src/spy.dart | 36 ----------------------- pkgs/mockito/test/spy_test.dart | 51 --------------------------------- 5 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 pkgs/mockito/lib/mirrors.dart delete mode 100644 pkgs/mockito/lib/src/spy.dart delete mode 100644 pkgs/mockito/test/spy_test.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 335b05b3b..e5aea896f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,7 @@ * `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` is provided. `thenReturn` calls with futures and streams should be changed to `thenAnswer`. See the README for more information. +* Completely remove the mirrors implementation of Mockito (`mirrors.dart`). ## 2.2.0 diff --git a/pkgs/mockito/lib/mirrors.dart b/pkgs/mockito/lib/mirrors.dart deleted file mode 100644 index 10fa9f63b..000000000 --- a/pkgs/mockito/lib/mirrors.dart +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2016 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export 'mockito.dart'; -export 'src/spy.dart' show spy; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 3d552dddf..8e4f1ffe0 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Warning: Do not import dart:mirrors in this library, as it's exported via -// lib/mockito.dart, which is used for Dart AOT projects such as Flutter. - import 'dart:async'; import 'package:meta/meta.dart'; diff --git a/pkgs/mockito/lib/src/spy.dart b/pkgs/mockito/lib/src/spy.dart deleted file mode 100644 index 2f9b6c1f5..000000000 --- a/pkgs/mockito/lib/src/spy.dart +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2016 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This file is intentionally separated from 'mock.dart' in order to avoid -// bringing in the mirrors dependency into mockito.dart. -import 'dart:mirrors'; - -import 'mock.dart' show Mock, setDefaultResponse; - -import 'package:mockito/src/call_pair.dart'; - -/// Sets the default response of [mock] to be delegated to [spyOn]. -/// -/// __Example use__: -/// var mockAnimal = new MockAnimal(); -/// var realAnimal = new RealAnimal(); -/// spy(mockAnimal, realAnimal); -E spy(Mock mock, E spyOn) { - var mirror = reflect(spyOn); - setDefaultResponse( - mock, - () => new CallPair.allInvocations(mirror.delegate), - ); - return mock as E; -} diff --git a/pkgs/mockito/test/spy_test.dart b/pkgs/mockito/test/spy_test.dart deleted file mode 100644 index e45c1e2e7..000000000 --- a/pkgs/mockito/test/spy_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2016 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:mockito/src/mock.dart' show resetMockitoState; -import 'package:mockito/mirrors.dart'; -import 'package:test/test.dart'; - -import 'mockito_test.dart' show MockedClass, RealClass; - -void main() { - RealClass mock; - - setUp(() { - mock = new MockedClass(); - }); - - tearDown(() { - // In some of the tests that expect an Error to be thrown, Mockito's - // global state can become invalid. Reset it. - resetMockitoState(); - }); - - group("spy", () { - setUp(() { - mock = spy(new MockedClass(), new RealClass()); - }); - - test("should delegate to real object by default", () { - expect(mock.methodWithoutArgs(), 'Real'); - }); - test("should record interactions delegated to real object", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - test("should behave as mock when expectation are set", () { - when(mock.methodWithoutArgs()).thenReturn('Spied'); - expect(mock.methodWithoutArgs(), 'Spied'); - }); - }); -} From ecbc9977b72b3395f9dc69ce9d7b95fd408e3081 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 15 Feb 2018 17:20:00 -0800 Subject: [PATCH 087/607] Remove Spy docs (dart-lang/mockito#97) --- pkgs/mockito/README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index c33390a3b..8ce37c4c5 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -258,22 +258,6 @@ when(cat.eatFood(any)).thenReturn(false); expect(cat.eatFood("Fish"), false); ``` -## Spy - -```dart -// Spy creation: -var cat = spy(new MockCat(), new Cat()); - -// Stubbing - before execution: -when(cat.sound()).thenReturn("Purr"); - -// Using mocked interaction: -expect(cat.sound(), "Purr"); - -// Using a real object: -expect(cat.lives, 9); -``` - ## Debugging ```dart From 04cc3f0edb1aa02fac910c7f76bfe6e47c85ed3d Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 Feb 2018 21:53:47 -0800 Subject: [PATCH 088/607] Remove references to `@proxy`. (dart-lang/mockito#99) --- pkgs/mockito/test/example/iss/README.md | 3 --- pkgs/mockito/test/example/iss/iss_test.dart | 3 --- 2 files changed, 6 deletions(-) diff --git a/pkgs/mockito/test/example/iss/README.md b/pkgs/mockito/test/example/iss/README.md index 53a2daee2..18039bcf2 100644 --- a/pkgs/mockito/test/example/iss/README.md +++ b/pkgs/mockito/test/example/iss/README.md @@ -25,9 +25,6 @@ The unit test, `iss_dart.test`, mocks the IssLocator class: ``` // The Mock class uses noSuchMethod to catch all method invocations. -// The @proxy annotation indicates that noSuchMethod calls should be -// handled gracefully. For more info, see the readme for package:mockito. -@proxy class MockIssLocator extends Mock implements IssLocator {} ``` The tests check for two scenarios: diff --git a/pkgs/mockito/test/example/iss/iss_test.dart b/pkgs/mockito/test/example/iss/iss_test.dart index f21658c91..a971b7bf9 100644 --- a/pkgs/mockito/test/example/iss/iss_test.dart +++ b/pkgs/mockito/test/example/iss/iss_test.dart @@ -20,9 +20,6 @@ import 'package:test/test.dart'; import 'iss.dart'; // The Mock class uses noSuchMethod to catch all method invocations. -// The @proxy annotation indicates that noSuchMethod calls should be -// handled gracefully. For more info, see the readme for package:mockito. -@proxy class MockIssLocator extends Mock implements IssLocator {} void main() { From cb39238efae85d259b787b1d96cd273e1912942b Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Tue, 20 Feb 2018 16:53:13 -0800 Subject: [PATCH 089/607] Generic support for `thenReturn` and `thenAnswer` (dart-lang/mockito#101) Generic support for `thenReturn` and `thenAnswer` --- pkgs/mockito/CHANGELOG.md | 3 +++ pkgs/mockito/lib/src/mock.dart | 12 ++++++------ pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/mockito_test.dart | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index e5aea896f..0156b3fda 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -4,6 +4,9 @@ is provided. `thenReturn` calls with futures and streams should be changed to `thenAnswer`. See the README for more information. * Completely remove the mirrors implementation of Mockito (`mirrors.dart`). +* `thenReturn` and `thenAnswer` now support generics and infer the correct + types from the `when` call. + ## 2.2.0 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 8e4f1ffe0..fff64aa2f 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -296,8 +296,8 @@ void clearInteractions(var mock) { mock._realCalls.clear(); } -class PostExpectation { - thenReturn(expected) { +class PostExpectation { + T thenReturn(T expected) { if (expected is Future) { throw new ArgumentError( '`thenReturn` should not be used to return a Future. ' @@ -317,7 +317,7 @@ class PostExpectation { }); } - thenAnswer(Answering answer) { + T thenAnswer(Answering answer) { return _completeWhen(answer); } @@ -602,7 +602,7 @@ class VerificationResult { } } -typedef dynamic Answering(Invocation realInvocation); +typedef T Answering(Invocation realInvocation); typedef Verification = VerificationResult Function(T matchingInvocations); @@ -717,7 +717,7 @@ void verifyZeroInteractions(var mock) { } } -typedef Expectation = PostExpectation Function(T x); +typedef Expectation = PostExpectation Function(T x); /// Create a stub method response. /// @@ -743,7 +743,7 @@ Expectation get when { _whenInProgress = true; return (T _) { _whenInProgress = false; - return new PostExpectation(); + return new PostExpectation(); }; } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8254c25df..c9c83a049 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0-alpha +version: 3.0.0-dev authors: - Dmitriy Fibulwinter - Dart Team diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 4cc31badd..023000e97 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -260,8 +260,8 @@ void main() { }); test("should return mock to make simple oneline mocks", () { - RealClass mockWithSetup = - when(new MockedClass().methodWithoutArgs()).thenReturn("oneline"); + RealClass mockWithSetup = new MockedClass(); + when(mockWithSetup.methodWithoutArgs()).thenReturn("oneline"); expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); }); From 82b9c128d31771d7f5a23dd21ee449fca6f763a8 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 20 Feb 2018 16:57:02 -0800 Subject: [PATCH 090/607] First draft of upgrade guide for Mockito 3.0 (dart-lang/mockito#96) First (perhaps final) draft of upgrade guide --- pkgs/mockito/upgrading-to-mockito-3.md | 142 +++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 pkgs/mockito/upgrading-to-mockito-3.md diff --git a/pkgs/mockito/upgrading-to-mockito-3.md b/pkgs/mockito/upgrading-to-mockito-3.md new file mode 100644 index 000000000..9be8d1eb7 --- /dev/null +++ b/pkgs/mockito/upgrading-to-mockito-3.md @@ -0,0 +1,142 @@ +# Migrating to Mockito 3 + +Mockito 3 aims to provide a consistent, type-safe API that adheres to the new +type rules of Dart 2 (both in terms of type inference, and runtime rules). + +In order to provide this API, Mockito's implementation had to be completely +changed. In the old days of Dart 1 (and before the Dart Dev Compiler), we could +pass any old object anywhere, and no one would complain. For example: + +```dart +when(cat.eatFood(argThat(contains('mouse')), hungry: any))... +``` + +In this code, Mockito is not passing a String as the first argument to +`eatFood()`, nor a bool as `hungry:`, which is what the method expects. +Instead, Mockito passes an ArgumentMatcher, a completely different object from +a String or bool! This is no longer allowed in Dart 2. As an interim solution, +Mockito 1.0.0 sports an awkward `typed` function, which wraps other Mockito +functions, in order to simulate using the Dart 2-safe implementation: + +```dart +when(cat.eatFood( + typed(argThat(contains('mouse'))), hungry: typed(any, named: 'hungry')))... +``` + +This code is safe to use with DDC and Dart 2, as it actually passes `null` for +both the positional and the named argument of `eatFood()`, which is legal. +However kludgy this API may seem, it actually laid the foundation for the +Mockito 3 implementation, which simplifies the upgrade path. + +In Mockito 3, we've ripped out the old implementation, and upgraded all the +normal API calls (`any`, `argThat`, etc.) to use the new Dart 2-safe +implementation, and done away with the `typed` wrapper, allowing users to go +almost right back to the first API (just some modifications for named +arguments): + +```dart +when(cat.eatFood(argThat(contains('mouse')), hungry: anyNamed('hungry')))... +``` + +Here's a cheatsheet with examples of migrating different Mockito 2 API calls to +Mockito 3 API calls: + +## Table + +| Version | | +| --- | --------------------------------------------------------------------- | +| | Using argument matchers as positional arguments | +| 2.x | `when(obj.fn(typed(any)))...` | +| 3.0 | `when(obj.fn(any))...` | +| 2.x | `when(obj.fn(typed(argThat(equals(7)))))...` | +| 3.0 | `when(obj.fn(argThat(equals(7))))...` | +| | Using argument matchers as named arguments | +| 2.x | `when(obj.fn(foo: typed(any, named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: anyNamed('foo')))...` | +| 2.x | `when(obj.fn(foo: typed(argThat(equals(7)), named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: argThat(equals(7), named: 'foo')))...` | +| 2.x | `when(obj.fn(foo: typed(null, named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: argThat(isNull, named: 'foo')))...` | +| 2.x | `when(obj.fn(foo: typed(captureAny, named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: captureAnyNamed('foo')))...` | +| 2.x | `when(obj.fn(foo: typed(captureThat(equals(7)), named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: captureThatNamed(equals(7), 'foo')))...` | + +## Mockito 2.3 - a backward-and-forward-compatible API + +If you have a large codebase, it may be difficult to upgrade _all_ of your tests +to the Mockito 3 API all at once. To provide an incremental upgrade path, upgrade to +Mockito 2.3. + +Mockito 2.3 is a very tiny release on top of the Mockito 2.x API. In fact, +here's the diff: + +```dart +-argThat(Matcher matcher) => new ArgMatcher(matcher, false); ++argThat(Matcher matcher, {String named}) => new ArgMatcher(matcher, false); + +-captureThat(Matcher matcher) => new ArgMatcher(matcher, true); ++captureThat(Matcher matcher, {String named}) => new ArgMatcher(matcher, true); + ++anyNamed(String named) => typed(any, named: named); + ++captureAnyNamed(String named) => typed(captureAny, named: named); +``` + +Because this version uses the Mockito 2.x implementation, it is not really +compatible with Dart 2 runtime semantics. If you write: + +```dart +when(cat.eatFood(argThat(contains('mouse')), hungry: any))... +``` + +then Mockito is still passing an ArgumentMatcher as each argument to +`eatFood()`, which only accepts a String and a bool argument. +However, this version lets you incrementally upgrade your tests to the +Mockito 3 API. Here's the workflow: + +1. **Upgrade to `mockito: '^2.3.0'` in your project's dependencies**, in + `pubspec.yaml`. This should not cause any tests to break. Commit this change. + +2. **Change your usage of Mockito from the old API to the new API**, in as many + chunks as you like. + + In practice, this means searching for the different code + patterns in the table, and replacing the old API calls with the new. You can + typically just use your IDE's search, or `git grep`, and search for the + following text: + + * `when(` + * `verify(` + * `verifyNever(` + * `typed(` + * `named:` + + The table above should be sufficient to see how to ugprade Mockito calls, + but here's some more explanation: + + * Any use of `any`, `argThat`, `captureAny`, or `captureThat` that is passed + in as a named argument needs to be appended with the argument name as a + String: + + * `foo: any` → `foo: anyNamed('foo')` + * `foo: argThat(...)` → `foo: argThat(..., named: 'foo')` + * `foo: captureAny` → `foo: captureAnyNamed('foo')` + * `foo: captureThat(...)` → `foo: captureThat(..., named: 'foo')` + + * Any bare `null` argument needs to rewritten as `argThat(isNull)`. + + * Any `typed` wrapper can be removed, sliding any `named` arguments to inner API call: + + * `typed(any)` → `any` + * `foo: typed(any, named: 'foo')` → `foo: anyNamed('foo')` + + Make sure your tests still pass as you go. + + You can also temporarily bump your dependency on Mockito to `'^3.0.0'` and + run your tests to see if you are making progress, and if the tests you are + fixing actually _do continue to pass_ when using Mockito 3. (Of course: + don't commit that bump to Mockito 3 until all tests pass.) + +3. **Finally, upgrade to `mockito: '^3.0.0'`.** Make sure your tests all pass, + and commit! From f2b9b3f885d40161f10adbea15cde9cf7fd92ccb Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 20 Feb 2018 17:06:28 -0800 Subject: [PATCH 091/607] verify*Interactions methods throw helpfully that they expect Mock (dart-lang/mockito#92) verify*Interactions methods throw helpfully that they expect Mock --- pkgs/mockito/lib/src/mock.dart | 23 +++++++++++++++++------ pkgs/mockito/test/mockito_test.dart | 5 +++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index fff64aa2f..cf54cda96 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -703,17 +703,28 @@ _InOrderVerification get verifyInOrder { }; } +void _throwMockArgumentError(method) => + throw new ArgumentError('$method must only be given a Mock object'); + void verifyNoMoreInteractions(var mock) { - var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); - if (unverified.isNotEmpty) { - fail("No more calls expected, but following found: " + unverified.join()); + if (mock is Mock) { + var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); + if (unverified.isNotEmpty) { + fail("No more calls expected, but following found: " + unverified.join()); + } + } else { + _throwMockArgumentError('verifyNoMoreInteractions'); } } void verifyZeroInteractions(var mock) { - if (mock._realCalls.isNotEmpty) { - fail("No interaction expected, but following found: " + - mock._realCalls.join()); + if (mock is Mock) { + if (mock._realCalls.isNotEmpty) { + fail("No interaction expected, but following found: " + + mock._realCalls.join()); + } + } else { + _throwMockArgumentError('verifyZeroInteractions'); } } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 023000e97..a129473c2 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -936,6 +936,11 @@ void main() { verify(mock.methodWithoutArgs()); verifyNoMoreInteractions(mock); }); + + test("throws if given a real object", () { + expect( + () => verifyNoMoreInteractions(new RealClass()), throwsArgumentError); + }); }); group("verifyInOrder()", () { From c5bf532937e789a703eb233b2ebb68eda7e9eb32 Mon Sep 17 00:00:00 2001 From: Brian Armstrong Date: Sat, 24 Feb 2018 11:03:35 -0700 Subject: [PATCH 092/607] Update travis script to actually run dartanalyzer (dart-lang/mockito#102) Current dartanalyzer returns: "Options file not found: .analysis_options" Updating the file name to point at the actual analysis options in the repo --- pkgs/mockito/tool/travis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 4d2387ab6..93999c121 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -28,7 +28,7 @@ echo "PASSED" # Make sure we pass the analyzer echo "Checking dartanalyzer..." -FAILS_ANALYZER="$(find lib test -name "*.dart" | xargs dartanalyzer --options .analysis_options)" +FAILS_ANALYZER="$(find lib test -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)" if [[ $FAILS_ANALYZER == *"[error]"* ]] then echo "FAILED" From ce8490deed640059a0178946c6a4a42701ea53ba Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 27 Feb 2018 16:00:55 -0800 Subject: [PATCH 093/607] Try using a staged, fancy travis config. (dart-lang/mockito#100) * Try using a staged, fancy travis config. * Remove env. * Use dart:dev . * Fix script. * Fix script. * Ignore DDC failures for now. * Fix dart2js. * Actually skip. * Fix again. * Dartfmt. * Add dart_test.yaml. * Try firefox instead. * Fix travis. --- pkgs/mockito/.gitignore | 1 + pkgs/mockito/.travis.yml | 56 +++++- pkgs/mockito/CHANGELOG.md | 3 +- pkgs/mockito/dart_test.yaml | 5 + pkgs/mockito/lib/src/mock.dart | 18 +- pkgs/mockito/pubspec.yaml | 6 + .../mockito/test/invocation_matcher_test.dart | 45 +++-- pkgs/mockito/test/mockito_test.dart | 172 +++++++++++------- pkgs/mockito/tool/travis.sh | 66 ++++--- 9 files changed, 251 insertions(+), 121 deletions(-) create mode 100644 pkgs/mockito/dart_test.yaml diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index 04fd6ee54..d87ef84cb 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -1,6 +1,7 @@ # See https://www.dartlang.org/guides/libraries/private-files # Files and directories created by pub +.dart_tool .packages .pub pubspec.lock diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 103a8a058..ae5985215 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -1,5 +1,53 @@ language: dart -sudo: false -dart: - - dev -script: ./tool/travis.sh + +# Gives more resources on Travis (8GB Ram, 2 CPUs). +# Do not remove without verifying w/ Travis. +sudo: required +addons: + chrome: stable + +# Build stages: https://docs.travis-ci.com/user/build-stages/. +stages: + - presubmit + - build + - testing + +# 1. Run dartfmt, dartanalyzer, pub run test (VM). +# 2. Then run a build. +# 3. Then run tests compiled via dartdevc and dart2js. +jobs: + include: + - stage: presubmit + script: ./tool/travis.sh dartfmt + dart: dev + - stage: presubmit + script: ./tool/travis.sh dartanalyzer + dart: dev + - stage: presubmit + script: ./tool/travis.sh vm_test + dart: dev + - stage: build + script: ./tool/travis.sh dartdevc_build + dart: dev + - stage: testing + script: ./tool/travis.sh dartdevc_test + dart: dev + - stage: testing + script: ./tool/travis.sh dart2js_test + dart: dev + +# Only building master means that we don't run two builds for each pull request. +branches: + only: [master] + +# Incremental pub cache and builds. +cache: + directories: + - $HOME/.pub-cache + - .dart_tool + +# Necessary for Chrome and Firefox to run +before_install: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + - "t=0; until (xdpyinfo -display :99 &> /dev/null || test $t -gt 10); do sleep 1; let t=$t+1; done" diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 0156b3fda..38b21cc78 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,10 +3,9 @@ * `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` is provided. `thenReturn` calls with futures and streams should be changed to `thenAnswer`. See the README for more information. -* Completely remove the mirrors implementation of Mockito (`mirrors.dart`). * `thenReturn` and `thenAnswer` now support generics and infer the correct types from the `when` call. - +* Completely remove the mirrors implementation of Mockito (`mirrors.dart`). ## 2.2.0 diff --git a/pkgs/mockito/dart_test.yaml b/pkgs/mockito/dart_test.yaml new file mode 100644 index 000000000..0a3cb47e0 --- /dev/null +++ b/pkgs/mockito/dart_test.yaml @@ -0,0 +1,5 @@ +# TODO(https://github.com/dart-lang/test/issues/772): Headless chrome timeout. +override_platforms: + chrome: + settings: + headless: false diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index cf54cda96..8a800112e 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -297,7 +297,7 @@ void clearInteractions(var mock) { } class PostExpectation { - T thenReturn(T expected) { + void thenReturn(T expected) { if (expected is Future) { throw new ArgumentError( '`thenReturn` should not be used to return a Future. ' @@ -311,22 +311,20 @@ class PostExpectation { return _completeWhen((_) => expected); } - thenThrow(throwable) { + void thenThrow(throwable) { return _completeWhen((_) { throw throwable; }); } - T thenAnswer(Answering answer) { + void thenAnswer(Answering answer) { return _completeWhen(answer); } - _completeWhen(Answering answer) { + void _completeWhen(Answering answer) { _whenCall._setExpected(answer); - var mock = _whenCall.mock; _whenCall = null; _whenInProgress = false; - return mock; } } @@ -563,18 +561,18 @@ class ArgMatcher { } /// An argument matcher that matches any argument passed in "this" position. -get any => new ArgMatcher(anything, false); +/*ArgMatcher*/ get any => new ArgMatcher(anything, false); /// An argument matcher that matches any argument passed in "this" position, and /// captures the argument for later access with `captured`. -get captureAny => new ArgMatcher(anything, true); +/*ArgMatcher*/ get captureAny => new ArgMatcher(anything, true); /// An argument matcher that matches an argument that matches [matcher]. -argThat(Matcher matcher) => new ArgMatcher(matcher, false); +/*ArgMatcher*/ argThat(Matcher matcher) => new ArgMatcher(matcher, false); /// An argument matcher that matches an argument that matches [matcher], and /// captures the argument for later access with `captured`. -captureThat(Matcher matcher) => new ArgMatcher(matcher, true); +/*ArgMatcher*/ captureThat(Matcher matcher) => new ArgMatcher(matcher, true); /// A Strong-mode safe argument matcher that wraps other argument matchers. /// See the README for a full explanation. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c9c83a049..5f2ec9aec 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -7,8 +7,14 @@ description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: sdk: '>=2.0.0-dev.16.0 <2.0.0' + dependencies: collection: '^1.1.0' matcher: '^0.12.0' meta: '^1.0.4' test: '>=0.12.0 <0.13.0' + +dev_dependencies: + build_runner: ^0.7.11 + build_test: ^0.10.0 + build_web_compilers: ^0.3.1 diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index bd96900fd..4f46ae769 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -22,9 +22,12 @@ void main() { group('$isInvocation', () { test('positional arguments', () { - var call1 = stub.say('Hello'); - var call2 = stub.say('Hello'); - var call3 = stub.say('Guten Tag'); + stub.say('Hello'); + var call1 = Stub.lastInvocation; + stub.say('Hello'); + var call2 = Stub.lastInvocation; + stub.say('Guten Tag'); + var call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -36,9 +39,12 @@ void main() { }); test('named arguments', () { - var call1 = stub.eat('Chicken', alsoDrink: true); - var call2 = stub.eat('Chicken', alsoDrink: true); - var call3 = stub.eat('Chicken', alsoDrink: false); + stub.eat('Chicken', alsoDrink: true); + var call1 = Stub.lastInvocation; + stub.eat('Chicken', alsoDrink: true); + var call2 = Stub.lastInvocation; + stub.eat('Chicken', alsoDrink: false); + var call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -50,9 +56,12 @@ void main() { }); test('optional arguments', () { - var call1 = stub.lie(true); - var call2 = stub.lie(true); - var call3 = stub.lie(false); + stub.lie(true); + var call1 = Stub.lastInvocation; + stub.lie(true); + var call2 = Stub.lastInvocation; + stub.lie(false); + var call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -64,11 +73,13 @@ void main() { }); test('getter', () { - var call1 = stub.value; - var call2 = stub.value; + stub.value; + var call1 = Stub.lastInvocation; + stub.value; + var call2 = Stub.lastInvocation; stub.value = true; var call3 = Stub.lastInvocation; - shouldPass(call1, isInvocation(call2 as Invocation)); + shouldPass(call1, isInvocation(call2)); shouldFail( call1, isInvocation(call3), @@ -98,7 +109,8 @@ void main() { group('$invokes', () { test('positional arguments', () { - var call = stub.say('Hello'); + stub.say('Hello'); + var call = Stub.lastInvocation; shouldPass(call, invokes(#say, positionalArguments: ['Hello'])); shouldPass(call, invokes(#say, positionalArguments: [anything])); shouldFail( @@ -111,7 +123,8 @@ void main() { }); test('named arguments', () { - var call = stub.fly(miles: 10); + stub.fly(miles: 10); + var call = Stub.lastInvocation; shouldPass(call, invokes(#fly, namedArguments: {#miles: 10})); shouldPass(call, invokes(#fly, namedArguments: {#miles: greaterThan(5)})); shouldFail( @@ -143,7 +156,9 @@ class Stub implements Interface { const Stub(); @override - noSuchMethod(Invocation invocation) => lastInvocation = invocation; + noSuchMethod(Invocation invocation) { + lastInvocation = invocation; + } } // Copied from package:test, which doesn't expose it to users. diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index a129473c2..7a9a44f39 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -167,39 +167,42 @@ void main() { }); test("should mock method with argument matcher", () { - when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) + when(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))) .thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), isNull); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); test("should mock method with any argument matcher", () { - when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); + when(mock.methodWithNormalArgs(typed(any))).thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), equals("A lot!")); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); test("should mock method with any list argument matcher", () { - when(mock.methodWithListArgs(any)).thenReturn("A lot!"); + when(mock.methodWithListArgs(typed(any))).thenReturn("A lot!"); expect(mock.methodWithListArgs([42]), equals("A lot!")); expect(mock.methodWithListArgs([43]), equals("A lot!")); }); test("should mock method with multiple named args and matchers", () { - when(mock.methodWithTwoNamedArgs(any, y: any)).thenReturn("x y"); - when(mock.methodWithTwoNamedArgs(any, z: any)).thenReturn("x z"); + when(mock.methodWithTwoNamedArgs(typed(any), y: typed(any, named: 'y'))) + .thenReturn("x y"); + when(mock.methodWithTwoNamedArgs(typed(any), z: typed(any, named: 'z'))) + .thenReturn("x z"); expect(mock.methodWithTwoNamedArgs(42), isNull); expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); - when(mock.methodWithTwoNamedArgs(any, y: any, z: any)) + when(mock.methodWithTwoNamedArgs(typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))) .thenReturn("x y z"); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); }); test("should mock method with mix of argument matchers and real things", () { - when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) + when(mock.methodWithPositionalArgs(typed(argThat(greaterThan(100))), 17)) .thenReturn("A lot with 17"); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); @@ -248,12 +251,13 @@ void main() { //no need to mock setter, except if we will have spies later... test("should mock method with thrown result", () { - when(mock.methodWithNormalArgs(any)).thenThrow(new StateError('Boo')); + when(mock.methodWithNormalArgs(typed(any))) + .thenThrow(new StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); test("should mock method with calculated result", () { - when(mock.methodWithNormalArgs(any)).thenAnswer( + when(mock.methodWithNormalArgs(typed(any))).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); expect(mock.methodWithNormalArgs(43), equals("43")); expect(mock.methodWithNormalArgs(42), equals("42")); @@ -272,8 +276,10 @@ void main() { }); test("should mock method with calculated result", () { - when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); - when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); + when(mock.methodWithNormalArgs(typed(argThat(equals(43))))) + .thenReturn("43"); + when(mock.methodWithNormalArgs(typed(argThat(equals(42))))) + .thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); @@ -356,7 +362,7 @@ void main() { test("should mock method when [typed] used alongside matched [null].", () { when(mock.typeParameterizedFn( - typed(any), argThat(equals(null)), typed(any))) + typed(any), typed(argThat(equals(null))), typed(any))) .thenReturn("A lot!"); expect(mock.typeParameterizedFn([42], null, [44]), equals("A lot!")); }); @@ -372,7 +378,8 @@ void main() { test("should mock method with named, typed arg matcher and an arg matcher", () { when(mock.typeParameterizedNamedFn(typed(any), [43], - y: typed(any, named: "y"), z: argThat(contains(45)))) + y: typed(any, named: "y"), + z: typed(argThat(contains(45)), named: 'z'))) .thenReturn("A lot!"); expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), equals("A lot!")); @@ -427,66 +434,76 @@ void main() { test("waits for method with normal args", () async { mock.methodWithNormalArgs(1); - await untilCalled(mock.methodWithNormalArgs(any)); + await untilCalled(mock.methodWithNormalArgs(typed(any))); - verify(mock.methodWithNormalArgs(any)).called(1); + verify(mock.methodWithNormalArgs(typed(any))).called(1); }); test("waits for method with list args", () async { mock.methodWithListArgs([1]); - await untilCalled(mock.methodWithListArgs(any)); + await untilCalled(mock.methodWithListArgs(typed(any))); - verify(mock.methodWithListArgs(any)).called(1); + verify(mock.methodWithListArgs(typed(any))).called(1); }); test("waits for method with positional args", () async { mock.methodWithPositionalArgs(1, 2); - await untilCalled(mock.methodWithPositionalArgs(any, any)); + await untilCalled( + mock.methodWithPositionalArgs(typed(any), typed(any))); - verify(mock.methodWithPositionalArgs(any, any)).called(1); + verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); }); test("waits for method with named args", () async { mock.methodWithNamedArgs(1, y: 2); - await untilCalled(mock.methodWithNamedArgs(any, y: any)); + await untilCalled( + mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))); - verify(mock.methodWithNamedArgs(any, y: any)).called(1); + verify(mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))) + .called(1); }); test("waits for method with two named args", () async { mock.methodWithTwoNamedArgs(1, y: 2, z: 3); - await untilCalled(mock.methodWithTwoNamedArgs(any, y: any, z: any)); + await untilCalled(mock.methodWithTwoNamedArgs(typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))); - verify(mock.methodWithTwoNamedArgs(any, y: any, z: any)).called(1); + verify(mock.methodWithTwoNamedArgs(typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))) + .called(1); }); test("waits for method with obj args", () async { mock.methodWithObjArgs(new RealClass()); - await untilCalled(mock.methodWithObjArgs(any)); + await untilCalled(mock.methodWithObjArgs(typed(any))); - verify(mock.methodWithObjArgs(any)).called(1); + verify(mock.methodWithObjArgs(typed(any))).called(1); }); test("waits for function with positional parameters", () async { mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]); - await untilCalled(mock.typeParameterizedFn(any, any, any, any)); + await untilCalled(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); - verify(mock.typeParameterizedFn(any, any, any, any)).called(1); + verify(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))) + .called(1); }); test("waits for function with named parameters", () async { mock.typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]); - await untilCalled( - mock.typeParameterizedNamedFn(any, any, y: any, z: any)); + await untilCalled(mock.typeParameterizedNamedFn(typed(any), typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))); - verify(mock.typeParameterizedNamedFn(any, any, y: any, z: any)) + verify(mock.typeParameterizedNamedFn(typed(any), typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))) .called(1); }); @@ -523,75 +540,89 @@ void main() { test("waits for method with normal args", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithNormalArgs(any)); + verifyNever(mock.methodWithNormalArgs(typed(any))); - await untilCalled(mock.methodWithNormalArgs(any)); + await untilCalled(mock.methodWithNormalArgs(typed(any))); - verify(mock.methodWithNormalArgs(any)).called(1); + verify(mock.methodWithNormalArgs(typed(any))).called(1); }); test("waits for method with list args", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithListArgs(any)); + verifyNever(mock.methodWithListArgs(typed(any))); - await untilCalled(mock.methodWithListArgs(any)); + await untilCalled(mock.methodWithListArgs(typed(any))); - verify(mock.methodWithListArgs(any)).called(1); + verify(mock.methodWithListArgs(typed(any))).called(1); }); test("waits for method with positional args", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithPositionalArgs(any, any)); + verifyNever(mock.methodWithPositionalArgs(typed(any), typed(any))); - await untilCalled(mock.methodWithPositionalArgs(any, any)); + await untilCalled( + mock.methodWithPositionalArgs(typed(any), typed(any))); - verify(mock.methodWithPositionalArgs(any, any)).called(1); + verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); }); test("waits for method with named args", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithNamedArgs(any, y: any)); + verifyNever( + mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))); - await untilCalled(mock.methodWithNamedArgs(any, y: any)); + await untilCalled( + mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))); - verify(mock.methodWithNamedArgs(any, y: any)).called(1); + verify(mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))) + .called(1); }); test("waits for method with two named args", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithTwoNamedArgs(any, y: any, z: any)); + verifyNever(mock.methodWithTwoNamedArgs(typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))); - await untilCalled(mock.methodWithTwoNamedArgs(any, y: any, z: any)); + await untilCalled(mock.methodWithTwoNamedArgs(typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))); - verify(mock.methodWithTwoNamedArgs(any, y: any, z: any)).called(1); + verify(mock.methodWithTwoNamedArgs(typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))) + .called(1); }); test("waits for method with obj args", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithObjArgs(any)); + verifyNever(mock.methodWithObjArgs(typed(any))); - await untilCalled(mock.methodWithObjArgs(any)); + await untilCalled(mock.methodWithObjArgs(typed(any))); - verify(mock.methodWithObjArgs(any)).called(1); + verify(mock.methodWithObjArgs(typed(any))).called(1); }); test("waits for function with positional parameters", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.typeParameterizedFn(any, any, any, any)); + verifyNever(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); - await untilCalled(mock.typeParameterizedFn(any, any, any, any)); + await untilCalled(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); - verify(mock.typeParameterizedFn(any, any, any, any)).called(1); + verify(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))) + .called(1); }); test("waits for function with named parameters", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.typeParameterizedNamedFn(any, any, y: any, z: any)); + verifyNever(mock.typeParameterizedNamedFn(typed(any), typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))); - await untilCalled( - mock.typeParameterizedNamedFn(any, any, y: any, z: any)); + await untilCalled(mock.typeParameterizedNamedFn(typed(any), typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))); - verify(mock.typeParameterizedNamedFn(any, any, y: any, z: any)) + verify(mock.typeParameterizedNamedFn(typed(any), typed(any), + y: typed(any, named: 'y'), z: typed(any, named: 'z'))) .called(1); }); @@ -687,15 +718,16 @@ void main() { expectFail( "No matching calls. All calls: MockedClass.methodWithNormalArgs(100)\n" "$noMatchingCallsFooter", () { - verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); + verify(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))); }); - verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100)))); + verify( + mock.methodWithNormalArgs(typed(argThat(greaterThanOrEqualTo(100))))); }); test("should mock method with argument capturer", () { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured, + expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, equals([50, 100])); }); @@ -703,12 +735,12 @@ void main() { mock.methodWithNormalArgs(50); mock.methodWithNormalArgs(100); expect( - verify(mock.methodWithNormalArgs(captureThat(greaterThan(75)))) + verify(mock.methodWithNormalArgs(typed(captureThat(greaterThan(75))))) .captured .single, equals(100)); expect( - verify(mock.methodWithNormalArgs(captureThat(lessThan(75)))) + verify(mock.methodWithNormalArgs(typed(captureThat(lessThan(75))))) .captured .single, equals(50)); @@ -721,15 +753,16 @@ void main() { "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)\n" "$noMatchingCallsFooter", () { verify(mock.methodWithPositionalArgs( - argThat(greaterThanOrEqualTo(100)), 18)); + typed(argThat(greaterThanOrEqualTo(100))), 18)); }); expectFail( "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)\n" "$noMatchingCallsFooter", () { - verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); + verify(mock.methodWithPositionalArgs( + typed(argThat(greaterThan(100))), 17)); }); verify(mock.methodWithPositionalArgs( - argThat(greaterThanOrEqualTo(100)), 17)); + typed(argThat(greaterThanOrEqualTo(100))), 17)); }); test("should mock getter", () { @@ -993,20 +1026,22 @@ void main() { group("capture", () { test("capture should work as captureOut", () { mock.methodWithNormalArgs(42); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, + expect( + verify(mock.methodWithNormalArgs(typed(captureAny))).captured.single, equals(42)); }); test("should captureOut list arguments", () { mock.methodWithListArgs([42]); - expect(verify(mock.methodWithListArgs(captureAny)).captured.single, + expect(verify(mock.methodWithListArgs(typed(captureAny))).captured.single, equals([42])); }); test("should captureOut multiple arguments", () { mock.methodWithPositionalArgs(1, 2); expect( - verify(mock.methodWithPositionalArgs(captureAny, captureAny)) + verify(mock.methodWithPositionalArgs( + typed(captureAny), typed(captureAny))) .captured, equals([1, 2])); }); @@ -1015,7 +1050,8 @@ void main() { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); expect( - verify(mock.methodWithPositionalArgs(captureAny, captureAny)) + verify(mock.methodWithPositionalArgs( + typed(captureAny), typed(captureAny))) .captured, equals([2, 3])); }); @@ -1023,7 +1059,7 @@ void main() { test("should captureOut multiple invocations", () { mock.methodWithNormalArgs(1); mock.methodWithNormalArgs(2); - expect(verify(mock.methodWithNormalArgs(captureAny)).captured, + expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, equals([1, 2])); }); }); diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 93999c121..d3adaadf2 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -14,30 +14,52 @@ #!/bin/bash -# Make sure dartfmt is run on everything -# This assumes you have dart_style as a dev_dependency -echo "Checking dartfmt..." -NEEDS_DARTFMT="$(find lib test -name "*.dart" | xargs dartfmt -n)" -if [[ ${NEEDS_DARTFMT} != "" ]] -then - echo "FAILED" - echo "${NEEDS_DARTFMT}" +if [ "$#" == "0" ]; then + echo -e '\033[31mAt least one task argument must be provided!\033[0m' exit 1 fi -echo "PASSED" -# Make sure we pass the analyzer -echo "Checking dartanalyzer..." -FAILS_ANALYZER="$(find lib test -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)" -if [[ $FAILS_ANALYZER == *"[error]"* ]] -then - echo "FAILED" - echo "${FAILS_ANALYZER}" - exit 1 -fi -echo "PASSED" +EXIT_CODE=0 + +while (( "$#" )); do + TASK=$1 + case $TASK in + dartfmt) echo + echo -e '\033[1mTASK: dartfmt\033[22m' + echo -e 'dartfmt -n --set-exit-if-changed .' + dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? + ;; + dartanalyzer) echo + echo -e '\033[1mTASK: dartanalyzer\033[22m' + echo -e 'dartanalyzer --fatal-warnings .' + dartanalyzer --fatal-warnings . || EXIT_CODE=$? + ;; + vm_test) echo + echo -e '\033[1mTASK: vmn_test\033[22m' + echo -e 'pub run test -p vm' + pub run test -p vm || EXIT_CODE=$? + ;; + dartdevc_build) echo + echo -e '\033[1mTASK: build\033[22m' + echo -e 'pub run build_runner build --fail-on-severe' + pub run build_runner build --fail-on-severe || EXIT_CODE=$? + ;; + dartdevc_test) echo + echo -e '\033[1mTASK: dartdevc_test\033[22m' + echo -e 'pub run build_runner test -- -p firefox' + pub run build_runner test -- -p firefox || EXIT_CODE=$? + ;; + dart2js_test) echo + echo -e '\033[1mTASK: dart2js_test\033[22m' + echo -e 'pub run test -p firefox' + pub run test -p firefox || EXIT_CODE=$? + ;; + *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" + EXIT_CODE=1 + ;; + esac -# Fail on anything that fails going forward. -set -e + shift +done -pub run test +exit $EXIT_CODE From 67d44452698a4495ab28e1ae84f6654af8e3299f Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Wed, 28 Feb 2018 20:27:26 -0800 Subject: [PATCH 094/607] Switch back to Chrome for Travis (dart-lang/mockito#104) --- pkgs/mockito/tool/travis.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index d3adaadf2..38419b8a6 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -35,7 +35,7 @@ while (( "$#" )); do dartanalyzer --fatal-warnings . || EXIT_CODE=$? ;; vm_test) echo - echo -e '\033[1mTASK: vmn_test\033[22m' + echo -e '\033[1mTASK: vm_test\033[22m' echo -e 'pub run test -p vm' pub run test -p vm || EXIT_CODE=$? ;; @@ -46,13 +46,13 @@ while (( "$#" )); do ;; dartdevc_test) echo echo -e '\033[1mTASK: dartdevc_test\033[22m' - echo -e 'pub run build_runner test -- -p firefox' - pub run build_runner test -- -p firefox || EXIT_CODE=$? + echo -e 'pub run build_runner test -- -p chrome' + pub run build_runner test -- -p chrome || EXIT_CODE=$? ;; dart2js_test) echo echo -e '\033[1mTASK: dart2js_test\033[22m' - echo -e 'pub run test -p firefox' - pub run test -p firefox || EXIT_CODE=$? + echo -e 'pub run test -p chrome' + pub run test -p chrome || EXIT_CODE=$? ;; *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" EXIT_CODE=1 From 1aafed1a1437b21dffeab52f490c3848c05169db Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 5 Apr 2018 16:29:11 -0700 Subject: [PATCH 095/607] Bump to 3.0.0-alpha+3 (dart-lang/mockito#112) --- pkgs/mockito/CHANGELOG.md | 15 +++++++++++---- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 38b21cc78..49ce5dbb6 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,12 +1,19 @@ -## 3.0.0 +## 3.0.0-alpha+3 -* `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` - is provided. `thenReturn` calls with futures and streams should be changed to - `thenAnswer`. See the README for more information. * `thenReturn` and `thenAnswer` now support generics and infer the correct types from the `when` call. * Completely remove the mirrors implementation of Mockito (`mirrors.dart`). +## 3.0.0-alpha+2 + +* Support stubbing of void methods in Dart 2. + +## 3.0.0-alpha + +* `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` + is provided. `thenReturn` calls with futures and streams should be changed to + `thenAnswer`. See the README for more information. + ## 2.2.0 * Add new feature to wait for an interaction: `untilCalled`. See the README for diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 5f2ec9aec..a78e1cb93 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0-dev +version: 3.0.0-alpha+3 authors: - Dmitriy Fibulwinter - Dart Team From d596ba5f1246c019809f8642a41e2408f5a381e1 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Wed, 11 Apr 2018 17:00:14 +0200 Subject: [PATCH 096/607] Remove upper case constants (dart-lang/mockito#113) Remove usage of upper-case constants. --- pkgs/mockito/test/example/iss/iss.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/test/example/iss/iss.dart b/pkgs/mockito/test/example/iss/iss.dart index 71c860579..9cfc7ebf9 100644 --- a/pkgs/mockito/test/example/iss/iss.dart +++ b/pkgs/mockito/test/example/iss/iss.dart @@ -42,7 +42,7 @@ class IssLocator { // Returns the point on the earth directly under the space station // at this moment. Response rs = await client.get('http://api.open-notify.org/iss-now.json'); - Map data = JSON.decode(rs.body); + Map data = jsonDecode(rs.body); double latitude = double.parse(data['iss_position']['latitude']); double longitude = double.parse(data['iss_position']['longitude']); _position = new Point(latitude, longitude); From 6edba139b35bc7f4bb90d1658d23b82b1bcf059b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 17 Apr 2018 11:05:17 -0700 Subject: [PATCH 097/607] Update the upgrade-to-mockito-3 doc (dart-lang/mockito#114) Update the upgrade-to-mockito-3 doc --- pkgs/mockito/upgrading-to-mockito-3.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/upgrading-to-mockito-3.md b/pkgs/mockito/upgrading-to-mockito-3.md index 9be8d1eb7..aec6e320c 100644 --- a/pkgs/mockito/upgrading-to-mockito-3.md +++ b/pkgs/mockito/upgrading-to-mockito-3.md @@ -45,12 +45,13 @@ Mockito 3 API calls: | Version | | | --- | --------------------------------------------------------------------- | -| | Using argument matchers as positional arguments | +| | **Using argument matchers as positional arguments** | | 2.x | `when(obj.fn(typed(any)))...` | | 3.0 | `when(obj.fn(any))...` | | 2.x | `when(obj.fn(typed(argThat(equals(7)))))...` | | 3.0 | `when(obj.fn(argThat(equals(7))))...` | -| | Using argument matchers as named arguments | +| | | +| | **Using argument matchers as named arguments** | | 2.x | `when(obj.fn(foo: typed(any, named: 'foo')))...` | | 3.0 | `when(obj.fn(foo: anyNamed('foo')))...` | | 2.x | `when(obj.fn(foo: typed(argThat(equals(7)), named: 'foo')))...` | @@ -60,16 +61,16 @@ Mockito 3 API calls: | 2.x | `when(obj.fn(foo: typed(captureAny, named: 'foo')))...` | | 3.0 | `when(obj.fn(foo: captureAnyNamed('foo')))...` | | 2.x | `when(obj.fn(foo: typed(captureThat(equals(7)), named: 'foo')))...` | -| 3.0 | `when(obj.fn(foo: captureThatNamed(equals(7), 'foo')))...` | +| 3.0 | `when(obj.fn(foo: captureThat(equals(7), named: 'foo')))...` | -## Mockito 2.3 - a backward-and-forward-compatible API +## Mockito 3.0.0-beta - a backward-and-forward-compatible API If you have a large codebase, it may be difficult to upgrade _all_ of your tests to the Mockito 3 API all at once. To provide an incremental upgrade path, upgrade to -Mockito 2.3. +Mockito 3.0.0-beta. -Mockito 2.3 is a very tiny release on top of the Mockito 2.x API. In fact, -here's the diff: +Mockito 3.0.0-beta is a very tiny release on top of Mockito 3.0.0-alpha+3, +which provides the Mockito 2.x API. In fact, here's the diff: ```dart -argThat(Matcher matcher) => new ArgMatcher(matcher, false); @@ -95,7 +96,7 @@ then Mockito is still passing an ArgumentMatcher as each argument to However, this version lets you incrementally upgrade your tests to the Mockito 3 API. Here's the workflow: -1. **Upgrade to `mockito: '^2.3.0'` in your project's dependencies**, in +1. **Upgrade to `mockito: '^3.0.0-beta'` in your project's dependencies**, in `pubspec.yaml`. This should not cause any tests to break. Commit this change. 2. **Change your usage of Mockito from the old API to the new API**, in as many From d222f95c1db66d533bb1a14ef4129aa31b410e8b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 17 Apr 2018 11:07:02 -0700 Subject: [PATCH 098/607] Flip to Dart 2.0-only syntax (dart-lang/mockito#85) * Flip to Dart 2.0-only syntax * Touch up README and tests * Change some error text * @Deprecate typed --- pkgs/mockito/README.md | 169 ++++++++++------------- pkgs/mockito/lib/mockito.dart | 2 + pkgs/mockito/lib/src/mock.dart | 158 ++++++++++++--------- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/mockito_test.dart | 206 ++++++++-------------------- 5 files changed, 222 insertions(+), 315 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 8ce37c4c5..ee3af3a37 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -132,11 +132,16 @@ when(mock.methodThatReturnsAFuture()).thenAnswer((_) => future); ## Argument matchers +Mockito provides the concept of the "argument matcher" (using the class +ArgMatcher) to capture arguments and to track how named arguments are passed. +In most cases, both plain arguments and argument matchers can be passed into +mock methods: + ```dart -// You can use arguments itself: +// You can use plain arguments themselves: when(cat.eatFood("fish")).thenReturn(true); -// ... or collections: +// ... including collections: when(cat.walk(["roof","tree"])).thenReturn(2); // ... or matchers: @@ -164,6 +169,38 @@ If an argument other than an ArgMatcher (like `any`, `anyNamed()`, `argThat`, used for argument matching. If you need more strict matching consider use `argThat(identical(arg))`. +However, note that `null` cannot be used as an argument adjacent to ArgMatcher +arguments, nor as an un-wrapped value passed as a named argument. For example: + +```dart +verify(cat.hunt("back yard", null)); // OK: no ArgMatchers. +verify(cat.hunt(argThat(contains("yard")), null)); // BAD: adjacent null. +verify(cat.hunt(argThat(contains("yard")), argThat(isNull))); // OK: wrapped in ArgMatcher. +verify(cat.eatFood("Milk", hungry: null)); // BAD: null as named argument. +verify(cat.eatFood("Milk", hungry: argThat(isNull))); // BAD: null as named argument. +``` + +## Named arguments + +Mockito currently has an awkward nuisance to it's syntax: named arguments and +argument matchers require more specification than you might think: you must +declare the name of the argument in the argument matcher. This is because we +can't rely on the position of a named argument, and the language doesn't +provide a mechanism to answer "Is this element being used as a named element?" + +```dart +// GOOD: argument matchers include their names. +when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(0); +when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))).thenReturn(0); +when(cat.eatFood(any, hungry: captureAnyNamed('hungry'))).thenReturn(0); +when(cat.eatFood(any, hungry: captureArgThat(isNotNull, named: 'hungry'))).thenReturn(0); + +// BAD: argument matchers do not include their names. +when(cat.eatFood(any, hungry: any)).thenReturn(0); +when(cat.eatFood(any, hungry: argThat(isNotNull))).thenReturn(0); +when(cat.eatFood(any, hungry: captureAny)).thenReturn(0); +when(cat.eatFood(any, hungry: captureArgThat(isNotNull))).thenReturn(0); +``` ## Verifying exact number of invocations / at least x / never @@ -268,92 +305,11 @@ logInvocations([catOne, catTwo]); throwOnMissingStub(cat); ``` -## Strong mode compliance - -Unfortunately, the use of the arg matchers in mock method calls (like `cat.eatFood(any)`) -violates the [Strong mode] type system. Specifically, if the method signature of a mocked -method has a parameter with a parameterized type (like `List`), then passing `any` or -`argThat` will result in a Strong mode warning: - -> [warning] Unsound implicit cast from dynamic to List<int> - -In order to write Strong mode-compliant tests with Mockito, you might need to use `typed`, -annotating it with a type parameter comment. Let's use a slightly different `Cat` class to -show some examples: - -```dart -class Cat { - bool eatFood(List foods, [List mixins]) => true; - int walk(List places, {Map gaits}) => 0; -} - -class MockCat extends Mock implements Cat {} - -var cat = new MockCat(); -``` - -OK, what if we try to stub using `any`: - -```dart -when(cat.eatFood(any)).thenReturn(true); -``` - -Let's analyze this code: - -``` -$ dartanalyzer --strong test/cat_test.dart -Analyzing [lib/cat_test.dart]... -[warning] Unsound implicit cast from dynamic to List (test/cat_test.dart, line 12, col 20) -1 warning found. -``` - -This code is not Strong mode-compliant. Let's change it to use `typed`: - -```dart -when(cat.eatFood(typed(any))) -``` - -``` -$ dartanalyzer --strong test/cat_test.dart -Analyzing [lib/cat_test.dart]... -No issues found -``` - -Great! A little ugly, but it works. Here are some more examples: - -```dart -when(cat.eatFood(typed(any), typed(any))).thenReturn(true); -when(cat.eatFood(typed(argThat(contains("fish"))))).thenReturn(true); -``` - -Named args require one more component: `typed` needs to know what named argument it is -being passed into: - -```dart -when(cat.walk(typed(any), gaits: typed(any, named: 'gaits'))) - .thenReturn(true); -``` - -Note the `named` argument. Mockito should fail gracefully if you forget to name a `typed` -call passed in as a named argument, or name the argument incorrectly. - -One more note about the `typed` API: you cannot mix `typed` arguments with `null` -arguments: - -```dart -when(cat.eatFood(null, typed(any))).thenReturn(true); // Throws! -when(cat.eatFood( - argThat(equals(null)), - typed(any))).thenReturn(true); // Works. -``` - -[Strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md - ## How it works -The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to catch -all method invocations, and returns the value that you have configured beforehand with -`when()` calls. +The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to +catch all method invocations, and returns the value that you have configured +beforehand with `when()` calls. The implementation of `when()` is a bit more tricky. Take this example: @@ -367,14 +323,27 @@ when(cat.sound()).thenReturn("Purr"); Since `cat.sound()` returns `null`, how can the `when()` call configure it? -It works, because `when` is not a function, but a top level getter that _returns_ a function. -Before returning the function, it sets a flag (`_whenInProgress`), so that all `Mock` objects -know to return a "matcher" (internally `_WhenCall`) instead of the expected value. As soon as -the function has been invoked `_whenInProgress` is set back to `false` and Mock objects behave -as normal. - -> **Be careful** never to write `when;` (without the function call) anywhere. This would set -> `_whenInProgress` to `true`, and the next mock invocation will return an unexpected value. +It works, because `when` is not a function, but a top level getter that +_returns_ a function. Before returning the function, it sets a flag +(`_whenInProgress`), so that all `Mock` objects know to return a "matcher" +(internally `_WhenCall`) instead of the expected value. As soon as the function +has been invoked `_whenInProgress` is set back to `false` and Mock objects +behave as normal. + +Argument matchers work by storing the wrapped arguments, one after another, +until the `when` (or `verify`) call gathers everything that has been stored, +and creates an InvocationMatcher with the arguments. This is a simple process +for positional arguments: the order in which the arguments has been stored +should be preserved for matching an invocation. Named arguments are trickier: +their evaluation order is not specified, so if Mockito blindly stored them in +the order of their evaluation, it wouldn't be able to match up each argument +matcher with the correct name. This is why each named argument matcher must +repeat it's own name. `foo: anyNamed('foo')` tells Mockito to store an argument +matcher for an invocation under the name 'foo'. + +> **Be careful** never to write `when;` (without the function call) anywhere. +> This would set `_whenInProgress` to `true`, and the next mock invocation will +> return an unexpected value. The same goes for "chaining" mock objects in a test call. This will fail: @@ -393,12 +362,12 @@ verify(mockUtils.stringUtils.uppercase()).called(1); verify(mockStringUtils.uppercase()).called(1); ``` -This fails, because `verify` sets an internal flag, so mock objects don't return their mocked -values anymore but their matchers. So `mockUtils.stringUtils` will *not* return the mocked -`stringUtils` object you put inside. - +This fails, because `verify` sets an internal flag, so mock objects don't +return their mocked values anymore but their matchers. So +`mockUtils.stringUtils` will *not* return the mocked `stringUtils` object you +put inside. -You can look at the `when` and `Mock.noSuchMethod` implementations to see how it's done. -It's very straightforward. +You can look at the `when` and `Mock.noSuchMethod` implementations to see how +it's done. It's very straightforward. **NOTE:** This is not an official Google product diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 3aaadc239..8643a881b 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -20,8 +20,10 @@ export 'src/mock.dart' // -- setting behaviour when, any, + anyNamed, argThat, captureAny, + captureAnyNamed, captureThat, typed, Answering, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 8a800112e..0c4aaf47a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -27,8 +27,8 @@ _UntilCall _untilCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; final _TimeStampProvider _timer = new _TimeStampProvider(); final List _capturedArgs = []; -final List _typedArgs = []; -final Map _typedNamedArgs = {}; +final List _storedArgs = []; +final Map _storedNamedArgs = {}; // Hidden from the public API, used by spy.dart. void setDefaultResponse(Mock mock, CallPair defaultResponse()) { @@ -63,7 +63,7 @@ void throwOnMissingStub(Mock mock) { /// var cat = new MockCat(); /// /// // When 'getSound' is called, return 'Woof' -/// when(cat.getSound(typed(any))).thenReturn('Woof'); +/// when(cat.getSound(any)).thenReturn('Woof'); /// /// // Try making a Cat sound... /// print(cat.getSound('foo')); // Prints 'Woof' @@ -99,7 +99,7 @@ class Mock { // noSuchMethod is that 'magic' that allows us to ignore implementing fields // and methods and instead define them later at compile-time per instance. // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH - invocation = _useTypedInvocationIfSet(invocation); + invocation = _useMatchedInvocationIfSet(invocation); if (_whenInProgress) { _whenCall = new _WhenCall(this, invocation); return null; @@ -137,19 +137,19 @@ class Mock { typedef CallPair _ReturnsCannedResponse(); -// When using the typed() matcher, we transform our invocation to have knowledge -// of which arguments are wrapped with typed() and which ones are not. Otherwise -// we just use the existing invocation object. -Invocation _useTypedInvocationIfSet(Invocation invocation) { - if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) { - invocation = new _InvocationForTypedArguments(invocation); +// When using an [ArgMatcher], we transform our invocation to have knowledge of +// which arguments are wrapped, and which ones are not. Otherwise we just use +// the existing invocation object. +Invocation _useMatchedInvocationIfSet(Invocation invocation) { + if (_storedArgs.isNotEmpty || _storedNamedArgs.isNotEmpty) { + invocation = new _InvocationForMatchedArguments(invocation); } return invocation; } -/// An Invocation implementation that takes arguments from [_typedArgs] and -/// [_typedNamedArgs]. -class _InvocationForTypedArguments extends Invocation { +/// An Invocation implementation that takes arguments from [_storedArgs] and +/// [_storedNamedArgs]. +class _InvocationForMatchedArguments extends Invocation { @override final Symbol memberName; @override @@ -163,10 +163,10 @@ class _InvocationForTypedArguments extends Invocation { @override final bool isSetter; - factory _InvocationForTypedArguments(Invocation invocation) { - if (_typedArgs.isEmpty && _typedNamedArgs.isEmpty) { + factory _InvocationForMatchedArguments(Invocation invocation) { + if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) { throw new StateError( - "_InvocationForTypedArguments called when no typed calls have been saved."); + "_InvocationForMatchedArguments called when no ArgMatchers have been saved."); } // Handle named arguments first, so that we can provide useful errors for @@ -176,10 +176,10 @@ class _InvocationForTypedArguments extends Invocation { var namedArguments = _reconstituteNamedArgs(invocation); var positionalArguments = _reconstitutePositionalArgs(invocation); - _typedArgs.clear(); - _typedNamedArgs.clear(); + _storedArgs.clear(); + _storedNamedArgs.clear(); - return new _InvocationForTypedArguments._( + return new _InvocationForMatchedArguments._( invocation.memberName, positionalArguments, namedArguments, @@ -188,53 +188,66 @@ class _InvocationForTypedArguments extends Invocation { invocation.isSetter); } - // Reconstitutes the named arguments in an invocation from [_typedNamedArgs]. + // Reconstitutes the named arguments in an invocation from + // [_storedNamedArgs]. // - // The namedArguments in [invocation] which are null should be represented - // by a stored value in [_typedNamedArgs]. The null presumably came from - // [typed]. + // The `namedArguments` in [invocation] which are null should be represented + // by a stored value in [_storedNamedArgs]. static Map _reconstituteNamedArgs(Invocation invocation) { var namedArguments = {}; - var _typedNamedArgSymbols = - _typedNamedArgs.keys.map((name) => new Symbol(name)); + var _storedNamedArgSymbols = + _storedNamedArgs.keys.map((name) => new Symbol(name)); // Iterate through [invocation]'s named args, validate them, and add them // to the return map. invocation.namedArguments.forEach((name, arg) { if (arg == null) { - if (!_typedNamedArgSymbols.contains(name)) { - // Incorrect usage of [typed], something like: - // `when(obj.fn(a: typed(any)))`. + if (!_storedNamedArgSymbols.contains(name)) { + // Incorrect usage of an ArgMatcher, something like: + // `when(obj.fn(a: any))`. + + // Clear things out for the next call. + _storedArgs.clear(); + _storedNamedArgs.clear(); throw new ArgumentError( - 'A typed argument was passed in as a named argument named "$name", ' - 'but did not pass a value for `named`. Each typed argument that is ' - 'passed as a named argument needs to specify the `named` argument. ' - 'For example: `when(obj.fn(x: typed(any, named: "x")))`.'); + 'An ArgumentMatcher (or a null value) was passed in as a named ' + 'argument named "$name", but was not passed a value for `named`. ' + 'Each ArgumentMatcher that is passed as a named argument needs ' + 'to specify the `named` argument, and each null value must be ' + 'wrapped in an ArgMatcher. For example: ' + '`when(obj.fn(x: anyNamed("x")))` or ' + '`when(obj.fn(x: argThat(isNull, named: "x")))`.'); } } else { - // Add each real named argument that was _not_ passed with [typed]. + // Add each real named argument (not wrapped in an ArgMatcher). namedArguments[name] = arg; } }); - // Iterate through the stored named args (stored with [typed]), validate - // them, and add them to the return map. - _typedNamedArgs.forEach((name, arg) { + // Iterate through the stored named args, validate them, and add them to + // the return map. + _storedNamedArgs.forEach((name, arg) { Symbol nameSymbol = new Symbol(name); if (!invocation.namedArguments.containsKey(nameSymbol)) { + // Clear things out for the next call. + _storedArgs.clear(); + _storedNamedArgs.clear(); throw new ArgumentError( - 'A typed argument was declared as named $name, but was not passed ' - 'as an argument named $name.\n\n' - 'BAD: when(obj.fn(typed(any, named: "a")))\n' - 'GOOD: when(obj.fn(a: typed(any, named: "a")))'); + 'An ArgumentMatcher was declared as named $name, but was not ' + 'passed as an argument named $name.\n\n' + 'BAD: when(obj.fn(anyNamed: "a")))\n' + 'GOOD: when(obj.fn(a: anyNamed: "a")))'); } if (invocation.namedArguments[nameSymbol] != null) { + // Clear things out for the next call. + _storedArgs.clear(); + _storedNamedArgs.clear(); throw new ArgumentError( - 'A typed argument was declared as named $name, but a different ' + 'An ArgumentMatcher was declared as named $name, but a different ' 'value (${invocation.namedArguments[nameSymbol]}) was passed as ' '$name.\n\n' - 'BAD: when(obj.fn(b: typed(any, name: "a")))\n' - 'GOOD: when(obj.fn(b: typed(any, name: "b")))'); + 'BAD: when(obj.fn(b: anyNamed("a")))\n' + 'GOOD: when(obj.fn(b: anyNamed("b")))'); } namedArguments[nameSymbol] = arg; }); @@ -246,30 +259,34 @@ class _InvocationForTypedArguments extends Invocation { var positionalArguments = []; var nullPositionalArguments = invocation.positionalArguments.where((arg) => arg == null); - if (_typedArgs.length != nullPositionalArguments.length) { + if (_storedArgs.length != nullPositionalArguments.length) { + // Clear things out for the next call. + _storedArgs.clear(); + _storedNamedArgs.clear(); throw new ArgumentError( - 'null arguments are not allowed alongside typed(); use ' - '"typed(eq(null))"'); + 'null arguments are not allowed alongside ArgMatchers; use ' + '"argThat(isNull)"'); } - int typedIndex = 0; + int storedIndex = 0; int positionalIndex = 0; - while (typedIndex < _typedArgs.length && + while (storedIndex < _storedArgs.length && positionalIndex < invocation.positionalArguments.length) { - var arg = _typedArgs[typedIndex]; + var arg = _storedArgs[storedIndex]; if (invocation.positionalArguments[positionalIndex] == null) { - // [typed] was used; add the [_ArgMatcher] given to [typed]. + // Add the [ArgMatcher] given to the argument matching helper. positionalArguments.add(arg); - typedIndex++; + storedIndex++; positionalIndex++; } else { - // [typed] was not used; add the [_ArgMatcher] from [invocation]. + // An argument matching helper was not used; add the [ArgMatcher] from + // [invocation]. positionalArguments .add(invocation.positionalArguments[positionalIndex]); positionalIndex++; } } while (positionalIndex < invocation.positionalArguments.length) { - // Some trailing non-[typed] arguments. + // Some trailing non-ArgMatcher arguments. positionalArguments.add(invocation.positionalArguments[positionalIndex]); positionalIndex++; } @@ -277,11 +294,11 @@ class _InvocationForTypedArguments extends Invocation { return positionalArguments; } - _InvocationForTypedArguments._(this.memberName, this.positionalArguments, + _InvocationForMatchedArguments._(this.memberName, this.positionalArguments, this.namedArguments, this.isGetter, this.isMethod, this.isSetter); } -named(var mock, {String name, int hashCode}) => mock +T named(T mock, {String name, int hashCode}) => mock .._givenName = name .._givenHashCode = hashCode; @@ -561,26 +578,35 @@ class ArgMatcher { } /// An argument matcher that matches any argument passed in "this" position. -/*ArgMatcher*/ get any => new ArgMatcher(anything, false); +Null get any => _registerMatcher(anything, false); + +Null anyNamed(String named) => _registerMatcher(anything, false, named: named); /// An argument matcher that matches any argument passed in "this" position, and /// captures the argument for later access with `captured`. -/*ArgMatcher*/ get captureAny => new ArgMatcher(anything, true); +Null get captureAny => _registerMatcher(anything, true); + +Null captureAnyNamed(String named) => + _registerMatcher(anything, true, named: named); /// An argument matcher that matches an argument that matches [matcher]. -/*ArgMatcher*/ argThat(Matcher matcher) => new ArgMatcher(matcher, false); +Null argThat(Matcher matcher, {String named}) => + _registerMatcher(matcher, false, named: named); /// An argument matcher that matches an argument that matches [matcher], and /// captures the argument for later access with `captured`. -/*ArgMatcher*/ captureThat(Matcher matcher) => new ArgMatcher(matcher, true); +Null captureThat(Matcher matcher, {String named}) => + _registerMatcher(matcher, true, named: named); + +@Deprecated('ArgMatchers no longer need to be wrapped in Mockito 3.0') +Null typed(ArgMatcher matcher, {String named}) => null; -/// A Strong-mode safe argument matcher that wraps other argument matchers. -/// See the README for a full explanation. -T typed(ArgMatcher matcher, {String named}) { +Null _registerMatcher(Matcher matcher, bool capture, {String named}) { + var argMatcher = new ArgMatcher(matcher, capture); if (named == null) { - _typedArgs.add(matcher); + _storedArgs.add(argMatcher); } else { - _typedNamedArgs[named] = matcher; + _storedNamedArgs[named] = argMatcher; } return null; } @@ -806,6 +832,6 @@ void resetMockitoState() { _untilCall = null; _verifyCalls.clear(); _capturedArgs.clear(); - _typedArgs.clear(); - _typedNamedArgs.clear(); + _storedArgs.clear(); + _storedNamedArgs.clear(); } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index a78e1cb93..f8496894b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: collection: '^1.1.0' matcher: '^0.12.0' meta: '^1.0.4' - test: '>=0.12.0 <0.13.0' + test: '>=0.12.25 <0.13.0' dev_dependencies: build_runner: ^0.7.11 diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 7a9a44f39..49d9d8d84 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -107,7 +107,7 @@ String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " "please instead use `verifyNever(...);`.)"; void main() { - RealClass mock; + MockedClass mock; setUp(() { mock = new MockedClass(); @@ -186,16 +186,15 @@ void main() { }); test("should mock method with multiple named args and matchers", () { - when(mock.methodWithTwoNamedArgs(typed(any), y: typed(any, named: 'y'))) + when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'))) .thenReturn("x y"); - when(mock.methodWithTwoNamedArgs(typed(any), z: typed(any, named: 'z'))) + when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) .thenReturn("x z"); expect(mock.methodWithTwoNamedArgs(42), isNull); expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); - when(mock.methodWithTwoNamedArgs(typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))) + when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'), z: anyNamed('z'))) .thenReturn("x y z"); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); }); @@ -223,11 +222,6 @@ void main() { expect(mock.hashCode, isNotNull); }); -// test("should n't mock toString", (){ -// when(mock.toString()).thenReturn("meow"); -// expect(mock.toString(), equals("meow")); -// }); - test("should have default toString when it is not mocked", () { expect(mock.toString(), equals("MockedClass")); }); @@ -283,6 +277,7 @@ void main() { expect(mock.methodWithNormalArgs(43), equals("43")); }); + // Error path tests. test("should throw if `when` is called while stubbing", () { expect(() { var responseHelper = () { @@ -294,6 +289,16 @@ void main() { }, throwsStateError); }); + test("should throw if `null` is passed alongside matchers", () { + expect(() { + when(mock.methodWithPositionalArgs(argThat(equals(42)), null)) + .thenReturn("99"); + }, throwsArgumentError); + + // but doesn't ruin later calls. + when(mock.methodWithNormalArgs(43)).thenReturn("43"); + }); + test("thenReturn throws if provided Future", () { expect( () => when(mock.methodReturningFuture()) @@ -322,99 +327,25 @@ void main() { expect(await mock.methodReturningStream().toList(), ["stub"]); }); - // [typed] API - test("should mock method with typed arg matchers", () { - when(mock.typeParameterizedFn(typed(any), typed(any))) - .thenReturn("A lot!"); - expect(mock.typeParameterizedFn([42], [43]), equals("A lot!")); - expect(mock.typeParameterizedFn([43], [44]), equals("A lot!")); - }); - - test("should mock method with an optional typed arg matcher", () { - when(mock.typeParameterizedFn(typed(any), typed(any), typed(any))) - .thenReturn("A lot!"); - expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); - }); - - test( - "should mock method with an optional typed arg matcher and an optional real arg", - () { - when(mock.typeParameterizedFn(typed(any), typed(any), [44], typed(any))) - .thenReturn("A lot!"); - expect( - mock.typeParameterizedFn([42], [43], [44], [45]), equals("A lot!")); - }); - - test("should mock method with only some typed arg matchers", () { - when(mock.typeParameterizedFn(typed(any), [43], typed(any))) - .thenReturn("A lot!"); - expect(mock.typeParameterizedFn([42], [43], [44]), equals("A lot!")); - when(mock.typeParameterizedFn(typed(any), [43])).thenReturn("A bunch!"); - expect(mock.typeParameterizedFn([42], [43]), equals("A bunch!")); - }); - - test("should throw when [typed] used alongside [null].", () { - expect(() => when(mock.typeParameterizedFn(typed(any), null, typed(any))), - throwsArgumentError); - expect(() => when(mock.typeParameterizedFn(typed(any), typed(any), null)), - throwsArgumentError); - }); - - test("should mock method when [typed] used alongside matched [null].", () { - when(mock.typeParameterizedFn( - typed(any), typed(argThat(equals(null))), typed(any))) - .thenReturn("A lot!"); - expect(mock.typeParameterizedFn([42], null, [44]), equals("A lot!")); - }); - - test("should mock method with named, typed arg matcher", () { - when(mock.typeParameterizedNamedFn(typed(any), [43], - y: typed(any, named: "y"))) - .thenReturn("A lot!"); - expect( - mock.typeParameterizedNamedFn([42], [43], y: [44]), equals("A lot!")); - }); - - test("should mock method with named, typed arg matcher and an arg matcher", - () { - when(mock.typeParameterizedNamedFn(typed(any), [43], - y: typed(any, named: "y"), - z: typed(argThat(contains(45)), named: 'z'))) - .thenReturn("A lot!"); - expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), - equals("A lot!")); - }); - - test("should mock method with named, typed arg matcher and a regular arg", - () { - when(mock.typeParameterizedNamedFn(typed(any), [43], - y: typed(any, named: "y"), z: [45])).thenReturn("A lot!"); - expect(mock.typeParameterizedNamedFn([42], [43], y: [44], z: [45]), - equals("A lot!")); - }); - - test("should throw when [typed] used as a named arg, without `named:`", () { - expect( - () => when( - mock.typeParameterizedNamedFn(typed(any), [43], y: typed(any))), - throwsArgumentError); + test("should throw if `null` is passed as a named arg", () { + expect(() { + when(mock.methodWithNamedArgs(argThat(equals(42)), y: null)) + .thenReturn("99"); + }, throwsArgumentError); }); - test("should throw when [typed] used as a positional arg, with `named:`", - () { - expect( - () => when(mock.typeParameterizedNamedFn( - typed(any), typed(any, named: "y"))), - throwsArgumentError); + test("should throw if named matcher is passed as a positional arg", () { + expect(() { + when(mock.methodWithNamedArgs(argThat(equals(42), named: "y"))) + .thenReturn("99"); + }, throwsArgumentError); }); - test( - "should throw when [typed] used as a named arg, with the wrong `named:`", - () { - expect( - () => when(mock.typeParameterizedNamedFn(typed(any), [43], - y: typed(any, named: "z"))), - throwsArgumentError); + test("should throw if named matcher is passed as the wrong name", () { + expect(() { + when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed("z"))) + .thenReturn("99"); + }, throwsArgumentError); }); }); @@ -459,21 +390,19 @@ void main() { test("waits for method with named args", () async { mock.methodWithNamedArgs(1, y: 2); - await untilCalled( - mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))); + await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); - verify(mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))) - .called(1); + verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); }); test("waits for method with two named args", () async { mock.methodWithTwoNamedArgs(1, y: 2, z: 3); - await untilCalled(mock.methodWithTwoNamedArgs(typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))); + await untilCalled(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))); - verify(mock.methodWithTwoNamedArgs(typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))) + verify(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))) .called(1); }); @@ -499,11 +428,11 @@ void main() { test("waits for function with named parameters", () async { mock.typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]); - await untilCalled(mock.typeParameterizedNamedFn(typed(any), typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))); + await untilCalled(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))); - verify(mock.typeParameterizedNamedFn(typed(any), typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))) + verify(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))) .called(1); }); @@ -568,26 +497,23 @@ void main() { test("waits for method with named args", () async { streamController.add(new CallMethodsEvent()); - verifyNever( - mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))); + verifyNever(mock.methodWithNamedArgs(any, y: anyNamed('y'))); - await untilCalled( - mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))); + await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); - verify(mock.methodWithNamedArgs(typed(any), y: typed(any, named: 'y'))) - .called(1); + verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); }); test("waits for method with two named args", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithTwoNamedArgs(typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))); + verifyNever(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))); - await untilCalled(mock.methodWithTwoNamedArgs(typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))); + await untilCalled(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))); - verify(mock.methodWithTwoNamedArgs(typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))) + verify(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))) .called(1); }); @@ -615,14 +541,14 @@ void main() { test("waits for function with named parameters", () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.typeParameterizedNamedFn(typed(any), typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))); + verifyNever(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))); - await untilCalled(mock.typeParameterizedNamedFn(typed(any), typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))); + await untilCalled(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))); - verify(mock.typeParameterizedNamedFn(typed(any), typed(any), - y: typed(any, named: 'y'), z: typed(any, named: 'z'))) + verify(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))) .called(1); }); @@ -779,22 +705,6 @@ void main() { }); verify(mock.setter = "A"); }); - - test("should verify method with typed arg matchers", () { - mock.typeParameterizedFn([42], [43]); - verify(mock.typeParameterizedFn(typed(any), typed(any))); - }); - - test("should verify method with argument capturer", () { - mock.typeParameterizedFn([50], [17]); - mock.typeParameterizedFn([100], [17]); - expect( - verify(mock.typeParameterizedFn(typed(captureAny), [17])).captured, - equals([ - [50], - [100] - ])); - }); }); group("verify() qualifies", () { @@ -1066,16 +976,16 @@ void main() { group("throwOnMissingStub", () { test("should throw when a mock was called without a matching stub", () { - throwOnMissingStub(mock as Mock); + throwOnMissingStub(mock); when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); expect( - () => (mock as MockedClass).methodWithoutArgs(), + () => (mock).methodWithoutArgs(), throwsNoSuchMethodError, ); }); test("should not throw when a mock was called with a matching stub", () { - throwOnMissingStub(mock as Mock); + throwOnMissingStub(mock); when(mock.methodWithoutArgs()).thenReturn("A"); expect(() => mock.methodWithoutArgs(), returnsNormally); }); From d607086a42d4a8436e82be226aad6bb89d63cd56 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 23 Apr 2018 08:35:02 -0700 Subject: [PATCH 099/607] Add two more compatible-friendly APIs to Mockito 3 (dart-lang/mockito#116) Add two more compatible-friendly APIs to Mockito 3 --- pkgs/mockito/lib/mockito.dart | 6 +- pkgs/mockito/lib/src/mock.dart | 8 +++ pkgs/mockito/upgrading-to-mockito-3.md | 88 ++++++++++++++++---------- 3 files changed, 66 insertions(+), 36 deletions(-) diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 8643a881b..90c73fd81 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -25,7 +25,6 @@ export 'src/mock.dart' captureAny, captureAnyNamed, captureThat, - typed, Answering, Expectation, PostExpectation, @@ -39,6 +38,11 @@ export 'src/mock.dart' VerificationResult, Verification, + // -- deprecated + typed, + typedArgThat, + typedCaptureThat, + // -- misc throwOnMissingStub, clearInteractions, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 0c4aaf47a..0e3977426 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -601,6 +601,14 @@ Null captureThat(Matcher matcher, {String named}) => @Deprecated('ArgMatchers no longer need to be wrapped in Mockito 3.0') Null typed(ArgMatcher matcher, {String named}) => null; +@Deprecated('Replace with `argThat`') +Null typedArgThat(Matcher matcher, {String named}) => + argThat(matcher, named: named); + +@Deprecated('Replace with `captureThat`') +Null typedCaptureThat(Matcher matcher, {String named}) => + captureThat(matcher, named: named); + Null _registerMatcher(Matcher matcher, bool capture, {String named}) { var argMatcher = new ArgMatcher(matcher, capture); if (named == null) { diff --git a/pkgs/mockito/upgrading-to-mockito-3.md b/pkgs/mockito/upgrading-to-mockito-3.md index aec6e320c..1511bd8d8 100644 --- a/pkgs/mockito/upgrading-to-mockito-3.md +++ b/pkgs/mockito/upgrading-to-mockito-3.md @@ -38,38 +38,13 @@ arguments): when(cat.eatFood(argThat(contains('mouse')), hungry: anyNamed('hungry')))... ``` -Here's a cheatsheet with examples of migrating different Mockito 2 API calls to -Mockito 3 API calls: - -## Table - -| Version | | -| --- | --------------------------------------------------------------------- | -| | **Using argument matchers as positional arguments** | -| 2.x | `when(obj.fn(typed(any)))...` | -| 3.0 | `when(obj.fn(any))...` | -| 2.x | `when(obj.fn(typed(argThat(equals(7)))))...` | -| 3.0 | `when(obj.fn(argThat(equals(7))))...` | -| | | -| | **Using argument matchers as named arguments** | -| 2.x | `when(obj.fn(foo: typed(any, named: 'foo')))...` | -| 3.0 | `when(obj.fn(foo: anyNamed('foo')))...` | -| 2.x | `when(obj.fn(foo: typed(argThat(equals(7)), named: 'foo')))...` | -| 3.0 | `when(obj.fn(foo: argThat(equals(7), named: 'foo')))...` | -| 2.x | `when(obj.fn(foo: typed(null, named: 'foo')))...` | -| 3.0 | `when(obj.fn(foo: argThat(isNull, named: 'foo')))...` | -| 2.x | `when(obj.fn(foo: typed(captureAny, named: 'foo')))...` | -| 3.0 | `when(obj.fn(foo: captureAnyNamed('foo')))...` | -| 2.x | `when(obj.fn(foo: typed(captureThat(equals(7)), named: 'foo')))...` | -| 3.0 | `when(obj.fn(foo: captureThat(equals(7), named: 'foo')))...` | - -## Mockito 3.0.0-beta - a backward-and-forward-compatible API +## Mockito 3.0.0-alpha+4 - a backward-and-forward-compatible API If you have a large codebase, it may be difficult to upgrade _all_ of your tests to the Mockito 3 API all at once. To provide an incremental upgrade path, upgrade to -Mockito 3.0.0-beta. +Mockito 3.0.0-alpha+4. -Mockito 3.0.0-beta is a very tiny release on top of Mockito 3.0.0-alpha+3, +Mockito 3.0.0-alpha+4 is a very tiny release on top of Mockito 3.0.0-alpha+3, which provides the Mockito 2.x API. In fact, here's the diff: ```dart @@ -84,8 +59,48 @@ which provides the Mockito 2.x API. In fact, here's the diff: +captureAnyNamed(String named) => typed(captureAny, named: named); ``` -Because this version uses the Mockito 2.x implementation, it is not really -compatible with Dart 2 runtime semantics. If you write: +## Table + +Here's a cheatsheet with examples of migrating different Mockito 2 API calls to +Mockito 3.0.0-alpha+4 API calls, and Mockito 3 API calls: + +| Version | | +| ------------- | ------------------------------------------------------------------- | +| | **Using argument matchers as positional arguments** | +| 2.x | `when(obj.fn(typed(any)))...` | +| 3.0.0-alpha+4 | `when(obj.fn(typed(any)))` | +| 3.0 | `when(obj.fn(any))...` | +| | | +| 2.x | `when(obj.fn(typed(argThat(equals(7)))))...` | +| 3.0.0-alpha+4 | `when(obj.fn(typed(argThat(equals(7)))))...` | +| 3.0 | `when(obj.fn(argThat(equals(7))))...` | +| | | +| | **Using argument matchers as named arguments** | +| 2.x | `when(obj.fn(foo: typed(any, named: 'foo')))...` | +| 3.0.0-alpha+4 | `when(obj.fn(foo: anyNamed('foo')))...` | +| 3.0 | `when(obj.fn(foo: anyNamed('foo')))...` | +| | | +| 2.x | `when(obj.fn(foo: typed(argThat(equals(7)), named: 'foo')))...` | +| 3.0.0-alpha+4 | `when(obj.fn(foo: typedArgThat(equals(7), named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: argThat(equals(7), named: 'foo')))...` | +| | | +| 2.x | `when(obj.fn(foo: typed(null, named: 'foo')))...` | +| 3.0.0-alpha+4 | `when(obj.fn(foo: typedArgThat(isNull, named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: argThat(isNull, named: 'foo')))...` | +| | | +| 2.x | `when(obj.fn(foo: typed(captureAny, named: 'foo')))...` | +| 3.0.0-alpha+4 | `when(obj.fn(foo: captureAnyNamed('foo')))...` | +| 3.0 | `when(obj.fn(foo: captureAnyNamed('foo')))...` | +| | | +| 2.x | `when(obj.fn(foo: typed(captureThat(equals(7)), named: 'foo')))...` | +| 3.0.0-alpha+4 | `when(obj.fn(foo: typedCaptureThat(equals(7), named: 'foo')))...` | +| 3.0 | `when(obj.fn(foo: captureThat(equals(7), named: 'foo')))...` | + +## Upgrade process from 2.x to 3. + +Because Mockito 3.0.0-alpha+4, the forward-and-backward-compatible release, +uses the Mockito 2.x implementation, it is not really compatible with Dart 2 +runtime semantics. If you write: ```dart when(cat.eatFood(argThat(contains('mouse')), hungry: any))... @@ -96,16 +111,15 @@ then Mockito is still passing an ArgumentMatcher as each argument to However, this version lets you incrementally upgrade your tests to the Mockito 3 API. Here's the workflow: -1. **Upgrade to `mockito: '^3.0.0-beta'` in your project's dependencies**, in +1. **Upgrade to `mockito: '^3.0.0-alpha+4'` in your project's dependencies**, in `pubspec.yaml`. This should not cause any tests to break. Commit this change. 2. **Change your usage of Mockito from the old API to the new API**, in as many chunks as you like. - In practice, this means searching for the different code - patterns in the table, and replacing the old API calls with the new. You can - typically just use your IDE's search, or `git grep`, and search for the - following text: + In practice, this means searching for the different code patterns in the + table, and replacing the old API calls with the new. You can typically just + use your IDE's search, or `git grep`, and search for the following text: * `when(` * `verify(` @@ -141,3 +155,7 @@ Mockito 3 API. Here's the workflow: 3. **Finally, upgrade to `mockito: '^3.0.0'`.** Make sure your tests all pass, and commit! + + In Mockito 3, `typed` still exists, but is deprecated, and a no-op, so + remove at your leisure. Additionally, `typedArgThat` and `typedCaptureThat` + still exist, but only redirect to `argThat` and `captureThat`, respectively. From a97dd5865ff11c0d3ded4ad9e2fb3b4420cc5d22 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 23 Apr 2018 19:21:56 -0700 Subject: [PATCH 100/607] Better verification error (dart-lang/mockito#121) Better verification error --- pkgs/mockito/lib/src/mock.dart | 14 +++++++++++++- pkgs/mockito/test/mockito_test.dart | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 0e3977426..99d1d6459 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -565,6 +565,9 @@ class _VerifyCall { inv.verified = true; }); } + + String toString() => + 'VerifyCall'; } class ArgMatcher { @@ -681,7 +684,16 @@ Verification get verify => _makeVerify(false); Verification _makeVerify(bool never) { if (_verifyCalls.isNotEmpty) { - throw new StateError(_verifyCalls.join()); + var message = 'Verification appears to be in progress.'; + if (_verifyCalls.length == 1) { + message = + '$message One verify call has been stored: ${_verifyCalls.single}'; + } else { + message = + '$message ${_verifyCalls.length} verify calls have been stored. ' + '[${_verifyCalls.first}, ..., ${_verifyCalls.last}]'; + } + throw new StateError(message); } _verificationInProgress = true; return (T mock) { diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 49d9d8d84..f377720bb 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -705,6 +705,29 @@ void main() { }); verify(mock.setter = "A"); }); + + test("should throw meaningful errors when verification is interrupted", () { + var badHelper = () => throw 'boo'; + try { + verify(mock.methodWithNamedArgs(42, y: badHelper())); + fail("verify call was expected to throw!"); + } catch (_) {} + // At this point, verification was interrupted, so + // `_verificationInProgress` is still `true`. Calling mock methods below + // adds items to `_verifyCalls`. + mock.methodWithNamedArgs(42, y: 17); + mock.methodWithNamedArgs(42, y: 17); + try { + verify(mock.methodWithNamedArgs(42, y: 17)); + fail("verify call was expected to throw!"); + } catch (e) { + expect(e, new isInstanceOf()); + expect( + e.message, + contains( + "Verification appears to be in progress. 2 verify calls have been stored.")); + } + }); }); group("verify() qualifies", () { From a0aa132e12a0acf4331d0e3616623b0d937a18f5 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 8 May 2018 09:57:52 -0700 Subject: [PATCH 101/607] Split tests --- pkgs/mockito/test/capture_test.dart | 98 ++++ pkgs/mockito/test/mockito_test.dart | 685 ----------------------- pkgs/mockito/test/until_called_test.dart | 306 ++++++++++ pkgs/mockito/test/verify_test.dart | 437 +++++++++++++++ 4 files changed, 841 insertions(+), 685 deletions(-) create mode 100644 pkgs/mockito/test/capture_test.dart create mode 100644 pkgs/mockito/test/until_called_test.dart create mode 100644 pkgs/mockito/test/verify_test.dart diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart new file mode 100644 index 000000000..13ab95a71 --- /dev/null +++ b/pkgs/mockito/test/capture_test.dart @@ -0,0 +1,98 @@ +// Copyright 2018 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:mockito/mockito.dart'; +import 'package:mockito/src/mock.dart' show resetMockitoState; +import 'package:test/test.dart'; + +class RealClass { + RealClass innerObj; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + set setter(String arg) { + throw new StateError('I must be mocked'); + } +} + +class MockedClass extends Mock implements RealClass {} + +void main() { + MockedClass mock; + + setUp(() { + mock = new MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group('capture', () { + test('captureAny should match anything', () { + mock.methodWithNormalArgs(42); + expect( + verify(mock.methodWithNormalArgs(typed(captureAny))).captured.single, + equals(42)); + }); + + test('captureThat should match some things', () { + mock.methodWithNormalArgs(42); + mock.methodWithNormalArgs(44); + mock.methodWithNormalArgs(43); + mock.methodWithNormalArgs(45); + expect( + verify(mock.methodWithNormalArgs(typed(captureThat(lessThan(44))))) + .captured, + equals([42, 43])); + }); + + test('should capture list arguments', () { + mock.methodWithListArgs([42]); + expect(verify(mock.methodWithListArgs(typed(captureAny))).captured.single, + equals([42])); + }); + + test('should capture multiple arguments', () { + mock.methodWithPositionalArgs(1, 2); + expect( + verify(mock.methodWithPositionalArgs( + typed(captureAny), typed(captureAny))) + .captured, + equals([1, 2])); + }); + + test('should capture with matching arguments', () { + mock.methodWithPositionalArgs(1); + mock.methodWithPositionalArgs(2, 3); + expect( + verify(mock.methodWithPositionalArgs( + typed(captureAny), typed(captureAny))) + .captured, + equals([2, 3])); + }); + + test('should capture multiple invocations', () { + mock.methodWithNormalArgs(1); + mock.methodWithNormalArgs(2); + expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, + equals([1, 2])); + }); + }); + + // TODO(srawlins): Test capturing in a setter. + // TODO(srawlins): Test capturing named arguments. +} diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index f377720bb..49946d751 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -30,49 +30,12 @@ class RealClass { String methodWithObjArgs(RealClass x) => "Real"; Future methodReturningFuture() => new Future.value("Real"); Stream methodReturningStream() => new Stream.fromIterable(["Real"]); - // "SpecialArgs" here means type-parameterized args. But that makes for a long - // method name. - String typeParameterizedFn(List w, List x, - [List y, List z]) => - "Real"; - // "SpecialNamedArgs" here means type-parameterized, named args. But that - // makes for a long method name. - String typeParameterizedNamedFn(List w, List x, - {List y, List z}) => - "Real"; String get getter => "Real"; set setter(String arg) { throw new StateError("I must be mocked"); } } -class CallMethodsEvent {} - -/// Listens on a stream and upon any event calls all methods in [RealClass]. -class RealClassController { - final RealClass _realClass; - - RealClassController( - this._realClass, StreamController streamController) { - streamController.stream.listen(_callAllMethods); - } - - Future _callAllMethods(_) async { - _realClass - ..methodWithoutArgs() - ..methodWithNormalArgs(1) - ..methodWithListArgs([1, 2]) - ..methodWithPositionalArgs(1, 2) - ..methodWithNamedArgs(1, y: 2) - ..methodWithTwoNamedArgs(1, y: 2, z: 3) - ..methodWithObjArgs(new RealClass()) - ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) - ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) - ..getter - ..setter = "A"; - } -} - abstract class Foo { String bar(); } @@ -349,654 +312,6 @@ void main() { }); }); - group("untilCalled", () { - StreamController streamController = - new StreamController.broadcast(); - - group("on methods already called", () { - test("waits for method without args", () async { - mock.methodWithoutArgs(); - - await untilCalled(mock.methodWithoutArgs()); - - verify(mock.methodWithoutArgs()).called(1); - }); - - test("waits for method with normal args", () async { - mock.methodWithNormalArgs(1); - - await untilCalled(mock.methodWithNormalArgs(typed(any))); - - verify(mock.methodWithNormalArgs(typed(any))).called(1); - }); - - test("waits for method with list args", () async { - mock.methodWithListArgs([1]); - - await untilCalled(mock.methodWithListArgs(typed(any))); - - verify(mock.methodWithListArgs(typed(any))).called(1); - }); - - test("waits for method with positional args", () async { - mock.methodWithPositionalArgs(1, 2); - - await untilCalled( - mock.methodWithPositionalArgs(typed(any), typed(any))); - - verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); - }); - - test("waits for method with named args", () async { - mock.methodWithNamedArgs(1, y: 2); - - await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); - - verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); - }); - - test("waits for method with two named args", () async { - mock.methodWithTwoNamedArgs(1, y: 2, z: 3); - - await untilCalled(mock.methodWithTwoNamedArgs(any, - y: anyNamed('y'), z: anyNamed('z'))); - - verify(mock.methodWithTwoNamedArgs(any, - y: anyNamed('y'), z: anyNamed('z'))) - .called(1); - }); - - test("waits for method with obj args", () async { - mock.methodWithObjArgs(new RealClass()); - - await untilCalled(mock.methodWithObjArgs(typed(any))); - - verify(mock.methodWithObjArgs(typed(any))).called(1); - }); - - test("waits for function with positional parameters", () async { - mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]); - - await untilCalled(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); - - verify(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))) - .called(1); - }); - - test("waits for function with named parameters", () async { - mock.typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]); - - await untilCalled(mock.typeParameterizedNamedFn(any, any, - y: anyNamed('y'), z: anyNamed('z'))); - - verify(mock.typeParameterizedNamedFn(any, any, - y: anyNamed('y'), z: anyNamed('z'))) - .called(1); - }); - - test("waits for getter", () async { - mock.getter; - - await untilCalled(mock.getter); - - verify(mock.getter).called(1); - }); - - test("waits for setter", () async { - mock.setter = "A"; - - await untilCalled(mock.setter = "A"); - - verify(mock.setter = "A").called(1); - }); - }); - - group("on methods not yet called", () { - setUp(() { - new RealClassController(mock, streamController); - }); - - test("waits for method without args", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithoutArgs()); - - await untilCalled(mock.methodWithoutArgs()); - - verify(mock.methodWithoutArgs()).called(1); - }); - - test("waits for method with normal args", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithNormalArgs(typed(any))); - - await untilCalled(mock.methodWithNormalArgs(typed(any))); - - verify(mock.methodWithNormalArgs(typed(any))).called(1); - }); - - test("waits for method with list args", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithListArgs(typed(any))); - - await untilCalled(mock.methodWithListArgs(typed(any))); - - verify(mock.methodWithListArgs(typed(any))).called(1); - }); - - test("waits for method with positional args", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithPositionalArgs(typed(any), typed(any))); - - await untilCalled( - mock.methodWithPositionalArgs(typed(any), typed(any))); - - verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); - }); - - test("waits for method with named args", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithNamedArgs(any, y: anyNamed('y'))); - - await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); - - verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); - }); - - test("waits for method with two named args", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithTwoNamedArgs(any, - y: anyNamed('y'), z: anyNamed('z'))); - - await untilCalled(mock.methodWithTwoNamedArgs(any, - y: anyNamed('y'), z: anyNamed('z'))); - - verify(mock.methodWithTwoNamedArgs(any, - y: anyNamed('y'), z: anyNamed('z'))) - .called(1); - }); - - test("waits for method with obj args", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithObjArgs(typed(any))); - - await untilCalled(mock.methodWithObjArgs(typed(any))); - - verify(mock.methodWithObjArgs(typed(any))).called(1); - }); - - test("waits for function with positional parameters", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); - - await untilCalled(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); - - verify(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))) - .called(1); - }); - - test("waits for function with named parameters", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.typeParameterizedNamedFn(any, any, - y: anyNamed('y'), z: anyNamed('z'))); - - await untilCalled(mock.typeParameterizedNamedFn(any, any, - y: anyNamed('y'), z: anyNamed('z'))); - - verify(mock.typeParameterizedNamedFn(any, any, - y: anyNamed('y'), z: anyNamed('z'))) - .called(1); - }); - - test("waits for getter", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.getter); - - await untilCalled(mock.getter); - - verify(mock.getter).called(1); - }); - - test("waits for setter", () async { - streamController.add(new CallMethodsEvent()); - verifyNever(mock.setter = "A"); - - await untilCalled(mock.setter = "A"); - - verify(mock.setter = "A").called(1); - }); - }); - }); - - group("verify()", () { - test("should verify method without args", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - - test("should verify method with normal args", () { - mock.methodWithNormalArgs(42); - expectFail( - "No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithNormalArgs(43)); - }); - verify(mock.methodWithNormalArgs(42)); - }); - - test("should mock method with positional args", () { - mock.methodWithPositionalArgs(42, 17); - expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithPositionalArgs(42)); - }); - expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(42, 17)\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithPositionalArgs(42, 18)); - }); - verify(mock.methodWithPositionalArgs(42, 17)); - }); - - test("should mock method with named args", () { - mock.methodWithNamedArgs(42, y: 17); - expectFail( - "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithNamedArgs(42)); - }); - expectFail( - "No matching calls. All calls: MockedClass.methodWithNamedArgs(42, {y: 17})\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithNamedArgs(42, y: 18)); - }); - verify(mock.methodWithNamedArgs(42, y: 17)); - }); - - test("should mock method with mock args", () { - var m1 = named(new MockedClass(), name: "m1"); - mock.methodWithObjArgs(m1); - expectFail( - "No matching calls. All calls: MockedClass.methodWithObjArgs(m1)\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithObjArgs(new MockedClass())); - }); - verify(mock.methodWithObjArgs(m1)); - }); - - test("should mock method with list args", () { - mock.methodWithListArgs([42]); - expectFail( - "No matching calls. All calls: MockedClass.methodWithListArgs([42])\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithListArgs([43])); - }); - verify(mock.methodWithListArgs([42])); - }); - - test("should mock method with argument matcher", () { - mock.methodWithNormalArgs(100); - expectFail( - "No matching calls. All calls: MockedClass.methodWithNormalArgs(100)\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))); - }); - verify( - mock.methodWithNormalArgs(typed(argThat(greaterThanOrEqualTo(100))))); - }); - - test("should mock method with argument capturer", () { - mock.methodWithNormalArgs(50); - mock.methodWithNormalArgs(100); - expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, - equals([50, 100])); - }); - - test("should mock method with argument matcher and capturer", () { - mock.methodWithNormalArgs(50); - mock.methodWithNormalArgs(100); - expect( - verify(mock.methodWithNormalArgs(typed(captureThat(greaterThan(75))))) - .captured - .single, - equals(100)); - expect( - verify(mock.methodWithNormalArgs(typed(captureThat(lessThan(75))))) - .captured - .single, - equals(50)); - }); - - test("should mock method with mix of argument matchers and real things", - () { - mock.methodWithPositionalArgs(100, 17); - expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThanOrEqualTo(100))), 18)); - }); - expectFail( - "No matching calls. All calls: MockedClass.methodWithPositionalArgs(100, 17)\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThan(100))), 17)); - }); - verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThanOrEqualTo(100))), 17)); - }); - - test("should mock getter", () { - mock.getter; - verify(mock.getter); - }); - - test("should mock setter", () { - mock.setter = "A"; - expectFail( - "No matching calls. All calls: MockedClass.setter==A\n" - "$noMatchingCallsFooter", () { - verify(mock.setter = "B"); - }); - verify(mock.setter = "A"); - }); - - test("should throw meaningful errors when verification is interrupted", () { - var badHelper = () => throw 'boo'; - try { - verify(mock.methodWithNamedArgs(42, y: badHelper())); - fail("verify call was expected to throw!"); - } catch (_) {} - // At this point, verification was interrupted, so - // `_verificationInProgress` is still `true`. Calling mock methods below - // adds items to `_verifyCalls`. - mock.methodWithNamedArgs(42, y: 17); - mock.methodWithNamedArgs(42, y: 17); - try { - verify(mock.methodWithNamedArgs(42, y: 17)); - fail("verify call was expected to throw!"); - } catch (e) { - expect(e, new isInstanceOf()); - expect( - e.message, - contains( - "Verification appears to be in progress. 2 verify calls have been stored.")); - } - }); - }); - - group("verify() qualifies", () { - group("unqualified as at least one", () { - test("zero fails", () { - expectFail( - "No matching calls (actually, no calls at all).\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithoutArgs()); - }); - }); - - test("one passes", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - - test("more than one passes", () { - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - }); - - group("expecting one call", () { - test("zero actual calls fails", () { - expectFail( - "No matching calls (actually, no calls at all).\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithoutArgs()).called(1); - }); - }); - - test("one actual call passes", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()).called(1); - }); - - test("more than one actual call fails", () { - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - expectFail("Expected: <1>\n Actual: <2>\nUnexpected number of calls\n", - () { - verify(mock.methodWithoutArgs()).called(1); - }); - }); - }); - - group("expecting more than two calls", () { - test("zero actual calls fails", () { - expectFail( - "No matching calls (actually, no calls at all).\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithoutArgs()).called(greaterThan(2)); - }); - }); - - test("one actual call fails", () { - mock.methodWithoutArgs(); - expectFail( - "Expected: a value greater than <2>\n" - " Actual: <1>\n" - " Which: is not a value greater than <2>\n" - "Unexpected number of calls\n", () { - verify(mock.methodWithoutArgs()).called(greaterThan(2)); - }); - }); - - test("three actual calls passes", () { - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()).called(greaterThan(2)); - }); - }); - - group("expecting zero calls", () { - test("zero actual calls passes", () { - expectFail( - "No matching calls (actually, no calls at all).\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithoutArgs()).called(0); - }); - }); - - test("one actual call fails", () { - mock.methodWithoutArgs(); - expectFail( - "Expected: <0>\n" - " Actual: <1>\n" - "Unexpected number of calls\n", () { - verify(mock.methodWithoutArgs()).called(0); - }); - }); - }); - - group("verifyNever", () { - test("zero passes", () { - verifyNever(mock.methodWithoutArgs()); - }); - - test("one fails", () { - mock.methodWithoutArgs(); - expectFail( - "Unexpected calls. All calls: MockedClass.methodWithoutArgs()", () { - verifyNever(mock.methodWithoutArgs()); - }); - }); - }); - - group("doesn't count already verified again", () { - test("fail case", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - expectFail( - "No matching calls. All calls: [VERIFIED] MockedClass.methodWithoutArgs()\n" - "$noMatchingCallsFooter", () { - verify(mock.methodWithoutArgs()); - }); - }); - - test("pass case", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - }); - }); - }); - - group("verifyZeroInteractions()", () { - test("never touched pass", () { - verifyZeroInteractions(mock); - }); - - test("any touch fails", () { - mock.methodWithoutArgs(); - expectFail( - "No interaction expected, but following found: MockedClass.methodWithoutArgs()", - () { - verifyZeroInteractions(mock); - }); - }); - - test("verifired call fails", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - expectFail( - "No interaction expected, but following found: [VERIFIED] MockedClass.methodWithoutArgs()", - () { - verifyZeroInteractions(mock); - }); - }); - }); - - group("verifyNoMoreInteractions()", () { - test("never touched pass", () { - verifyNoMoreInteractions(mock); - }); - - test("any unverified touch fails", () { - mock.methodWithoutArgs(); - expectFail( - "No more calls expected, but following found: MockedClass.methodWithoutArgs()", - () { - verifyNoMoreInteractions(mock); - }); - }); - - test("verified touch passes", () { - mock.methodWithoutArgs(); - verify(mock.methodWithoutArgs()); - verifyNoMoreInteractions(mock); - }); - - test("throws if given a real object", () { - expect( - () => verifyNoMoreInteractions(new RealClass()), throwsArgumentError); - }); - }); - - group("verifyInOrder()", () { - test("right order passes", () { - mock.methodWithoutArgs(); - mock.getter; - verifyInOrder([mock.methodWithoutArgs(), mock.getter]); - }); - - test("wrong order fails", () { - mock.methodWithoutArgs(); - mock.getter; - expectFail( - "Matching call #1 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.getter", - () { - verifyInOrder([mock.getter, mock.methodWithoutArgs()]); - }); - }); - - test("uncomplete fails", () { - mock.methodWithoutArgs(); - expectFail( - "Matching call #1 not found. All calls: MockedClass.methodWithoutArgs()", - () { - verifyInOrder([mock.methodWithoutArgs(), mock.getter]); - }); - }); - - test("methods can be called again and again", () { - mock.methodWithoutArgs(); - mock.getter; - mock.methodWithoutArgs(); - verifyInOrder( - [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); - }); - - test("methods can be called again and again - fail case", () { - mock.methodWithoutArgs(); - mock.methodWithoutArgs(); - mock.getter; - expectFail( - "Matching call #2 not found. All calls: MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), MockedClass.getter", - () { - verifyInOrder( - [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); - }); - }); - }); - - group("capture", () { - test("capture should work as captureOut", () { - mock.methodWithNormalArgs(42); - expect( - verify(mock.methodWithNormalArgs(typed(captureAny))).captured.single, - equals(42)); - }); - - test("should captureOut list arguments", () { - mock.methodWithListArgs([42]); - expect(verify(mock.methodWithListArgs(typed(captureAny))).captured.single, - equals([42])); - }); - - test("should captureOut multiple arguments", () { - mock.methodWithPositionalArgs(1, 2); - expect( - verify(mock.methodWithPositionalArgs( - typed(captureAny), typed(captureAny))) - .captured, - equals([1, 2])); - }); - - test("should captureOut with matching arguments", () { - mock.methodWithPositionalArgs(1); - mock.methodWithPositionalArgs(2, 3); - expect( - verify(mock.methodWithPositionalArgs( - typed(captureAny), typed(captureAny))) - .captured, - equals([2, 3])); - }); - - test("should captureOut multiple invocations", () { - mock.methodWithNormalArgs(1); - mock.methodWithNormalArgs(2); - expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, - equals([1, 2])); - }); - }); - group("throwOnMissingStub", () { test("should throw when a mock was called without a matching stub", () { throwOnMissingStub(mock); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart new file mode 100644 index 000000000..e4c17e37b --- /dev/null +++ b/pkgs/mockito/test/until_called_test.dart @@ -0,0 +1,306 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:async'; + +import 'package:mockito/mockito.dart'; +import 'package:mockito/src/mock.dart' show resetMockitoState; +import 'package:test/test.dart'; + +class RealClass { + RealClass innerObj; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithObjArgs(RealClass x) => 'Real'; + String typeParameterizedFn(List w, List x, + [List y, List z]) => + 'Real'; + String typeParameterizedNamedFn(List w, List x, + {List y, List z}) => + 'Real'; + String get getter => 'Real'; + set setter(String arg) { + throw new StateError('I must be mocked'); + } +} + +class CallMethodsEvent {} + +/// Listens on a stream and upon any event calls all methods in [RealClass]. +class RealClassController { + final RealClass _realClass; + + RealClassController( + this._realClass, StreamController streamController) { + streamController.stream.listen(_callAllMethods); + } + + Future _callAllMethods(_) async { + _realClass + ..methodWithoutArgs() + ..methodWithNormalArgs(1) + ..methodWithListArgs([1, 2]) + ..methodWithPositionalArgs(1, 2) + ..methodWithNamedArgs(1, y: 2) + ..methodWithTwoNamedArgs(1, y: 2, z: 3) + ..methodWithObjArgs(new RealClass()) + ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) + ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) + ..getter + ..setter = 'A'; + } +} + +class MockedClass extends Mock implements RealClass {} + +void main() { + MockedClass mock; + + setUp(() { + mock = new MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group('untilCalled', () { + StreamController streamController = + new StreamController.broadcast(); + + group('on methods already called', () { + test('waits for method without args', () async { + mock.methodWithoutArgs(); + + await untilCalled(mock.methodWithoutArgs()); + + verify(mock.methodWithoutArgs()).called(1); + }); + + test('waits for method with normal args', () async { + mock.methodWithNormalArgs(1); + + await untilCalled(mock.methodWithNormalArgs(typed(any))); + + verify(mock.methodWithNormalArgs(typed(any))).called(1); + }); + + test('waits for method with list args', () async { + mock.methodWithListArgs([1]); + + await untilCalled(mock.methodWithListArgs(typed(any))); + + verify(mock.methodWithListArgs(typed(any))).called(1); + }); + + test('waits for method with positional args', () async { + mock.methodWithPositionalArgs(1, 2); + + await untilCalled( + mock.methodWithPositionalArgs(typed(any), typed(any))); + + verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); + }); + + test('waits for method with named args', () async { + mock.methodWithNamedArgs(1, y: 2); + + await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); + + verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); + }); + + test('waits for method with two named args', () async { + mock.methodWithTwoNamedArgs(1, y: 2, z: 3); + + await untilCalled(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))); + + verify(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))) + .called(1); + }); + + test('waits for method with obj args', () async { + mock.methodWithObjArgs(new RealClass()); + + await untilCalled(mock.methodWithObjArgs(typed(any))); + + verify(mock.methodWithObjArgs(typed(any))).called(1); + }); + + test('waits for function with positional parameters', () async { + mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]); + + await untilCalled(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); + + verify(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))) + .called(1); + }); + + test('waits for function with named parameters', () async { + mock.typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]); + + await untilCalled(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))); + + verify(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))) + .called(1); + }); + + test('waits for getter', () async { + mock.getter; + + await untilCalled(mock.getter); + + verify(mock.getter).called(1); + }); + + test('waits for setter', () async { + mock.setter = 'A'; + + await untilCalled(mock.setter = 'A'); + + verify(mock.setter = 'A').called(1); + }); + }); + + group('on methods not yet called', () { + setUp(() { + new RealClassController(mock, streamController); + }); + + test('waits for method without args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithoutArgs()); + + await untilCalled(mock.methodWithoutArgs()); + + verify(mock.methodWithoutArgs()).called(1); + }); + + test('waits for method with normal args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithNormalArgs(typed(any))); + + await untilCalled(mock.methodWithNormalArgs(typed(any))); + + verify(mock.methodWithNormalArgs(typed(any))).called(1); + }); + + test('waits for method with list args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithListArgs(typed(any))); + + await untilCalled(mock.methodWithListArgs(typed(any))); + + verify(mock.methodWithListArgs(typed(any))).called(1); + }); + + test('waits for method with positional args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithPositionalArgs(typed(any), typed(any))); + + await untilCalled( + mock.methodWithPositionalArgs(typed(any), typed(any))); + + verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); + }); + + test('waits for method with named args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithNamedArgs(any, y: anyNamed('y'))); + + await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); + + verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); + }); + + test('waits for method with two named args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))); + + await untilCalled(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))); + + verify(mock.methodWithTwoNamedArgs(any, + y: anyNamed('y'), z: anyNamed('z'))) + .called(1); + }); + + test('waits for method with obj args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithObjArgs(typed(any))); + + await untilCalled(mock.methodWithObjArgs(typed(any))); + + verify(mock.methodWithObjArgs(typed(any))).called(1); + }); + + test('waits for function with positional parameters', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); + + await untilCalled(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); + + verify(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))) + .called(1); + }); + + test('waits for function with named parameters', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))); + + await untilCalled(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))); + + verify(mock.typeParameterizedNamedFn(any, any, + y: anyNamed('y'), z: anyNamed('z'))) + .called(1); + }); + + test('waits for getter', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.getter); + + await untilCalled(mock.getter); + + verify(mock.getter).called(1); + }); + + test('waits for setter', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.setter = 'A'); + + await untilCalled(mock.setter = 'A'); + + verify(mock.setter = 'A').called(1); + }); + }); + }); +} diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart new file mode 100644 index 000000000..27d77ed00 --- /dev/null +++ b/pkgs/mockito/test/verify_test.dart @@ -0,0 +1,437 @@ +// Copyright 2018 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:mockito/mockito.dart'; +import 'package:mockito/src/mock.dart' show resetMockitoState; +import 'package:test/test.dart'; + +class RealClass { + RealClass innerObj; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithObjArgs(RealClass x) => 'Real'; + String get getter => 'Real'; + set setter(String arg) { + throw new StateError('I must be mocked'); + } +} + +class MockedClass extends Mock implements RealClass {} + +expectFail(String expectedMessage, expectedToFail()) { + try { + expectedToFail(); + fail('It was expected to fail!'); + } catch (e) { + if (!(e is TestFailure)) { + throw e; + } else { + if (expectedMessage != e.message) { + throw new TestFailure('Failed, but with wrong message: ${e.message}'); + } + } + } +} + +String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' + 'please instead use `verifyNever(...);`.)'; + +void main() { + MockedClass mock; + + setUp(() { + mock = new MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group('verify', () { + test('should verify method without args', () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + + test('should verify method with normal args', () { + mock.methodWithNormalArgs(42); + expectFail( + 'No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNormalArgs(43)); + }); + verify(mock.methodWithNormalArgs(42)); + }); + + test('should mock method with positional args', () { + mock.methodWithPositionalArgs(42, 17); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithPositionalArgs(42, 17)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithPositionalArgs(42)); + }); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithPositionalArgs(42, 17)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithPositionalArgs(42, 18)); + }); + verify(mock.methodWithPositionalArgs(42, 17)); + }); + + test('should mock method with named args', () { + mock.methodWithNamedArgs(42, y: 17); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithNamedArgs(42, {y: 17})\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNamedArgs(42)); + }); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithNamedArgs(42, {y: 17})\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNamedArgs(42, y: 18)); + }); + verify(mock.methodWithNamedArgs(42, y: 17)); + }); + + test('should mock method with mock args', () { + var m1 = named(new MockedClass(), name: 'm1'); + mock.methodWithObjArgs(m1); + expectFail( + 'No matching calls. All calls: MockedClass.methodWithObjArgs(m1)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithObjArgs(new MockedClass())); + }); + verify(mock.methodWithObjArgs(m1)); + }); + + test('should mock method with list args', () { + mock.methodWithListArgs([42]); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithListArgs([42])\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithListArgs([43])); + }); + verify(mock.methodWithListArgs([42])); + }); + + test('should mock method with argument matcher', () { + mock.methodWithNormalArgs(100); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithNormalArgs(100)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))); + }); + verify( + mock.methodWithNormalArgs(typed(argThat(greaterThanOrEqualTo(100))))); + }); + + test('should mock method with mix of argument matchers and real things', + () { + mock.methodWithPositionalArgs(100, 17); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithPositionalArgs(100, 17)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithPositionalArgs( + typed(argThat(greaterThanOrEqualTo(100))), 18)); + }); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithPositionalArgs(100, 17)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithPositionalArgs( + typed(argThat(greaterThan(100))), 17)); + }); + verify(mock.methodWithPositionalArgs( + typed(argThat(greaterThanOrEqualTo(100))), 17)); + }); + + test('should mock getter', () { + mock.getter; + verify(mock.getter); + }); + + test('should mock setter', () { + mock.setter = 'A'; + expectFail( + 'No matching calls. All calls: MockedClass.setter==A\n' + '$noMatchingCallsFooter', () { + verify(mock.setter = 'B'); + }); + verify(mock.setter = 'A'); + }); + + test('should throw meaningful errors when verification is interrupted', () { + var badHelper = () => throw 'boo'; + try { + verify(mock.methodWithNamedArgs(42, y: badHelper())); + fail('verify call was expected to throw!'); + } catch (_) {} + // At this point, verification was interrupted, so + // `_verificationInProgress` is still `true`. Calling mock methods below + // adds items to `_verifyCalls`. + mock.methodWithNamedArgs(42, y: 17); + mock.methodWithNamedArgs(42, y: 17); + try { + verify(mock.methodWithNamedArgs(42, y: 17)); + fail('verify call was expected to throw!'); + } catch (e) { + expect(e, new isInstanceOf()); + expect( + e.message, + contains('Verification appears to be in progress. ' + '2 verify calls have been stored.')); + } + }); + }); + + group('verify qualifies', () { + group('unqualified as at least one', () { + test('zero fails', () { + expectFail( + 'No matching calls (actually, no calls at all).\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithoutArgs()); + }); + }); + + test('one passes', () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + + test('more than one passes', () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + }); + + group('expecting one call', () { + test('zero actual calls fails', () { + expectFail( + 'No matching calls (actually, no calls at all).\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithoutArgs()).called(1); + }); + }); + + test('one actual call passes', () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()).called(1); + }); + + test('more than one actual call fails', () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + expectFail('Expected: <1>\n Actual: <2>\nUnexpected number of calls\n', + () { + verify(mock.methodWithoutArgs()).called(1); + }); + }); + }); + + group('expecting more than two calls', () { + test('zero actual calls fails', () { + expectFail( + 'No matching calls (actually, no calls at all).\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithoutArgs()).called(greaterThan(2)); + }); + }); + + test('one actual call fails', () { + mock.methodWithoutArgs(); + expectFail( + 'Expected: a value greater than <2>\n' + ' Actual: <1>\n' + ' Which: is not a value greater than <2>\n' + 'Unexpected number of calls\n', () { + verify(mock.methodWithoutArgs()).called(greaterThan(2)); + }); + }); + + test('three actual calls passes', () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()).called(greaterThan(2)); + }); + }); + + group('expecting zero calls', () { + test('zero actual calls passes', () { + expectFail( + 'No matching calls (actually, no calls at all).\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithoutArgs()).called(0); + }); + }); + + test('one actual call fails', () { + mock.methodWithoutArgs(); + expectFail( + 'Expected: <0>\n' + ' Actual: <1>\n' + 'Unexpected number of calls\n', () { + verify(mock.methodWithoutArgs()).called(0); + }); + }); + }); + + group('verifyNever', () { + test('zero passes', () { + verifyNever(mock.methodWithoutArgs()); + }); + + test('one fails', () { + mock.methodWithoutArgs(); + expectFail( + 'Unexpected calls. All calls: MockedClass.methodWithoutArgs()', () { + verifyNever(mock.methodWithoutArgs()); + }); + }); + }); + + group('does not count already verified again', () { + test('fail case', () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + expectFail( + 'No matching calls. ' + 'All calls: [VERIFIED] MockedClass.methodWithoutArgs()\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithoutArgs()); + }); + }); + + test('pass case', () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + }); + }); + }); + + group('verifyZeroInteractions', () { + test('never touched pass', () { + verifyZeroInteractions(mock); + }); + + test('any touch fails', () { + mock.methodWithoutArgs(); + expectFail( + 'No interaction expected, but following found: ' + 'MockedClass.methodWithoutArgs()', () { + verifyZeroInteractions(mock); + }); + }); + + test('verified call fails', () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + expectFail( + 'No interaction expected, but following found: ' + '[VERIFIED] MockedClass.methodWithoutArgs()', () { + verifyZeroInteractions(mock); + }); + }); + }); + + group('verifyNoMoreInteractions', () { + test('never touched pass', () { + verifyNoMoreInteractions(mock); + }); + + test('any unverified touch fails', () { + mock.methodWithoutArgs(); + expectFail( + 'No more calls expected, but following found: ' + 'MockedClass.methodWithoutArgs()', () { + verifyNoMoreInteractions(mock); + }); + }); + + test('verified touch passes', () { + mock.methodWithoutArgs(); + verify(mock.methodWithoutArgs()); + verifyNoMoreInteractions(mock); + }); + + test('throws if given a real object', () { + expect( + () => verifyNoMoreInteractions(new RealClass()), throwsArgumentError); + }); + }); + + group('verifyInOrder', () { + test('right order passes', () { + mock.methodWithoutArgs(); + mock.getter; + verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + }); + + test('wrong order fails', () { + mock.methodWithoutArgs(); + mock.getter; + expectFail( + 'Matching call #1 not found. All calls: ' + 'MockedClass.methodWithoutArgs(), MockedClass.getter', () { + verifyInOrder([mock.getter, mock.methodWithoutArgs()]); + }); + }); + + test('incomplete fails', () { + mock.methodWithoutArgs(); + expectFail( + 'Matching call #1 not found. All calls: ' + 'MockedClass.methodWithoutArgs()', () { + verifyInOrder([mock.methodWithoutArgs(), mock.getter]); + }); + }); + + test('methods can be called again and again', () { + mock.methodWithoutArgs(); + mock.getter; + mock.methodWithoutArgs(); + verifyInOrder( + [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + }); + + test('methods can be called again and again - fail case', () { + mock.methodWithoutArgs(); + mock.methodWithoutArgs(); + mock.getter; + expectFail( + 'Matching call #2 not found. All calls: ' + 'MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), ' + 'MockedClass.getter', () { + verifyInOrder( + [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); + }); + }); + }); +} From 7d78fcd8b386be12b49955affdb3fce0eff68658 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 15 May 2018 09:56:38 -0700 Subject: [PATCH 102/607] Improve verify failure message; more tests (dart-lang/mockito#131) Improve verify failure message; more tests --- pkgs/mockito/lib/src/mock.dart | 3 +- pkgs/mockito/test/verify_test.dart | 51 +++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 99d1d6459..7bb81a5fc 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -464,7 +464,8 @@ class RealCall { .map((key) => "${_symbolToString(key)}: ${invocation.namedArguments[key]}") .join(", "); - args += ", {$namedArgs}"; + if (args.isNotEmpty) args += ", "; + args += "{$namedArgs}"; } var method = _symbolToString(invocation.memberName); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 27d77ed00..eab950af6 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -23,6 +23,7 @@ class RealClass { String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithOnlyNamedArgs({int y, int z}) => 'Real'; String methodWithObjArgs(RealClass x) => 'Real'; String get getter => 'Real'; set setter(String arg) { @@ -71,11 +72,6 @@ void main() { test('should verify method with normal args', () { mock.methodWithNormalArgs(42); - expectFail( - 'No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n' - '$noMatchingCallsFooter', () { - verify(mock.methodWithNormalArgs(43)); - }); verify(mock.methodWithNormalArgs(42)); }); @@ -183,6 +179,51 @@ void main() { verify(mock.setter = 'A'); }); + test( + 'should fail when no matching call is found, ' + 'and there are no unmatched calls', () { + expectFail( + 'No matching calls (actually, no calls at all).\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNormalArgs(43)); + }); + }); + + test( + 'should fail when no matching call is found, ' + 'and there is one unmatched call', () { + mock.methodWithNormalArgs(42); + expectFail( + 'No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNormalArgs(43)); + }); + }); + + test( + 'should fail when no matching call is found, ' + 'and there are multiple unmatched calls', () { + mock.methodWithNormalArgs(41); + mock.methodWithNormalArgs(42); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithNormalArgs(41), MockedClass.methodWithNormalArgs(42)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNormalArgs(43)); + }); + }); + + test( + 'should fail when no matching call is found, ' + 'and unmatched calls have only named args', () { + mock.methodWithOnlyNamedArgs(y: 1); + expectFail( + 'No matching calls. All calls: MockedClass.methodWithOnlyNamedArgs({y: 1})\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithOnlyNamedArgs()); + }); + }); + test('should throw meaningful errors when verification is interrupted', () { var badHelper = () => throw 'boo'; try { From 146d9ab1fba85b4f13bf6846f85a7d877189f3fe Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 15 May 2018 14:18:24 -0700 Subject: [PATCH 103/607] Better multiline call string representations --- pkgs/mockito/lib/src/mock.dart | 60 +++++++++---- pkgs/mockito/test/verify_test.dart | 137 +++++++++++++++++++++-------- 2 files changed, 145 insertions(+), 52 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 7bb81a5fc..b8f6ab58c 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -133,6 +133,18 @@ class Mock { @override String toString() => _givenName != null ? _givenName : runtimeType.toString(); + + String _realCallsToString() { + var stringRepresentations = _realCalls.map((call) => call.toString()); + if (stringRepresentations.any((s) => s.contains('\n'))) { + // As each call contains newlines, put each on its own line, for better + // readability. + return stringRepresentations.join(',\n'); + } else { + // A compact String should be perfect. + return stringRepresentations.join(', '); + } + } } typedef CallPair _ReturnsCannedResponse(); @@ -456,32 +468,50 @@ class RealCall { @override String toString() { + var argString = ''; var args = invocation.positionalArguments - .map((v) => v == null ? "null" : v.toString()) - .join(", "); + .map((v) => v == null ? "null" : v.toString()); + if (args.any((arg) => arg.contains('\n'))) { + // As one or more arg contains newlines, put each on its own line, and + // indent each, for better readability. + argString += '\n' + + args + .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')) + .join(',\n'); + } else { + // A compact String should be perfect. + argString += args.join(', '); + } if (invocation.namedArguments.isNotEmpty) { - var namedArgs = invocation.namedArguments.keys - .map((key) => - "${_symbolToString(key)}: ${invocation.namedArguments[key]}") - .join(", "); - if (args.isNotEmpty) args += ", "; - args += "{$namedArgs}"; + if (argString.isNotEmpty) argString += ', '; + var namedArgs = invocation.namedArguments.keys.map((key) => + '${_symbolToString(key)}: ${invocation.namedArguments[key]}'); + if (namedArgs.any((arg) => arg.contains('\n'))) { + // As one or more arg contains newlines, put each on its own line, and + // indent each, for better readability. + namedArgs = namedArgs + .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')); + argString += '{\n${namedArgs.join(',\n')}}'; + } else { + // A compact String should be perfect. + argString += '{${namedArgs.join(', ')}}'; + } } var method = _symbolToString(invocation.memberName); if (invocation.isMethod) { - method = "$method($args)"; + method = '$method($argString)'; } else if (invocation.isGetter) { - method = "$method"; + method = '$method'; } else if (invocation.isSetter) { - method = "$method=$args"; + method = '$method=$argString'; } else { throw new StateError( 'Invocation should be getter, setter or a method call.'); } - var verifiedText = verified ? "[VERIFIED] " : ""; - return "$verifiedText$mock.$method"; + var verifiedText = verified ? '[VERIFIED] ' : ''; + return '$verifiedText$mock.$method'; } // This used to use MirrorSystem, which cleans up the Symbol() wrapper. @@ -551,7 +581,7 @@ class _VerifyCall { if (mock._realCalls.isEmpty) { message = "No matching calls (actually, no calls at all)."; } else { - var otherCalls = mock._realCalls.join(", "); + var otherCalls = mock._realCallsToString(); message = "No matching calls. All calls: $otherCalls"; } fail("$message\n" @@ -559,7 +589,7 @@ class _VerifyCall { "`verifyNever(...);`.)"); } if (never && matchingInvocations.isNotEmpty) { - var calls = mock._realCalls.join(", "); + var calls = mock._realCallsToString(); fail("Unexpected calls. All calls: $calls"); } matchingInvocations.forEach((inv) { diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index eab950af6..9c1bcf1c4 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -21,6 +21,7 @@ class RealClass { String methodWithoutArgs() => 'Real'; String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; + String methodWithOptionalArg([int x]) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; String methodWithNamedArgs(int x, {int y}) => 'Real'; String methodWithOnlyNamedArgs({int y, int z}) => 'Real'; @@ -29,6 +30,24 @@ class RealClass { set setter(String arg) { throw new StateError('I must be mocked'); } + + String methodWithLongArgs(LongToString a, LongToString b, + {LongToString c, LongToString d}) => + 'Real'; +} + +class LongToString { + final List aList; + final Map aMap; + final String aString; + + LongToString(this.aList, this.aMap, this.aString); + + String toString() => 'LongToString<\n' + ' aList: $aList\n' + ' aMap: $aMap\n' + ' aString: $aString\n' + '>'; } class MockedClass extends Mock implements RealClass {} @@ -179,9 +198,32 @@ void main() { verify(mock.setter = 'A'); }); - test( - 'should fail when no matching call is found, ' - 'and there are no unmatched calls', () { + test('should throw meaningful errors when verification is interrupted', () { + var badHelper = () => throw 'boo'; + try { + verify(mock.methodWithNamedArgs(42, y: badHelper())); + fail('verify call was expected to throw!'); + } catch (_) {} + // At this point, verification was interrupted, so + // `_verificationInProgress` is still `true`. Calling mock methods below + // adds items to `_verifyCalls`. + mock.methodWithNamedArgs(42, y: 17); + mock.methodWithNamedArgs(42, y: 17); + try { + verify(mock.methodWithNamedArgs(42, y: 17)); + fail('verify call was expected to throw!'); + } catch (e) { + expect(e, new isInstanceOf()); + expect( + e.message, + contains('Verification appears to be in progress. ' + '2 verify calls have been stored.')); + } + }); + }); + + group('verify should fail when no matching call is found', () { + test('and there are no unmatched calls', () { expectFail( 'No matching calls (actually, no calls at all).\n' '$noMatchingCallsFooter', () { @@ -189,9 +231,7 @@ void main() { }); }); - test( - 'should fail when no matching call is found, ' - 'and there is one unmatched call', () { + test('and there is one unmatched call', () { mock.methodWithNormalArgs(42); expectFail( 'No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n' @@ -200,52 +240,36 @@ void main() { }); }); - test( - 'should fail when no matching call is found, ' - 'and there are multiple unmatched calls', () { + test('and there is one unmatched call without args', () { + mock.methodWithOptionalArg(); + expectFail( + 'No matching calls. All calls: MockedClass.methodWithOptionalArg()\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithOptionalArg(43)); + }); + }); + + test('and there are multiple unmatched calls', () { mock.methodWithNormalArgs(41); mock.methodWithNormalArgs(42); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithNormalArgs(41), MockedClass.methodWithNormalArgs(42)\n' + 'MockedClass.methodWithNormalArgs(41), ' + 'MockedClass.methodWithNormalArgs(42)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithNormalArgs(43)); }); }); - test( - 'should fail when no matching call is found, ' - 'and unmatched calls have only named args', () { + test('and unmatched calls have only named args', () { mock.methodWithOnlyNamedArgs(y: 1); expectFail( - 'No matching calls. All calls: MockedClass.methodWithOnlyNamedArgs({y: 1})\n' + 'No matching calls. All calls: ' + 'MockedClass.methodWithOnlyNamedArgs({y: 1})\n' '$noMatchingCallsFooter', () { verify(mock.methodWithOnlyNamedArgs()); }); }); - - test('should throw meaningful errors when verification is interrupted', () { - var badHelper = () => throw 'boo'; - try { - verify(mock.methodWithNamedArgs(42, y: badHelper())); - fail('verify call was expected to throw!'); - } catch (_) {} - // At this point, verification was interrupted, so - // `_verificationInProgress` is still `true`. Calling mock methods below - // adds items to `_verifyCalls`. - mock.methodWithNamedArgs(42, y: 17); - mock.methodWithNamedArgs(42, y: 17); - try { - verify(mock.methodWithNamedArgs(42, y: 17)); - fail('verify call was expected to throw!'); - } catch (e) { - expect(e, new isInstanceOf()); - expect( - e.message, - contains('Verification appears to be in progress. ' - '2 verify calls have been stored.')); - } - }); }); group('verify qualifies', () { @@ -475,4 +499,43 @@ void main() { }); }); }); + + group('multiline toStrings on objects', () { + test( + '"No matching calls" message visibly separates unmatched calls, ' + 'if an arg\'s string representations is multiline', () { + mock.methodWithLongArgs(new LongToString([1, 2], {1: 'a', 2: 'b'}, 'c'), + new LongToString([4, 5], {3: 'd', 4: 'e'}, 'f')); + mock.methodWithLongArgs(null, null, + c: new LongToString([5, 6], {5: 'g', 6: 'h'}, 'i'), + d: new LongToString([7, 8], {7: 'j', 8: 'k'}, 'l')); + expectFail( + 'No matching calls. All calls: ' + 'MockedClass.methodWithLongArgs(\n' + ' LongToString<\n' + ' aList: [1, 2]\n' + ' aMap: {1: a, 2: b}\n' + ' aString: c\n' + ' >,\n' + ' LongToString<\n' + ' aList: [4, 5]\n' + ' aMap: {3: d, 4: e}\n' + ' aString: f\n' + ' >),\n' + 'MockedClass.methodWithLongArgs(null, null, {\n' + ' c: LongToString<\n' + ' aList: [5, 6]\n' + ' aMap: {5: g, 6: h}\n' + ' aString: i\n' + ' >,\n' + ' d: LongToString<\n' + ' aList: [7, 8]\n' + ' aMap: {7: j, 8: k}\n' + ' aString: l\n' + ' >})\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNormalArgs(43)); + }); + }); + }); } From de7e20efc4a0932a5ab61e53b9da4d45b94481bc Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 16 May 2018 15:25:02 -0700 Subject: [PATCH 104/607] Support nSM Forwarding (dart-lang/mockito#133) Support nSM Forwarding --- pkgs/mockito/CHANGELOG.md | 49 +++++++++++++++++++++++++++++ pkgs/mockito/lib/src/mock.dart | 32 +++++++++---------- pkgs/mockito/test/capture_test.dart | 7 ++++- pkgs/mockito/test/mockito_test.dart | 33 +++++-------------- pkgs/mockito/test/utils.dart | 21 +++++++++++++ pkgs/mockito/test/verify_test.dart | 14 +++++++-- 6 files changed, 109 insertions(+), 47 deletions(-) create mode 100644 pkgs/mockito/test/utils.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 49ce5dbb6..fc74b0730 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,52 @@ +## 3.0.0-alpha+5 + +* Fix compatibility with new [noSuchMethod Forwarding] feature of Dart 2. This + is thankfully a mostly backwards-compatible change. This means that this + version of Mockito should continue to work: + + * with Dart `>=2.0.0-dev.16.0`, + * with Dart 2 runtime semantics (i.e. with `dart --preview-dart-2`, or with + Flutter Beta 3), and + * with the new noSuchMethod Forwarding feature, when it lands in CFE, and when + it lands in DDC. + + This change, when combined with noSuchMethod Forwarding, will break a few + code paths which do not seem to be frequently used. Two examples: + + ```dart + class A { + int fn(int a, [int b]) => 7; + } + class MockA extends Mock implements A {} + + var a = new MockA(); + when(a.fn(typed(any), typed(any))).thenReturn(0); + print(a.fn(1)); + ``` + + This used to print `null`, because only one argument was passed, which did + not match the two-argument stub. Now it will print `0`, as the real call + contains a value for both the required argument, and the optional argument. + + ```dart + a.fn(1); + a.fn(2, 3); + print(verify(a.fn(typed(captureAny), typed(captureAny))).captured); + ``` + + This used to print `[2, 3]`, because only the second call matched the `verify` + call. Now, it will print `[1, null, 2, 3]`, as both real calls contain a value + for both the required argument, and the optional argument. + +[noSuchMethod Forwarding]: https://github.com/dart-lang/sdk/blob/master/docs/language/informal/nosuchmethod-forwarding.md + +## 3.0.0-alpha+4 + +* Introduce a backward-and-forward compatible API to help users migrate to + Mockito 3. See more details in the [upgrading-to-mockito-3] doc. + +[upgrading-to-mockito-3]: https://github.com/dart-lang/mockito/blob/master/upgrading-to-mockito-3.md + ## 3.0.0-alpha+3 * `thenReturn` and `thenAnswer` now support generics and infer the correct diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index b8f6ab58c..34978be6c 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -215,20 +215,10 @@ class _InvocationForMatchedArguments extends Invocation { invocation.namedArguments.forEach((name, arg) { if (arg == null) { if (!_storedNamedArgSymbols.contains(name)) { - // Incorrect usage of an ArgMatcher, something like: - // `when(obj.fn(a: any))`. - - // Clear things out for the next call. - _storedArgs.clear(); - _storedNamedArgs.clear(); - throw new ArgumentError( - 'An ArgumentMatcher (or a null value) was passed in as a named ' - 'argument named "$name", but was not passed a value for `named`. ' - 'Each ArgumentMatcher that is passed as a named argument needs ' - 'to specify the `named` argument, and each null value must be ' - 'wrapped in an ArgMatcher. For example: ' - '`when(obj.fn(x: anyNamed("x")))` or ' - '`when(obj.fn(x: argThat(isNull, named: "x")))`.'); + // Either this is a parameter with default value `null`, or a `null` + // argument was passed, or an unnamed ArgMatcher was used. Just use + // `null`. + namedArguments[name] = null; } } else { // Add each real named argument (not wrapped in an ArgMatcher). @@ -271,13 +261,19 @@ class _InvocationForMatchedArguments extends Invocation { var positionalArguments = []; var nullPositionalArguments = invocation.positionalArguments.where((arg) => arg == null); - if (_storedArgs.length != nullPositionalArguments.length) { - // Clear things out for the next call. + if (_storedArgs.length > nullPositionalArguments.length) { + // More _positional_ ArgMatchers were stored than were actually passed as + // positional arguments. The only way this call was parsed and resolved is + // if an ArgMatcher was passed as a named argument, but without a name, + // and thus stored in [_storedArgs], something like + // `when(obj.fn(a: any))`. _storedArgs.clear(); _storedNamedArgs.clear(); throw new ArgumentError( - 'null arguments are not allowed alongside ArgMatchers; use ' - '"argThat(isNull)"'); + 'An argument matcher (like `any`) was used as a named argument, but ' + 'did not use a Mockito "named" API. Each argument matcher that is ' + 'used as a named argument needs to specify the name of the argument ' + 'it is being used in. For example: `when(obj.fn(x: anyNamed("x")))`.'); } int storedIndex = 0; int positionalIndex = 0; diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 13ab95a71..8ca3606c9 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -16,6 +16,8 @@ import 'package:mockito/mockito.dart'; import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:test/test.dart'; +import 'utils.dart'; + class RealClass { RealClass innerObj; String methodWithNormalArgs(int x) => 'Real'; @@ -31,6 +33,8 @@ class MockedClass extends Mock implements RealClass {} void main() { MockedClass mock; + var isNsmForwarding = assessNsmForwarding(); + setUp(() { mock = new MockedClass(); }); @@ -78,11 +82,12 @@ void main() { test('should capture with matching arguments', () { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); + var expectedCaptures = isNsmForwarding ? [1, null, 2, 3] : [2, 3]; expect( verify(mock.methodWithPositionalArgs( typed(captureAny), typed(captureAny))) .captured, - equals([2, 3])); + equals(expectedCaptures)); }); test('should capture multiple invocations', () { diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 49946d751..579474056 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -19,6 +19,8 @@ import 'package:mockito/src/mock.dart' show resetMockitoState, throwOnMissingStub; import 'package:test/test.dart'; +import 'utils.dart'; + class RealClass { RealClass innerObj; String methodWithoutArgs() => "Real"; @@ -72,6 +74,8 @@ String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " void main() { MockedClass mock; + var isNsmForwarding = assessNsmForwarding(); + setUp(() { mock = new MockedClass(); }); @@ -153,7 +157,10 @@ void main() { .thenReturn("x y"); when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) .thenReturn("x z"); - expect(mock.methodWithTwoNamedArgs(42), isNull); + if (isNsmForwarding) + expect(mock.methodWithTwoNamedArgs(42), "x z"); + else + expect(mock.methodWithTwoNamedArgs(42), isNull); expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); @@ -252,16 +259,6 @@ void main() { }, throwsStateError); }); - test("should throw if `null` is passed alongside matchers", () { - expect(() { - when(mock.methodWithPositionalArgs(argThat(equals(42)), null)) - .thenReturn("99"); - }, throwsArgumentError); - - // but doesn't ruin later calls. - when(mock.methodWithNormalArgs(43)).thenReturn("43"); - }); - test("thenReturn throws if provided Future", () { expect( () => when(mock.methodReturningFuture()) @@ -290,20 +287,6 @@ void main() { expect(await mock.methodReturningStream().toList(), ["stub"]); }); - test("should throw if `null` is passed as a named arg", () { - expect(() { - when(mock.methodWithNamedArgs(argThat(equals(42)), y: null)) - .thenReturn("99"); - }, throwsArgumentError); - }); - - test("should throw if named matcher is passed as a positional arg", () { - expect(() { - when(mock.methodWithNamedArgs(argThat(equals(42), named: "y"))) - .thenReturn("99"); - }, throwsArgumentError); - }); - test("should throw if named matcher is passed as the wrong name", () { expect(() { when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed("z"))) diff --git a/pkgs/mockito/test/utils.dart b/pkgs/mockito/test/utils.dart new file mode 100644 index 000000000..a8ecd4943 --- /dev/null +++ b/pkgs/mockito/test/utils.dart @@ -0,0 +1,21 @@ +import 'package:mockito/mockito.dart'; + +abstract class NsmForwardingSignal { + void fn([int a]); +} + +class MockNsmForwardingSignal extends Mock implements NsmForwardingSignal {} + +bool assessNsmForwarding() { + var signal = new MockNsmForwardingSignal(); + signal.fn(); + try { + verify(signal.fn(typed(any))); + return true; + } catch (_) { + // The verify failed, because the default value of 7 was not passed to + // noSuchMethod. + verify(signal.fn()); + return false; + } +} diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 9c1bcf1c4..6945306ea 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -16,6 +16,8 @@ import 'package:mockito/mockito.dart'; import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:test/test.dart'; +import 'utils.dart'; + class RealClass { RealClass innerObj; String methodWithoutArgs() => 'Real'; @@ -73,6 +75,8 @@ String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' void main() { MockedClass mock; + var isNsmForwarding = assessNsmForwarding(); + setUp(() { mock = new MockedClass(); }); @@ -242,8 +246,9 @@ void main() { test('and there is one unmatched call without args', () { mock.methodWithOptionalArg(); + var nsmForwardedArgs = isNsmForwarding ? 'null' : ''; expectFail( - 'No matching calls. All calls: MockedClass.methodWithOptionalArg()\n' + 'No matching calls. All calls: MockedClass.methodWithOptionalArg($nsmForwardedArgs)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithOptionalArg(43)); }); @@ -263,9 +268,10 @@ void main() { test('and unmatched calls have only named args', () { mock.methodWithOnlyNamedArgs(y: 1); + var nsmForwardedArgs = isNsmForwarding ? '{y: 1, z: null}' : '{y: 1}'; expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithOnlyNamedArgs({y: 1})\n' + 'MockedClass.methodWithOnlyNamedArgs($nsmForwardedArgs)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithOnlyNamedArgs()); }); @@ -509,6 +515,8 @@ void main() { mock.methodWithLongArgs(null, null, c: new LongToString([5, 6], {5: 'g', 6: 'h'}, 'i'), d: new LongToString([7, 8], {7: 'j', 8: 'k'}, 'l')); + var nsmForwardedNamedArgs = + isNsmForwarding ? '>, {c: null, d: null}),' : '>),'; expectFail( 'No matching calls. All calls: ' 'MockedClass.methodWithLongArgs(\n' @@ -521,7 +529,7 @@ void main() { ' aList: [4, 5]\n' ' aMap: {3: d, 4: e}\n' ' aString: f\n' - ' >),\n' + ' $nsmForwardedNamedArgs\n' 'MockedClass.methodWithLongArgs(null, null, {\n' ' c: LongToString<\n' ' aList: [5, 6]\n' From 2e04ddd8bd139ab07b324ce0e609e150c5cf42a0 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 16 May 2018 16:10:10 -0700 Subject: [PATCH 105/607] Bump to 3.0.0-beta (dart-lang/mockito#115) Bump to 3.0.0-beta --- pkgs/mockito/CHANGELOG.md | 23 +++++++++++++++++++++++ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index fc74b0730..7ef64cf0b 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,26 @@ +## 3.0.0-beta + +* This release is the first 3.0.0 release featuring the new Mockito 3 API. The + README has been updated, and an [upgrading-to-mockito-3] doc has been added + to help users upgrade. Here's a quick rundown: + + ```dart + // Old API: + when(obj.fn(typed(any)))... + // New API: + when(obj.fn(any))... + + // Old API: + when(obj.fn(foo: typed(any, named: 'foo')))... + // New API: + when(obj.fn(foo: anyNamed('foo')))... + + // Old API: + when(obj.fn(foo: typed(null, named: 'foo')))... + // New API: + when(obj.fn(foo: argThat(isNull, named: 'foo')))... + ``` + ## 3.0.0-alpha+5 * Fix compatibility with new [noSuchMethod Forwarding] feature of Dart 2. This diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index f8496894b..74076cab1 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0-alpha+3 +version: 3.0.0-beta authors: - Dmitriy Fibulwinter - Dart Team From 3f1823bf58ae7bf52bddad7a9a68e0da117defe0 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 21 May 2018 10:48:44 -0700 Subject: [PATCH 106/607] Add coveralls support (dart-lang/mockito#134) --- pkgs/mockito/.travis.yml | 3 +++ pkgs/mockito/test/all.dart | 30 ++++++++++++++++++++++++++++++ pkgs/mockito/tool/travis.sh | 14 ++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 pkgs/mockito/test/all.dart diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index ae5985215..6d661f459 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -35,6 +35,9 @@ jobs: - stage: testing script: ./tool/travis.sh dart2js_test dart: dev + - stage: testing + script: ./tool/travis.sh coverage + dart: dev # Only building master means that we don't run two builds for each pull request. branches: diff --git a/pkgs/mockito/test/all.dart b/pkgs/mockito/test/all.dart new file mode 100644 index 000000000..ef120e6e7 --- /dev/null +++ b/pkgs/mockito/test/all.dart @@ -0,0 +1,30 @@ +// Copyright 2018 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file explicitly does _not_ end in `_test.dart`, so that it is not picked +// up by `pub run test`. It is here for coveralls. + +import 'capture_test.dart' as capture_test; +import 'invocation_matcher_test.dart' as invocation_matcher_test; +import 'mockito_test.dart' as mockito_test; +import 'until_called_test.dart' as until_called_test; +import 'verify_test.dart' as verify_test; + +void main() { + capture_test.main(); + invocation_matcher_test.main(); + mockito_test.main(); + until_called_test.main(); + verify_test.main(); +} diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 38419b8a6..748d6196a 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -54,6 +54,20 @@ while (( "$#" )); do echo -e 'pub run test -p chrome' pub run test -p chrome || EXIT_CODE=$? ;; + coverage) echo + echo -e '\033[1mTASK: coverage\033[22m' + if [ "$REPO_TOKEN" ]; then + echo -e 'pub run dart_coveralls report test/all.dart' + pub global activate dart_coveralls + pub global run dart_coveralls report \ + --token $REPO_TOKEN \ + --retry 2 \ + --exclude-test-files \ + test/all.dart + else + echo -e "\033[33mNo token for coveralls. Skipping.\033[0m" + fi + ;; *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" EXIT_CODE=1 ;; From 8c144e78d6dfdf001e97e543aafde828c57d6317 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 21 May 2018 21:13:39 -0700 Subject: [PATCH 107/607] Remove unnecessary imports in tests (dart-lang/mockito#135) --- pkgs/mockito/test/capture_test.dart | 1 - pkgs/mockito/test/mockito_test.dart | 2 -- pkgs/mockito/test/until_called_test.dart | 1 - pkgs/mockito/test/verify_test.dart | 1 - 4 files changed, 5 deletions(-) diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 8ca3606c9..022a7306d 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -13,7 +13,6 @@ // limitations under the License. import 'package:mockito/mockito.dart'; -import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 579474056..1bc0705f5 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -15,8 +15,6 @@ import 'dart:async'; import 'package:mockito/mockito.dart'; -import 'package:mockito/src/mock.dart' - show resetMockitoState, throwOnMissingStub; import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index e4c17e37b..bde801e26 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -15,7 +15,6 @@ import 'dart:async'; import 'package:mockito/mockito.dart'; -import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:test/test.dart'; class RealClass { diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 6945306ea..10d62edf3 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -13,7 +13,6 @@ // limitations under the License. import 'package:mockito/mockito.dart'; -import 'package:mockito/src/mock.dart' show resetMockitoState; import 'package:test/test.dart'; import 'utils.dart'; From f6ff3a6db7fdaeeea7487a5c0e08e244f1ce8ba4 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 15 Jun 2018 14:26:09 -0700 Subject: [PATCH 108/607] Replace Apache v2 summary with full text (dart-lang/mockito#139) --- pkgs/mockito/LICENSE | 209 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 199 insertions(+), 10 deletions(-) diff --git a/pkgs/mockito/LICENSE b/pkgs/mockito/LICENSE index f7345eb9d..d64569567 100644 --- a/pkgs/mockito/LICENSE +++ b/pkgs/mockito/LICENSE @@ -1,13 +1,202 @@ -Copyright 2016 Dart Mockito authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - http://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 575e2d67a77b9a01789c82e06c3dc5b11f16408c Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 15 Jun 2018 19:48:21 -0700 Subject: [PATCH 109/607] Add some notes for 2.2.0 changelog entry --- pkgs/mockito/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 7ef64cf0b..0281f1df7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -90,6 +90,11 @@ * Add new feature to wait for an interaction: `untilCalled`. See the README for documentation. +* `capture*` calls outside of a `verify*` call no longer capture arguments. +* Some collections require stricter argument matching. For example, a stub like: + `mock.methodWithListArgs([1,2,3].map((e) => e*2))` (note the _`Iterable`_ + argument) will no longer match the following stub: + `when(mock.methodWithListArgs([42])).thenReturn(7);`. ## 2.1.0 From e72adab64f1365b6a300b60a806a24e39b26c285 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 19 Jun 2018 09:32:33 -0700 Subject: [PATCH 110/607] Add implicit-casts: false to analysis_options (dart-lang/mockito#141) --- pkgs/mockito/analysis_options.yaml | 3 ++- pkgs/mockito/test/example/iss/iss.dart | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 092773bea..dcc81ea1d 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,5 +1,6 @@ analyzer: - strong-mode: true + strong-mode: + implicit-casts: false linter: rules: # Errors diff --git a/pkgs/mockito/test/example/iss/iss.dart b/pkgs/mockito/test/example/iss/iss.dart index 9cfc7ebf9..b81424c32 100644 --- a/pkgs/mockito/test/example/iss/iss.dart +++ b/pkgs/mockito/test/example/iss/iss.dart @@ -42,9 +42,9 @@ class IssLocator { // Returns the point on the earth directly under the space station // at this moment. Response rs = await client.get('http://api.open-notify.org/iss-now.json'); - Map data = jsonDecode(rs.body); - double latitude = double.parse(data['iss_position']['latitude']); - double longitude = double.parse(data['iss_position']['longitude']); + var data = jsonDecode(rs.body); + var latitude = double.parse(data['iss_position']['latitude'] as String); + var longitude = double.parse(data['iss_position']['longitude'] as String); _position = new Point(latitude, longitude); } } @@ -68,14 +68,14 @@ class IssSpotter { // Returns the distance, in kilometers, between p1 and p2 along the earth's // curved surface. double sphericalDistanceKm(Point p1, Point p2) { - double dLat = _toRadian(p1.x - p2.x); - double sLat = pow(sin(dLat / 2), 2); - double dLng = _toRadian(p1.y - p2.y); - double sLng = pow(sin(dLng / 2), 2); - double cosALat = cos(_toRadian(p1.x)); - double cosBLat = cos(_toRadian(p2.x)); - double x = sLat + cosALat * cosBLat * sLng; - double d = 2 * atan2(sqrt(x), sqrt(1 - x)) * _radiusOfEarth; + var dLat = _toRadian(p1.x - p2.x); + var sLat = pow(sin(dLat / 2), 2); + var dLng = _toRadian(p1.y - p2.y); + var sLng = pow(sin(dLng / 2), 2); + var cosALat = cos(_toRadian(p1.x)); + var cosBLat = cos(_toRadian(p2.x)); + var x = sLat + cosALat * cosBLat * sLng; + var d = 2 * atan2(sqrt(x), sqrt(1 - x)) * _radiusOfEarth; return d; } From 3a54019aa756ec452dd4e8df3de9057c3fe457bd Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 20 Jun 2018 09:13:28 -0700 Subject: [PATCH 111/607] Move all tests of deprecated typed APIs to test/deprecated_apis (dart-lang/mockito#140) --- pkgs/mockito/lib/mockito.dart | 6 +- pkgs/mockito/lib/src/mock.dart | 1 + pkgs/mockito/test/capture_test.dart | 26 +-- .../test/deprecated_apis/capture_test.dart | 105 +++++++++ .../test/deprecated_apis/mockito_test.dart | 137 ++++++++++++ .../deprecated_apis/until_called_test.dart | 200 ++++++++++++++++++ .../test/deprecated_apis/verify_test.dart | 123 +++++++++++ pkgs/mockito/test/mockito_test.dart | 53 +++-- pkgs/mockito/test/until_called_test.dart | 85 ++++---- pkgs/mockito/test/utils.dart | 2 +- pkgs/mockito/test/verify_test.dart | 86 ++++---- 11 files changed, 687 insertions(+), 137 deletions(-) create mode 100644 pkgs/mockito/test/deprecated_apis/capture_test.dart create mode 100644 pkgs/mockito/test/deprecated_apis/mockito_test.dart create mode 100644 pkgs/mockito/test/deprecated_apis/until_called_test.dart create mode 100644 pkgs/mockito/test/deprecated_apis/verify_test.dart diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 90c73fd81..14a506f43 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -39,9 +39,9 @@ export 'src/mock.dart' Verification, // -- deprecated - typed, - typedArgThat, - typedCaptureThat, + typed, // ignore: deprecated_member_use + typedArgThat, // ignore: deprecated_member_use + typedCaptureThat, // ignore: deprecated_member_use // -- misc throwOnMissingStub, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 34978be6c..60b04ea84 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -593,6 +593,7 @@ class _VerifyCall { }); } + @override String toString() => 'VerifyCall'; } diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 022a7306d..9c1e02b95 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -17,8 +17,8 @@ import 'package:test/test.dart'; import 'utils.dart'; -class RealClass { - RealClass innerObj; +class _RealClass { + _RealClass innerObj; String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; @@ -27,15 +27,15 @@ class RealClass { } } -class MockedClass extends Mock implements RealClass {} +class _MockedClass extends Mock implements _RealClass {} void main() { - MockedClass mock; + _MockedClass mock; var isNsmForwarding = assessNsmForwarding(); setUp(() { - mock = new MockedClass(); + mock = new _MockedClass(); }); tearDown(() { @@ -47,8 +47,7 @@ void main() { group('capture', () { test('captureAny should match anything', () { mock.methodWithNormalArgs(42); - expect( - verify(mock.methodWithNormalArgs(typed(captureAny))).captured.single, + expect(verify(mock.methodWithNormalArgs(captureAny)).captured.single, equals(42)); }); @@ -58,22 +57,20 @@ void main() { mock.methodWithNormalArgs(43); mock.methodWithNormalArgs(45); expect( - verify(mock.methodWithNormalArgs(typed(captureThat(lessThan(44))))) - .captured, + verify(mock.methodWithNormalArgs(captureThat(lessThan(44)))).captured, equals([42, 43])); }); test('should capture list arguments', () { mock.methodWithListArgs([42]); - expect(verify(mock.methodWithListArgs(typed(captureAny))).captured.single, + expect(verify(mock.methodWithListArgs(captureAny)).captured.single, equals([42])); }); test('should capture multiple arguments', () { mock.methodWithPositionalArgs(1, 2); expect( - verify(mock.methodWithPositionalArgs( - typed(captureAny), typed(captureAny))) + verify(mock.methodWithPositionalArgs(captureAny, captureAny)) .captured, equals([1, 2])); }); @@ -83,8 +80,7 @@ void main() { mock.methodWithPositionalArgs(2, 3); var expectedCaptures = isNsmForwarding ? [1, null, 2, 3] : [2, 3]; expect( - verify(mock.methodWithPositionalArgs( - typed(captureAny), typed(captureAny))) + verify(mock.methodWithPositionalArgs(captureAny, captureAny)) .captured, equals(expectedCaptures)); }); @@ -92,7 +88,7 @@ void main() { test('should capture multiple invocations', () { mock.methodWithNormalArgs(1); mock.methodWithNormalArgs(2); - expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, + expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([1, 2])); }); }); diff --git a/pkgs/mockito/test/deprecated_apis/capture_test.dart b/pkgs/mockito/test/deprecated_apis/capture_test.dart new file mode 100644 index 000000000..89f03f9e8 --- /dev/null +++ b/pkgs/mockito/test/deprecated_apis/capture_test.dart @@ -0,0 +1,105 @@ +// Copyright 2018 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@deprecated +library mockito.test.deprecated_apis.capture_test; + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +import '../utils.dart'; + +class _RealClass { + _RealClass innerObj; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + set setter(String arg) { + throw new StateError('I must be mocked'); + } +} + +class MockedClass extends Mock implements _RealClass {} + +void main() { + MockedClass mock; + + var isNsmForwarding = assessNsmForwarding(); + + setUp(() { + mock = new MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group('capture', () { + test('captureAny should match anything', () { + mock.methodWithNormalArgs(42); + expect( + verify(mock.methodWithNormalArgs(typed(captureAny))).captured.single, + equals(42)); + }); + + test('captureThat should match some things', () { + mock.methodWithNormalArgs(42); + mock.methodWithNormalArgs(44); + mock.methodWithNormalArgs(43); + mock.methodWithNormalArgs(45); + expect( + verify(mock.methodWithNormalArgs(typed(captureThat(lessThan(44))))) + .captured, + equals([42, 43])); + }); + + test('should capture list arguments', () { + mock.methodWithListArgs([42]); + expect(verify(mock.methodWithListArgs(typed(captureAny))).captured.single, + equals([42])); + }); + + test('should capture multiple arguments', () { + mock.methodWithPositionalArgs(1, 2); + expect( + verify(mock.methodWithPositionalArgs( + typed(captureAny), typed(captureAny))) + .captured, + equals([1, 2])); + }); + + test('should capture with matching arguments', () { + mock.methodWithPositionalArgs(1); + mock.methodWithPositionalArgs(2, 3); + var expectedCaptures = isNsmForwarding ? [1, null, 2, 3] : [2, 3]; + expect( + verify(mock.methodWithPositionalArgs( + typed(captureAny), typed(captureAny))) + .captured, + equals(expectedCaptures)); + }); + + test('should capture multiple invocations', () { + mock.methodWithNormalArgs(1); + mock.methodWithNormalArgs(2); + expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, + equals([1, 2])); + }); + }); + + // TODO(srawlins): Test capturing in a setter. + // TODO(srawlins): Test capturing named arguments. +} diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart new file mode 100644 index 000000000..73e5b3459 --- /dev/null +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -0,0 +1,137 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@deprecated +library mockito.test.deprecated_apis.mockito_test; + +import 'dart:async'; + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +class _RealClass { + _RealClass innerObj; + String methodWithoutArgs() => "Real"; + String methodWithNormalArgs(int x) => "Real"; + String methodWithListArgs(List x) => "Real"; + String methodWithPositionalArgs(int x, [int y]) => "Real"; + String methodWithNamedArgs(int x, {int y}) => "Real"; + String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; + String methodWithObjArgs(_RealClass x) => "Real"; + Future methodReturningFuture() => new Future.value("Real"); + Stream methodReturningStream() => new Stream.fromIterable(["Real"]); + String get getter => "Real"; + set setter(String arg) { + throw new StateError("I must be mocked"); + } +} + +abstract class Foo { + String bar(); +} + +abstract class AbstractFoo implements Foo { + @override + String bar() => baz(); + + String baz(); +} + +class MockFoo extends AbstractFoo with Mock {} + +class _MockedClass extends Mock implements _RealClass {} + +expectFail(String expectedMessage, expectedToFail()) { + try { + expectedToFail(); + fail("It was expected to fail!"); + } catch (e) { + if (!(e is TestFailure)) { + throw e; + } else { + if (expectedMessage != e.message) { + throw new TestFailure("Failed, but with wrong message: ${e.message}"); + } + } + } +} + +String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " + "please instead use `verifyNever(...);`.)"; + +void main() { + _MockedClass mock; + + setUp(() { + mock = new _MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group("when()", () { + test("should mock method with argument matcher", () { + when(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))) + .thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), isNull); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); + }); + + test("should mock method with any argument matcher", () { + when(mock.methodWithNormalArgs(typed(any))).thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); + }); + + test("should mock method with any list argument matcher", () { + when(mock.methodWithListArgs(typed(any))).thenReturn("A lot!"); + expect(mock.methodWithListArgs([42]), equals("A lot!")); + expect(mock.methodWithListArgs([43]), equals("A lot!")); + }); + + test("should mock method with mix of argument matchers and real things", + () { + when(mock.methodWithPositionalArgs(typed(argThat(greaterThan(100))), 17)) + .thenReturn("A lot with 17"); + expect(mock.methodWithPositionalArgs(100, 17), isNull); + expect(mock.methodWithPositionalArgs(101, 18), isNull); + expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); + }); + + //no need to mock setter, except if we will have spies later... + test("should mock method with thrown result", () { + when(mock.methodWithNormalArgs(typed(any))) + .thenThrow(new StateError('Boo')); + expect(() => mock.methodWithNormalArgs(42), throwsStateError); + }); + + test("should mock method with calculated result", () { + when(mock.methodWithNormalArgs(typed(any))).thenAnswer( + (Invocation inv) => inv.positionalArguments[0].toString()); + expect(mock.methodWithNormalArgs(43), equals("43")); + expect(mock.methodWithNormalArgs(42), equals("42")); + }); + + test("should mock method with calculated result", () { + when(mock.methodWithNormalArgs(typed(argThat(equals(43))))) + .thenReturn("43"); + when(mock.methodWithNormalArgs(typed(argThat(equals(42))))) + .thenReturn("42"); + expect(mock.methodWithNormalArgs(43), equals("43")); + }); + }); +} diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart new file mode 100644 index 000000000..1daa1a4fd --- /dev/null +++ b/pkgs/mockito/test/deprecated_apis/until_called_test.dart @@ -0,0 +1,200 @@ +// Copyright 2016 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@deprecated +library mockito.test.deprecated_apis.until_called_test; + +import 'dart:async'; + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +class _RealClass { + _RealClass innerObj; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; + String typeParameterizedFn(List w, List x, + [List y, List z]) => + 'Real'; + String typeParameterizedNamedFn(List w, List x, + {List y, List z}) => + 'Real'; + String get getter => 'Real'; + set setter(String arg) { + throw new StateError('I must be mocked'); + } +} + +class CallMethodsEvent {} + +/// Listens on a stream and upon any event calls all methods in [_RealClass]. +class _RealClassController { + final _RealClass _realClass; + + _RealClassController( + this._realClass, StreamController streamController) { + streamController.stream.listen(_callAllMethods); + } + + Future _callAllMethods(_) async { + _realClass + ..methodWithoutArgs() + ..methodWithNormalArgs(1) + ..methodWithListArgs([1, 2]) + ..methodWithPositionalArgs(1, 2) + ..methodWithNamedArgs(1, y: 2) + ..methodWithTwoNamedArgs(1, y: 2, z: 3) + ..methodWithObjArgs(new _RealClass()) + ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) + ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) + ..getter + ..setter = 'A'; + } +} + +class MockedClass extends Mock implements _RealClass {} + +void main() { + MockedClass mock; + + setUp(() { + mock = new MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group('untilCalled', () { + StreamController streamController = + new StreamController.broadcast(); + + group('on methods already called', () { + test('waits for method with normal args', () async { + mock.methodWithNormalArgs(1); + + await untilCalled(mock.methodWithNormalArgs(typed(any))); + + verify(mock.methodWithNormalArgs(typed(any))).called(1); + }); + + test('waits for method with list args', () async { + mock.methodWithListArgs([1]); + + await untilCalled(mock.methodWithListArgs(typed(any))); + + verify(mock.methodWithListArgs(typed(any))).called(1); + }); + + test('waits for method with positional args', () async { + mock.methodWithPositionalArgs(1, 2); + + await untilCalled( + mock.methodWithPositionalArgs(typed(any), typed(any))); + + verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); + }); + + test('waits for method with named args', () async { + mock.methodWithNamedArgs(1, y: 2); + + await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); + + verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); + }); + + test('waits for method with obj args', () async { + mock.methodWithObjArgs(new _RealClass()); + + await untilCalled(mock.methodWithObjArgs(typed(any))); + + verify(mock.methodWithObjArgs(typed(any))).called(1); + }); + + test('waits for function with positional parameters', () async { + mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]); + + await untilCalled(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); + + verify(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))) + .called(1); + }); + }); + + group('on methods not yet called', () { + setUp(() { + new _RealClassController(mock, streamController); + }); + + test('waits for method with normal args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithNormalArgs(typed(any))); + + await untilCalled(mock.methodWithNormalArgs(typed(any))); + + verify(mock.methodWithNormalArgs(typed(any))).called(1); + }); + + test('waits for method with list args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithListArgs(typed(any))); + + await untilCalled(mock.methodWithListArgs(typed(any))); + + verify(mock.methodWithListArgs(typed(any))).called(1); + }); + + test('waits for method with positional args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithPositionalArgs(typed(any), typed(any))); + + await untilCalled( + mock.methodWithPositionalArgs(typed(any), typed(any))); + + verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); + }); + + test('waits for method with obj args', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.methodWithObjArgs(typed(any))); + + await untilCalled(mock.methodWithObjArgs(typed(any))); + + verify(mock.methodWithObjArgs(typed(any))).called(1); + }); + + test('waits for function with positional parameters', () async { + streamController.add(new CallMethodsEvent()); + verifyNever(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); + + await untilCalled(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))); + + verify(mock.typeParameterizedFn( + typed(any), typed(any), typed(any), typed(any))) + .called(1); + }); + }); + }); +} diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart new file mode 100644 index 000000000..a9178104e --- /dev/null +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -0,0 +1,123 @@ +// Copyright 2018 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@deprecated +library mockito.test.deprecated_apis.verify_test; + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +class _RealClass { + _RealClass innerObj; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithOptionalArg([int x]) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithOnlyNamedArgs({int y, int z}) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; + String get getter => 'Real'; + set setter(String arg) { + throw new StateError('I must be mocked'); + } + + String methodWithLongArgs(LongToString a, LongToString b, + {LongToString c, LongToString d}) => + 'Real'; +} + +class LongToString { + final List aList; + final Map aMap; + final String aString; + + LongToString(this.aList, this.aMap, this.aString); + + @override + String toString() => 'LongToString<\n' + ' aList: $aList\n' + ' aMap: $aMap\n' + ' aString: $aString\n' + '>'; +} + +class _MockedClass extends Mock implements _RealClass {} + +expectFail(String expectedMessage, expectedToFail()) { + try { + expectedToFail(); + fail('It was expected to fail!'); + } catch (e) { + if (!(e is TestFailure)) { + throw e; + } else { + if (expectedMessage != e.message) { + throw new TestFailure('Failed, but with wrong message: ${e.message}'); + } + } + } +} + +String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' + 'please instead use `verifyNever(...);`.)'; + +void main() { + _MockedClass mock; + + setUp(() { + mock = new _MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group('verify', () { + test('should mock method with argument matcher', () { + mock.methodWithNormalArgs(100); + expectFail( + 'No matching calls. All calls: ' + '_MockedClass.methodWithNormalArgs(100)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))); + }); + verify( + mock.methodWithNormalArgs(typed(argThat(greaterThanOrEqualTo(100))))); + }); + + test('should mock method with mix of argument matchers and real things', + () { + mock.methodWithPositionalArgs(100, 17); + expectFail( + 'No matching calls. All calls: ' + '_MockedClass.methodWithPositionalArgs(100, 17)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithPositionalArgs( + typed(argThat(greaterThanOrEqualTo(100))), 18)); + }); + expectFail( + 'No matching calls. All calls: ' + '_MockedClass.methodWithPositionalArgs(100, 17)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithPositionalArgs( + typed(argThat(greaterThan(100))), 17)); + }); + verify(mock.methodWithPositionalArgs( + typed(argThat(greaterThanOrEqualTo(100))), 17)); + }); + }); +} diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 1bc0705f5..4e6a0e5dc 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -19,15 +19,15 @@ import 'package:test/test.dart'; import 'utils.dart'; -class RealClass { - RealClass innerObj; +class _RealClass { + _RealClass innerObj; String methodWithoutArgs() => "Real"; String methodWithNormalArgs(int x) => "Real"; String methodWithListArgs(List x) => "Real"; String methodWithPositionalArgs(int x, [int y]) => "Real"; String methodWithNamedArgs(int x, {int y}) => "Real"; String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; - String methodWithObjArgs(RealClass x) => "Real"; + String methodWithObjArgs(_RealClass x) => "Real"; Future methodReturningFuture() => new Future.value("Real"); Stream methodReturningStream() => new Stream.fromIterable(["Real"]); String get getter => "Real"; @@ -36,20 +36,20 @@ class RealClass { } } -abstract class Foo { +abstract class _Foo { String bar(); } -abstract class AbstractFoo implements Foo { +abstract class _AbstractFoo implements _Foo { @override String bar() => baz(); String baz(); } -class MockFoo extends AbstractFoo with Mock {} +class _MockFoo extends _AbstractFoo with Mock {} -class MockedClass extends Mock implements RealClass {} +class _MockedClass extends Mock implements _RealClass {} expectFail(String expectedMessage, expectedToFail()) { try { @@ -70,12 +70,12 @@ String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " "please instead use `verifyNever(...);`.)"; void main() { - MockedClass mock; + _MockedClass mock; var isNsmForwarding = assessNsmForwarding(); setUp(() { - mock = new MockedClass(); + mock = new _MockedClass(); }); tearDown(() { @@ -86,7 +86,7 @@ void main() { group("mixin support", () { test("should work", () { - var foo = new MockFoo(); + var foo = new _MockFoo(); when(foo.baz()).thenReturn('baz'); expect(foo.bar(), 'baz'); }); @@ -105,9 +105,9 @@ void main() { }); test("should mock method with mock args", () { - var m1 = new MockedClass(); + var m1 = new _MockedClass(); when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); - expect(mock.methodWithObjArgs(new MockedClass()), isNull); + expect(mock.methodWithObjArgs(new _MockedClass()), isNull); expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); }); @@ -132,20 +132,20 @@ void main() { }); test("should mock method with argument matcher", () { - when(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))) + when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) .thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), isNull); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); test("should mock method with any argument matcher", () { - when(mock.methodWithNormalArgs(typed(any))).thenReturn("A lot!"); + when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), equals("A lot!")); expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); test("should mock method with any list argument matcher", () { - when(mock.methodWithListArgs(typed(any))).thenReturn("A lot!"); + when(mock.methodWithListArgs(any)).thenReturn("A lot!"); expect(mock.methodWithListArgs([42]), equals("A lot!")); expect(mock.methodWithListArgs([43]), equals("A lot!")); }); @@ -169,7 +169,7 @@ void main() { test("should mock method with mix of argument matchers and real things", () { - when(mock.methodWithPositionalArgs(typed(argThat(greaterThan(100))), 17)) + when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) .thenReturn("A lot with 17"); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); @@ -191,7 +191,7 @@ void main() { }); test("should have default toString when it is not mocked", () { - expect(mock.toString(), equals("MockedClass")); + expect(mock.toString(), equals("_MockedClass")); }); test("should have toString as name when it is not mocked", () { @@ -200,33 +200,32 @@ void main() { }); test("should mock equals between mocks when givenHashCode is equals", () { - var anotherMock = named(new MockedClass(), hashCode: 42); + var anotherMock = named(new _MockedClass(), hashCode: 42); named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); }); test("should use identical equality between it is not mocked", () { - var anotherMock = new MockedClass(); + var anotherMock = new _MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); //no need to mock setter, except if we will have spies later... test("should mock method with thrown result", () { - when(mock.methodWithNormalArgs(typed(any))) - .thenThrow(new StateError('Boo')); + when(mock.methodWithNormalArgs(any)).thenThrow(new StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); test("should mock method with calculated result", () { - when(mock.methodWithNormalArgs(typed(any))).thenAnswer( + when(mock.methodWithNormalArgs(any)).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); expect(mock.methodWithNormalArgs(43), equals("43")); expect(mock.methodWithNormalArgs(42), equals("42")); }); test("should return mock to make simple oneline mocks", () { - RealClass mockWithSetup = new MockedClass(); + _RealClass mockWithSetup = new _MockedClass(); when(mockWithSetup.methodWithoutArgs()).thenReturn("oneline"); expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); }); @@ -238,10 +237,8 @@ void main() { }); test("should mock method with calculated result", () { - when(mock.methodWithNormalArgs(typed(argThat(equals(43))))) - .thenReturn("43"); - when(mock.methodWithNormalArgs(typed(argThat(equals(42))))) - .thenReturn("42"); + when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); + when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); @@ -249,7 +246,7 @@ void main() { test("should throw if `when` is called while stubbing", () { expect(() { var responseHelper = () { - var mock2 = new MockedClass(); + var mock2 = new _MockedClass(); when(mock2.getter).thenReturn("A"); return mock2; }; diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index bde801e26..24e31a3c1 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -17,15 +17,15 @@ import 'dart:async'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; -class RealClass { - RealClass innerObj; +class _RealClass { + _RealClass innerObj; String methodWithoutArgs() => 'Real'; String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; String methodWithNamedArgs(int x, {int y}) => 'Real'; String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; - String methodWithObjArgs(RealClass x) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; String typeParameterizedFn(List w, List x, [List y, List z]) => 'Real'; @@ -40,11 +40,11 @@ class RealClass { class CallMethodsEvent {} -/// Listens on a stream and upon any event calls all methods in [RealClass]. -class RealClassController { - final RealClass _realClass; +/// Listens on a stream and upon any event calls all methods in [_RealClass]. +class _RealClassController { + final _RealClass _realClass; - RealClassController( + _RealClassController( this._realClass, StreamController streamController) { streamController.stream.listen(_callAllMethods); } @@ -57,7 +57,7 @@ class RealClassController { ..methodWithPositionalArgs(1, 2) ..methodWithNamedArgs(1, y: 2) ..methodWithTwoNamedArgs(1, y: 2, z: 3) - ..methodWithObjArgs(new RealClass()) + ..methodWithObjArgs(new _RealClass()) ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) ..getter @@ -65,13 +65,13 @@ class RealClassController { } } -class MockedClass extends Mock implements RealClass {} +class _MockedClass extends Mock implements _RealClass {} void main() { - MockedClass mock; + _MockedClass mock; setUp(() { - mock = new MockedClass(); + mock = new _MockedClass(); }); tearDown(() { @@ -96,26 +96,25 @@ void main() { test('waits for method with normal args', () async { mock.methodWithNormalArgs(1); - await untilCalled(mock.methodWithNormalArgs(typed(any))); + await untilCalled(mock.methodWithNormalArgs(any)); - verify(mock.methodWithNormalArgs(typed(any))).called(1); + verify(mock.methodWithNormalArgs(any)).called(1); }); test('waits for method with list args', () async { mock.methodWithListArgs([1]); - await untilCalled(mock.methodWithListArgs(typed(any))); + await untilCalled(mock.methodWithListArgs(any)); - verify(mock.methodWithListArgs(typed(any))).called(1); + verify(mock.methodWithListArgs(any)).called(1); }); test('waits for method with positional args', () async { mock.methodWithPositionalArgs(1, 2); - await untilCalled( - mock.methodWithPositionalArgs(typed(any), typed(any))); + await untilCalled(mock.methodWithPositionalArgs(any, any)); - verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); + verify(mock.methodWithPositionalArgs(any, any)).called(1); }); test('waits for method with named args', () async { @@ -138,22 +137,19 @@ void main() { }); test('waits for method with obj args', () async { - mock.methodWithObjArgs(new RealClass()); + mock.methodWithObjArgs(new _RealClass()); - await untilCalled(mock.methodWithObjArgs(typed(any))); + await untilCalled(mock.methodWithObjArgs(any)); - verify(mock.methodWithObjArgs(typed(any))).called(1); + verify(mock.methodWithObjArgs(any)).called(1); }); test('waits for function with positional parameters', () async { mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]); - await untilCalled(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); + await untilCalled(mock.typeParameterizedFn(any, any, any, any)); - verify(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))) - .called(1); + verify(mock.typeParameterizedFn(any, any, any, any)).called(1); }); test('waits for function with named parameters', () async { @@ -186,7 +182,7 @@ void main() { group('on methods not yet called', () { setUp(() { - new RealClassController(mock, streamController); + new _RealClassController(mock, streamController); }); test('waits for method without args', () async { @@ -200,30 +196,29 @@ void main() { test('waits for method with normal args', () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithNormalArgs(typed(any))); + verifyNever(mock.methodWithNormalArgs(any)); - await untilCalled(mock.methodWithNormalArgs(typed(any))); + await untilCalled(mock.methodWithNormalArgs(any)); - verify(mock.methodWithNormalArgs(typed(any))).called(1); + verify(mock.methodWithNormalArgs(any)).called(1); }); test('waits for method with list args', () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithListArgs(typed(any))); + verifyNever(mock.methodWithListArgs(any)); - await untilCalled(mock.methodWithListArgs(typed(any))); + await untilCalled(mock.methodWithListArgs(any)); - verify(mock.methodWithListArgs(typed(any))).called(1); + verify(mock.methodWithListArgs(any)).called(1); }); test('waits for method with positional args', () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithPositionalArgs(typed(any), typed(any))); + verifyNever(mock.methodWithPositionalArgs(any, any)); - await untilCalled( - mock.methodWithPositionalArgs(typed(any), typed(any))); + await untilCalled(mock.methodWithPositionalArgs(any, any)); - verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); + verify(mock.methodWithPositionalArgs(any, any)).called(1); }); test('waits for method with named args', () async { @@ -250,24 +245,20 @@ void main() { test('waits for method with obj args', () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.methodWithObjArgs(typed(any))); + verifyNever(mock.methodWithObjArgs(any)); - await untilCalled(mock.methodWithObjArgs(typed(any))); + await untilCalled(mock.methodWithObjArgs(any)); - verify(mock.methodWithObjArgs(typed(any))).called(1); + verify(mock.methodWithObjArgs(any)).called(1); }); test('waits for function with positional parameters', () async { streamController.add(new CallMethodsEvent()); - verifyNever(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); + verifyNever(mock.typeParameterizedFn(any, any, any, any)); - await untilCalled(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); + await untilCalled(mock.typeParameterizedFn(any, any, any, any)); - verify(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))) - .called(1); + verify(mock.typeParameterizedFn(any, any, any, any)).called(1); }); test('waits for function with named parameters', () async { diff --git a/pkgs/mockito/test/utils.dart b/pkgs/mockito/test/utils.dart index a8ecd4943..715ba3b79 100644 --- a/pkgs/mockito/test/utils.dart +++ b/pkgs/mockito/test/utils.dart @@ -10,7 +10,7 @@ bool assessNsmForwarding() { var signal = new MockNsmForwardingSignal(); signal.fn(); try { - verify(signal.fn(typed(any))); + verify(signal.fn(any)); return true; } catch (_) { // The verify failed, because the default value of 7 was not passed to diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 10d62edf3..05a6e266a 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -17,8 +17,8 @@ import 'package:test/test.dart'; import 'utils.dart'; -class RealClass { - RealClass innerObj; +class _RealClass { + _RealClass innerObj; String methodWithoutArgs() => 'Real'; String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; @@ -26,7 +26,7 @@ class RealClass { String methodWithPositionalArgs(int x, [int y]) => 'Real'; String methodWithNamedArgs(int x, {int y}) => 'Real'; String methodWithOnlyNamedArgs({int y, int z}) => 'Real'; - String methodWithObjArgs(RealClass x) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; String get getter => 'Real'; set setter(String arg) { throw new StateError('I must be mocked'); @@ -44,6 +44,7 @@ class LongToString { LongToString(this.aList, this.aMap, this.aString); + @override String toString() => 'LongToString<\n' ' aList: $aList\n' ' aMap: $aMap\n' @@ -51,7 +52,7 @@ class LongToString { '>'; } -class MockedClass extends Mock implements RealClass {} +class _MockedClass extends Mock implements _RealClass {} expectFail(String expectedMessage, expectedToFail()) { try { @@ -72,12 +73,12 @@ String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' 'please instead use `verifyNever(...);`.)'; void main() { - MockedClass mock; + _MockedClass mock; var isNsmForwarding = assessNsmForwarding(); setUp(() { - mock = new MockedClass(); + mock = new _MockedClass(); }); tearDown(() { @@ -101,13 +102,13 @@ void main() { mock.methodWithPositionalArgs(42, 17); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithPositionalArgs(42, 17)\n' + '_MockedClass.methodWithPositionalArgs(42, 17)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithPositionalArgs(42)); }); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithPositionalArgs(42, 17)\n' + '_MockedClass.methodWithPositionalArgs(42, 17)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithPositionalArgs(42, 18)); }); @@ -118,13 +119,13 @@ void main() { mock.methodWithNamedArgs(42, y: 17); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithNamedArgs(42, {y: 17})\n' + '_MockedClass.methodWithNamedArgs(42, {y: 17})\n' '$noMatchingCallsFooter', () { verify(mock.methodWithNamedArgs(42)); }); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithNamedArgs(42, {y: 17})\n' + '_MockedClass.methodWithNamedArgs(42, {y: 17})\n' '$noMatchingCallsFooter', () { verify(mock.methodWithNamedArgs(42, y: 18)); }); @@ -132,12 +133,12 @@ void main() { }); test('should mock method with mock args', () { - var m1 = named(new MockedClass(), name: 'm1'); + var m1 = named(new _MockedClass(), name: 'm1'); mock.methodWithObjArgs(m1); expectFail( - 'No matching calls. All calls: MockedClass.methodWithObjArgs(m1)\n' + 'No matching calls. All calls: _MockedClass.methodWithObjArgs(m1)\n' '$noMatchingCallsFooter', () { - verify(mock.methodWithObjArgs(new MockedClass())); + verify(mock.methodWithObjArgs(new _MockedClass())); }); verify(mock.methodWithObjArgs(m1)); }); @@ -146,7 +147,7 @@ void main() { mock.methodWithListArgs([42]); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithListArgs([42])\n' + '_MockedClass.methodWithListArgs([42])\n' '$noMatchingCallsFooter', () { verify(mock.methodWithListArgs([43])); }); @@ -157,12 +158,11 @@ void main() { mock.methodWithNormalArgs(100); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithNormalArgs(100)\n' + '_MockedClass.methodWithNormalArgs(100)\n' '$noMatchingCallsFooter', () { - verify(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))); + verify(mock.methodWithNormalArgs(argThat(greaterThan(100)))); }); - verify( - mock.methodWithNormalArgs(typed(argThat(greaterThanOrEqualTo(100))))); + verify(mock.methodWithNormalArgs(argThat(greaterThanOrEqualTo(100)))); }); test('should mock method with mix of argument matchers and real things', @@ -170,20 +170,19 @@ void main() { mock.methodWithPositionalArgs(100, 17); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithPositionalArgs(100, 17)\n' + '_MockedClass.methodWithPositionalArgs(100, 17)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThanOrEqualTo(100))), 18)); + argThat(greaterThanOrEqualTo(100)), 18)); }); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithPositionalArgs(100, 17)\n' + '_MockedClass.methodWithPositionalArgs(100, 17)\n' '$noMatchingCallsFooter', () { - verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThan(100))), 17)); + verify(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)); }); verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThanOrEqualTo(100))), 17)); + argThat(greaterThanOrEqualTo(100)), 17)); }); test('should mock getter', () { @@ -194,7 +193,7 @@ void main() { test('should mock setter', () { mock.setter = 'A'; expectFail( - 'No matching calls. All calls: MockedClass.setter==A\n' + 'No matching calls. All calls: _MockedClass.setter==A\n' '$noMatchingCallsFooter', () { verify(mock.setter = 'B'); }); @@ -237,7 +236,7 @@ void main() { test('and there is one unmatched call', () { mock.methodWithNormalArgs(42); expectFail( - 'No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n' + 'No matching calls. All calls: _MockedClass.methodWithNormalArgs(42)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithNormalArgs(43)); }); @@ -247,7 +246,7 @@ void main() { mock.methodWithOptionalArg(); var nsmForwardedArgs = isNsmForwarding ? 'null' : ''; expectFail( - 'No matching calls. All calls: MockedClass.methodWithOptionalArg($nsmForwardedArgs)\n' + 'No matching calls. All calls: _MockedClass.methodWithOptionalArg($nsmForwardedArgs)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithOptionalArg(43)); }); @@ -258,8 +257,8 @@ void main() { mock.methodWithNormalArgs(42); expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithNormalArgs(41), ' - 'MockedClass.methodWithNormalArgs(42)\n' + '_MockedClass.methodWithNormalArgs(41), ' + '_MockedClass.methodWithNormalArgs(42)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithNormalArgs(43)); }); @@ -270,7 +269,7 @@ void main() { var nsmForwardedArgs = isNsmForwarding ? '{y: 1, z: null}' : '{y: 1}'; expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithOnlyNamedArgs($nsmForwardedArgs)\n' + '_MockedClass.methodWithOnlyNamedArgs($nsmForwardedArgs)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithOnlyNamedArgs()); }); @@ -379,7 +378,8 @@ void main() { test('one fails', () { mock.methodWithoutArgs(); expectFail( - 'Unexpected calls. All calls: MockedClass.methodWithoutArgs()', () { + 'Unexpected calls. All calls: _MockedClass.methodWithoutArgs()', + () { verifyNever(mock.methodWithoutArgs()); }); }); @@ -391,7 +391,7 @@ void main() { verify(mock.methodWithoutArgs()); expectFail( 'No matching calls. ' - 'All calls: [VERIFIED] MockedClass.methodWithoutArgs()\n' + 'All calls: [VERIFIED] _MockedClass.methodWithoutArgs()\n' '$noMatchingCallsFooter', () { verify(mock.methodWithoutArgs()); }); @@ -415,7 +415,7 @@ void main() { mock.methodWithoutArgs(); expectFail( 'No interaction expected, but following found: ' - 'MockedClass.methodWithoutArgs()', () { + '_MockedClass.methodWithoutArgs()', () { verifyZeroInteractions(mock); }); }); @@ -425,7 +425,7 @@ void main() { verify(mock.methodWithoutArgs()); expectFail( 'No interaction expected, but following found: ' - '[VERIFIED] MockedClass.methodWithoutArgs()', () { + '[VERIFIED] _MockedClass.methodWithoutArgs()', () { verifyZeroInteractions(mock); }); }); @@ -440,7 +440,7 @@ void main() { mock.methodWithoutArgs(); expectFail( 'No more calls expected, but following found: ' - 'MockedClass.methodWithoutArgs()', () { + '_MockedClass.methodWithoutArgs()', () { verifyNoMoreInteractions(mock); }); }); @@ -452,8 +452,8 @@ void main() { }); test('throws if given a real object', () { - expect( - () => verifyNoMoreInteractions(new RealClass()), throwsArgumentError); + expect(() => verifyNoMoreInteractions(new _RealClass()), + throwsArgumentError); }); }); @@ -469,7 +469,7 @@ void main() { mock.getter; expectFail( 'Matching call #1 not found. All calls: ' - 'MockedClass.methodWithoutArgs(), MockedClass.getter', () { + '_MockedClass.methodWithoutArgs(), _MockedClass.getter', () { verifyInOrder([mock.getter, mock.methodWithoutArgs()]); }); }); @@ -478,7 +478,7 @@ void main() { mock.methodWithoutArgs(); expectFail( 'Matching call #1 not found. All calls: ' - 'MockedClass.methodWithoutArgs()', () { + '_MockedClass.methodWithoutArgs()', () { verifyInOrder([mock.methodWithoutArgs(), mock.getter]); }); }); @@ -497,8 +497,8 @@ void main() { mock.getter; expectFail( 'Matching call #2 not found. All calls: ' - 'MockedClass.methodWithoutArgs(), MockedClass.methodWithoutArgs(), ' - 'MockedClass.getter', () { + '_MockedClass.methodWithoutArgs(), _MockedClass.methodWithoutArgs(), ' + '_MockedClass.getter', () { verifyInOrder( [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); }); @@ -518,7 +518,7 @@ void main() { isNsmForwarding ? '>, {c: null, d: null}),' : '>),'; expectFail( 'No matching calls. All calls: ' - 'MockedClass.methodWithLongArgs(\n' + '_MockedClass.methodWithLongArgs(\n' ' LongToString<\n' ' aList: [1, 2]\n' ' aMap: {1: a, 2: b}\n' @@ -529,7 +529,7 @@ void main() { ' aMap: {3: d, 4: e}\n' ' aString: f\n' ' $nsmForwardedNamedArgs\n' - 'MockedClass.methodWithLongArgs(null, null, {\n' + '_MockedClass.methodWithLongArgs(null, null, {\n' ' c: LongToString<\n' ' aList: [5, 6]\n' ' aMap: {5: g, 6: h}\n' From 1c697c4890530cb6e93bb7f10fd1ef83629a586e Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Fri, 22 Jun 2018 15:29:55 -0400 Subject: [PATCH 112/607] Upgrade pkg deps; implement workaround in tests; minor code cleanup (dart-lang/mockito#146) * pubspec.yaml: upgrade dependencies * Upgrade dependencies; in particular, support test 1.0.0 * Generalize test helpers to support RegExp matcher --- pkgs/mockito/CHANGELOG.md | 4 +++ pkgs/mockito/CONTRIBUTING.md | 6 ++-- pkgs/mockito/lib/src/call_pair.dart | 2 +- pkgs/mockito/pubspec.yaml | 17 ++++++---- pkgs/mockito/test/example/iss/iss.dart | 2 +- .../mockito/test/invocation_matcher_test.dart | 33 ++++++++----------- pkgs/mockito/test/verify_test.dart | 29 ++++++++-------- 7 files changed, 47 insertions(+), 46 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 0281f1df7..f677ae4f7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0-beta+1 + +- Upgrade package dependencies. + ## 3.0.0-beta * This release is the first 3.0.0 release featuring the new Mockito 3 API. The diff --git a/pkgs/mockito/CONTRIBUTING.md b/pkgs/mockito/CONTRIBUTING.md index e8cd89080..a984856bf 100644 --- a/pkgs/mockito/CONTRIBUTING.md +++ b/pkgs/mockito/CONTRIBUTING.md @@ -1,6 +1,7 @@ Want to contribute? Great! First, read this page (including the small print at the end). ### Before you contribute + Before we can use your code, you must sign the [Google Individual Contributor License Agreement] (https://cla.developers.google.com/about/google-individual) @@ -17,13 +18,14 @@ possibly guide you. Coordinating up front makes it much easier to avoid frustration later on. ### Code reviews + All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. ### The small print + Contributions made by corporations are covered by a different agreement than the one above, the -[Software Grant and Corporate Contributor License Agreement] -(https://cla.developers.google.com/about/google-corporate). +[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). **NOTE:** This is not an official Google product diff --git a/pkgs/mockito/lib/src/call_pair.dart b/pkgs/mockito/lib/src/call_pair.dart index bae33ebed..26b258b6d 100644 --- a/pkgs/mockito/lib/src/call_pair.dart +++ b/pkgs/mockito/lib/src/call_pair.dart @@ -29,7 +29,7 @@ class CallPair { const CallPair(this.call, this.response); const CallPair.allInvocations(this.response) - : call = const isInstanceOf(); + : call = const TypeMatcher(); @override String toString() => '$CallPair {$call -> $response}'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 74076cab1..377191315 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,20 +1,23 @@ name: mockito -version: 3.0.0-beta +version: 3.0.0-beta+1 + authors: - Dmitriy Fibulwinter - Dart Team + description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito + environment: sdk: '>=2.0.0-dev.16.0 <2.0.0' dependencies: - collection: '^1.1.0' - matcher: '^0.12.0' - meta: '^1.0.4' - test: '>=0.12.25 <0.13.0' + collection: ^1.1.0 + matcher: ^0.12.0 + meta: ^1.0.4 + test: '>=0.12.25 <2.0.0' dev_dependencies: - build_runner: ^0.7.11 + build_runner: ^0.8.10 build_test: ^0.10.0 - build_web_compilers: ^0.3.1 + build_web_compilers: ^0.4.0 diff --git a/pkgs/mockito/test/example/iss/iss.dart b/pkgs/mockito/test/example/iss/iss.dart index b81424c32..889988d5f 100644 --- a/pkgs/mockito/test/example/iss/iss.dart +++ b/pkgs/mockito/test/example/iss/iss.dart @@ -81,4 +81,4 @@ double sphericalDistanceKm(Point p1, Point p2) { /// Radius of the earth in km. const int _radiusOfEarth = 6371; -double _toRadian(num degree) => degree * PI / 180.0; +double _toRadian(num degree) => degree * pi / 180.0; diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 4f46ae769..32693e5aa 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -83,9 +83,10 @@ void main() { shouldFail( call1, isInvocation(call3), - "Expected: set value= " + // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 + RegExp('Expected: set value=? ' "Actual: " - "Which: Does not match get value", + 'Which: Does not match get value'), ); }); @@ -100,9 +101,10 @@ void main() { shouldFail( call1, isInvocation(call3), - "Expected: set value= " + // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 + RegExp("Expected: set value=? " "Actual: " - "Which: Does not match set value= ", + "Which: Does not match set value=? "), ); }); }); @@ -161,25 +163,18 @@ class Stub implements Interface { } } -// Copied from package:test, which doesn't expose it to users. -// https://github.com/dart-lang/matcher/issues/39 +// Inspired by shouldFail() from package:test, which doesn't expose it to users. void shouldFail(value, Matcher matcher, expected) { - var failed = false; + const reason = 'Expected to fail.'; try { expect(value, matcher); - } on TestFailure catch (err) { - failed = true; - - var _errorString = err.message; - - if (expected is String) { - expect(_errorString, equalsIgnoringWhitespace(expected)); - } else { - expect(_errorString.replaceAll('\n', ''), expected); - } + fail(reason); + } on TestFailure catch (e) { + final matcher = expected is String + ? equalsIgnoringWhitespace(expected) + : expected is RegExp ? contains(expected) : expected; + expect(collapseWhitespace(e.message), matcher, reason: reason); } - - expect(failed, isTrue, reason: 'Expected to fail.'); } void shouldPass(value, Matcher matcher) { diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 05a6e266a..d7c472e03 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -54,22 +54,18 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -expectFail(String expectedMessage, expectedToFail()) { +expectFail(Pattern expectedMessage, expectedToFail()) { try { expectedToFail(); fail('It was expected to fail!'); - } catch (e) { - if (!(e is TestFailure)) { - throw e; - } else { - if (expectedMessage != e.message) { - throw new TestFailure('Failed, but with wrong message: ${e.message}'); - } - } + } on TestFailure catch (e) { + expect(e.message, + expectedMessage is String ? expectedMessage : contains(expectedMessage), + reason: 'Failed but with unexpected message'); } } -String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' +const noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' 'please instead use `verifyNever(...);`.)'; void main() { @@ -192,11 +188,12 @@ void main() { test('should mock setter', () { mock.setter = 'A'; - expectFail( - 'No matching calls. All calls: _MockedClass.setter==A\n' - '$noMatchingCallsFooter', () { - verify(mock.setter = 'B'); - }); + final expectedMessage = RegExp.escape('No matching calls. ' + 'All calls: _MockedClass.setter==A\n$noMatchingCallsFooter'); + // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 + var expectedPattern = RegExp(expectedMessage.replaceFirst('==', '=?=')); + + expectFail(expectedPattern, () => verify(mock.setter = 'B')); verify(mock.setter = 'A'); }); @@ -215,7 +212,7 @@ void main() { verify(mock.methodWithNamedArgs(42, y: 17)); fail('verify call was expected to throw!'); } catch (e) { - expect(e, new isInstanceOf()); + expect(e, TypeMatcher()); expect( e.message, contains('Verification appears to be in progress. ' From bdf3a7b1015143b134b268022cbd4dd1076a4671 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 22 Jun 2018 13:26:31 -0700 Subject: [PATCH 113/607] No optional new yet (dart-lang/mockito#147) --- pkgs/mockito/test/invocation_matcher_test.dart | 4 ++-- pkgs/mockito/test/verify_test.dart | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 32693e5aa..b9d4f8d1f 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -84,7 +84,7 @@ void main() { call1, isInvocation(call3), // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - RegExp('Expected: set value=? ' + new RegExp('Expected: set value=? ' "Actual: " 'Which: Does not match get value'), ); @@ -102,7 +102,7 @@ void main() { call1, isInvocation(call3), // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - RegExp("Expected: set value=? " + new RegExp("Expected: set value=? " "Actual: " "Which: Does not match set value=? "), ); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index d7c472e03..aff826813 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -191,7 +191,8 @@ void main() { final expectedMessage = RegExp.escape('No matching calls. ' 'All calls: _MockedClass.setter==A\n$noMatchingCallsFooter'); // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - var expectedPattern = RegExp(expectedMessage.replaceFirst('==', '=?=')); + var expectedPattern = + new RegExp(expectedMessage.replaceFirst('==', '=?=')); expectFail(expectedPattern, () => verify(mock.setter = 'B')); verify(mock.setter = 'A'); @@ -212,7 +213,7 @@ void main() { verify(mock.methodWithNamedArgs(42, y: 17)); fail('verify call was expected to throw!'); } catch (e) { - expect(e, TypeMatcher()); + expect(e, new TypeMatcher()); expect( e.message, contains('Verification appears to be in progress. ' From 9050956300de269db7d44324a846fbe1bc5cee0a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 27 Jun 2018 10:33:31 -0700 Subject: [PATCH 114/607] Enforce a _few_ of the --no-strict-dynamic rules (dart-lang/mockito#144) * Enforce a _few_ of the --no-strict-dynamic rules * fmt * Less stringent checks in test/deprecated_apis --- pkgs/mockito/analysis_options.yaml | 10 ++++-- pkgs/mockito/lib/src/invocation_matcher.dart | 7 +++-- pkgs/mockito/lib/src/mock.dart | 31 ++++++++++--------- .../test/deprecated_apis/capture_test.dart | 2 ++ .../test/deprecated_apis/mockito_test.dart | 4 ++- .../deprecated_apis/until_called_test.dart | 2 ++ .../test/deprecated_apis/verify_test.dart | 4 ++- .../mockito/test/invocation_matcher_test.dart | 10 +++--- pkgs/mockito/test/mockito_test.dart | 2 +- pkgs/mockito/test/verify_test.dart | 2 +- 10 files changed, 45 insertions(+), 29 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index dcc81ea1d..02a06eebd 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,6 +1,12 @@ analyzer: - strong-mode: - implicit-casts: false + strong-mode: + implicit-casts: false + implicit-dynamic: false + errors: + strong_mode_implicit_dynamic_list_literal: ignore + strong_mode_implicit_dynamic_map_literal: ignore + strong_mode_implicit_dynamic_parameter: ignore + strong_mode_implicit_dynamic_variable: ignore linter: rules: # Errors diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index e09d9d70a..085ab7972 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -33,7 +33,7 @@ import 'package:mockito/src/mock.dart'; /// what a user expects to be called. Matcher invokes( Symbol memberName, { - List positionalArguments: const [], + List positionalArguments: const [], Map namedArguments: const {}, bool isGetter: false, bool isSetter: false, @@ -156,9 +156,10 @@ class _InvocationMatcher implements Matcher { _invocation.memberName == item.memberName && _invocation.isSetter == item.isSetter && _invocation.isGetter == item.isGetter && - const ListEquality(const _MatcherEquality()) + const ListEquality(const _MatcherEquality()) .equals(_invocation.positionalArguments, item.positionalArguments) && - const MapEquality(values: const _MatcherEquality()) + const MapEquality( + values: const _MatcherEquality()) .equals(_invocation.namedArguments, item.namedArguments); } diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 60b04ea84..0b947200f 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -31,7 +31,7 @@ final List _storedArgs = []; final Map _storedNamedArgs = {}; // Hidden from the public API, used by spy.dart. -void setDefaultResponse(Mock mock, CallPair defaultResponse()) { +void setDefaultResponse(Mock mock, CallPair defaultResponse()) { mock._defaultResponse = defaultResponse; } @@ -39,7 +39,8 @@ void setDefaultResponse(Mock mock, CallPair defaultResponse()) { /// /// The default behavior when not using this is to always return `null`. void throwOnMissingStub(Mock mock) { - mock._defaultResponse = () => new CallPair.allInvocations(mock._noSuchMethod); + mock._defaultResponse = + () => new CallPair.allInvocations(mock._noSuchMethod); } /// Extend or mixin this class to mark the implementation as a [Mock]. @@ -75,27 +76,27 @@ void throwOnMissingStub(Mock mock) { /// used within the context of Dart for Web (dart2js, DDC) and Dart for Mobile /// (Flutter). class Mock { - static _answerNull(_) => null; + static Null _answerNull(_) => null; - static const _nullResponse = const CallPair.allInvocations(_answerNull); + static const _nullResponse = const CallPair.allInvocations(_answerNull); final StreamController _invocationStreamController = new StreamController.broadcast(); final _realCalls = []; - final _responses = []; + final _responses = >[]; String _givenName; int _givenHashCode; _ReturnsCannedResponse _defaultResponse = () => _nullResponse; - void _setExpected(CallPair cannedResponse) { + void _setExpected(CallPair cannedResponse) { _responses.add(cannedResponse); } @override @visibleForTesting - noSuchMethod(Invocation invocation) { + dynamic noSuchMethod(Invocation invocation) { // noSuchMethod is that 'magic' that allows us to ignore implementing fields // and methods and instead define them later at compile-time per instance. // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH @@ -120,7 +121,7 @@ class Mock { } } - _noSuchMethod(Invocation invocation) => + dynamic _noSuchMethod(Invocation invocation) => const Object().noSuchMethod(invocation); @override @@ -147,7 +148,7 @@ class Mock { } } -typedef CallPair _ReturnsCannedResponse(); +typedef CallPair _ReturnsCannedResponse(); // When using an [ArgMatcher], we transform our invocation to have knowledge of // which arguments are wrapped, and which ones are not. Otherwise we just use @@ -346,8 +347,8 @@ class PostExpectation { return _completeWhen(answer); } - void _completeWhen(Answering answer) { - _whenCall._setExpected(answer); + void _completeWhen(Answering answer) { + _whenCall._setExpected(answer); _whenCall = null; _whenInProgress = false; } @@ -524,8 +525,8 @@ class _WhenCall { final Invocation whenInvocation; _WhenCall(this.mock, this.whenInvocation); - void _setExpected(Answering answer) { - mock._setExpected(new CallPair(isInvocation(whenInvocation), answer)); + void _setExpected(Answering answer) { + mock._setExpected(new CallPair(isInvocation(whenInvocation), answer)); } } @@ -651,11 +652,11 @@ Null _registerMatcher(Matcher matcher, bool capture, {String named}) { } class VerificationResult { - List captured = []; + List captured = []; int callCount; VerificationResult(this.callCount) { - captured = new List.from(_capturedArgs, growable: false); + captured = new List.from(_capturedArgs, growable: false); _capturedArgs.clear(); } diff --git a/pkgs/mockito/test/deprecated_apis/capture_test.dart b/pkgs/mockito/test/deprecated_apis/capture_test.dart index 89f03f9e8..d20c069c0 100644 --- a/pkgs/mockito/test/deprecated_apis/capture_test.dart +++ b/pkgs/mockito/test/deprecated_apis/capture_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: strong_mode_implicit_dynamic_function + @deprecated library mockito.test.deprecated_apis.capture_test; diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index 73e5b3459..2091b9f7b 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: strong_mode_implicit_dynamic_function + @deprecated library mockito.test.deprecated_apis.mockito_test; @@ -52,7 +54,7 @@ class MockFoo extends AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -expectFail(String expectedMessage, expectedToFail()) { +void expectFail(String expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); fail("It was expected to fail!"); diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart index 1daa1a4fd..9263d6f96 100644 --- a/pkgs/mockito/test/deprecated_apis/until_called_test.dart +++ b/pkgs/mockito/test/deprecated_apis/until_called_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: strong_mode_implicit_dynamic_function + @deprecated library mockito.test.deprecated_apis.until_called_test; diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index a9178104e..219bc31d7 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: strong_mode_implicit_dynamic_function + @deprecated library mockito.test.deprecated_apis.verify_test; @@ -55,7 +57,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -expectFail(String expectedMessage, expectedToFail()) { +void expectFail(String expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); fail('It was expected to fail!'); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index b9d4f8d1f..ef5efa502 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -143,10 +143,10 @@ void main() { abstract class Interface { bool get value; set value(value); - say(String text); - eat(String food, {bool alsoDrink}); - lie([bool facingDown]); - fly({int miles}); + void say(String text); + void eat(String food, {bool alsoDrink}); + void lie([bool facingDown]); + void fly({int miles}); } /// An example of a class that captures Invocation objects. @@ -158,7 +158,7 @@ class Stub implements Interface { const Stub(); @override - noSuchMethod(Invocation invocation) { + void noSuchMethod(Invocation invocation) { lastInvocation = invocation; } } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 4e6a0e5dc..3223cb0d0 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -51,7 +51,7 @@ class _MockFoo extends _AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -expectFail(String expectedMessage, expectedToFail()) { +void expectFail(String expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); fail("It was expected to fail!"); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index aff826813..43e6df8e0 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -54,7 +54,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -expectFail(Pattern expectedMessage, expectedToFail()) { +void expectFail(Pattern expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); fail('It was expected to fail!'); From 7497ca49de7d9c9ae4a639d798606a66b742e0c0 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 3 Jul 2018 13:01:38 -0700 Subject: [PATCH 115/607] Tighten matcher's version constraints to fix dart-lang/mockito#149 --- pkgs/mockito/CHANGELOG.md | 7 ++++++- pkgs/mockito/pubspec.yaml | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f677ae4f7..6d30c6941 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,11 @@ +## 3.0.0-beta+2 + +* Fix matcher package's version constraint to only support versions with `const + TypeMatcher`. + ## 3.0.0-beta+1 -- Upgrade package dependencies. +* Upgrade package dependencies. ## 3.0.0-beta diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 377191315..c02ceb5dc 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0-beta+1 +version: 3.0.0-beta+2 authors: - Dmitriy Fibulwinter @@ -13,7 +13,7 @@ environment: dependencies: collection: ^1.1.0 - matcher: ^0.12.0 + matcher: ^0.12.3 meta: ^1.0.4 test: '>=0.12.25 <2.0.0' From 597b0930cfa2487e308d41694ae6847aff88ee82 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 3 Jul 2018 13:38:14 -0700 Subject: [PATCH 116/607] dartfmt --- pkgs/mockito/lib/src/mock.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 0b947200f..ec0d486dc 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -766,8 +766,8 @@ _InOrderVerification get verifyInOrder { if (allInvocations.isNotEmpty) { otherCalls = " All calls: ${allInvocations.join(", ")}"; } - fail( - "Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls"); + fail("Matching call #${tmpVerifyCalls + .indexOf(verifyCall)} not found.$otherCalls"); } } for (var call in matchedCalls) { From a9b78567c59accd17aa5fa37883760cf459a7ecf Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Sun, 15 Jul 2018 14:05:09 -0400 Subject: [PATCH 117/607] chore: set max SDK version to <3.0.0 (dart-lang/mockito#157) chore: set max SDK version to <3.0.0 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/mock.dart | 4 ++-- pkgs/mockito/pubspec.yaml | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 6d30c6941..ddbb33488 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0-beta+3 + +* Set max SDK version to <3.0.0, and adjusted other dependencies. + ## 3.0.0-beta+2 * Fix matcher package's version constraint to only support versions with `const diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index ec0d486dc..8a398e6e6 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -766,8 +766,8 @@ _InOrderVerification get verifyInOrder { if (allInvocations.isNotEmpty) { otherCalls = " All calls: ${allInvocations.join(", ")}"; } - fail("Matching call #${tmpVerifyCalls - .indexOf(verifyCall)} not found.$otherCalls"); + fail('Matching call #${tmpVerifyCalls.indexOf(verifyCall)} ' + 'not found.$otherCalls'); } } for (var call in matchedCalls) { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c02ceb5dc..89d5c4ad9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0-beta+2 +version: 3.0.0-beta+3 authors: - Dmitriy Fibulwinter @@ -9,15 +9,15 @@ description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.0.0-dev.16.0 <2.0.0' + sdk: '>=2.0.0-dev.16.0 <3.0.0' dependencies: collection: ^1.1.0 matcher: ^0.12.3 meta: ^1.0.4 - test: '>=0.12.25 <2.0.0' + test: ^1.2.0 dev_dependencies: - build_runner: ^0.8.10 + build_runner: ^0.9.1 build_test: ^0.10.0 build_web_compilers: ^0.4.0 From 832ed8e452a98c41781ec1103a8e6aeeade1ac85 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 18 Jul 2018 13:58:09 -0700 Subject: [PATCH 118/607] Throw an error when stubbing a non-mock method (dart-lang/mockito#159) --- pkgs/mockito/lib/src/mock.dart | 4 ++++ pkgs/mockito/test/mockito_test.dart | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 8a398e6e6..760107221 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -348,6 +348,10 @@ class PostExpectation { } void _completeWhen(Answering answer) { + if (_whenCall == null) { + throw new StateError( + 'Mock method was not called within `when()`. Was a real method called?'); + } _whenCall._setExpected(answer); _whenCall = null; _whenInProgress = false; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 3223cb0d0..fc2b62d72 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -45,6 +45,8 @@ abstract class _AbstractFoo implements _Foo { String bar() => baz(); String baz(); + + String quux() => "Real"; } class _MockFoo extends _AbstractFoo with Mock {} @@ -288,6 +290,13 @@ void main() { .thenReturn("99"); }, throwsArgumentError); }); + + test("should throw if attempting to stub a real method", () { + var foo = new _MockFoo(); + expect(() { + when(foo.quux()).thenReturn("Stub"); + }, throwsStateError); + }); }); group("throwOnMissingStub", () { From 54ae7f55f6ca6931a34992163e1c82f34e3c8930 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 24 Jul 2018 11:48:10 -0700 Subject: [PATCH 119/607] Bump to 3.0.0 final (dart-lang/mockito#161) --- pkgs/mockito/CHANGELOG.md | 82 ++++++++++----------------------------- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 22 insertions(+), 62 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ddbb33488..8ba65add3 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,41 +1,20 @@ -## 3.0.0-beta+3 - -* Set max SDK version to <3.0.0, and adjusted other dependencies. - -## 3.0.0-beta+2 - -* Fix matcher package's version constraint to only support versions with `const - TypeMatcher`. - -## 3.0.0-beta+1 - -* Upgrade package dependencies. - -## 3.0.0-beta - -* This release is the first 3.0.0 release featuring the new Mockito 3 API. The - README has been updated, and an [upgrading-to-mockito-3] doc has been added - to help users upgrade. Here's a quick rundown: - - ```dart - // Old API: - when(obj.fn(typed(any)))... - // New API: - when(obj.fn(any))... - - // Old API: - when(obj.fn(foo: typed(any, named: 'foo')))... - // New API: - when(obj.fn(foo: anyNamed('foo')))... - - // Old API: - when(obj.fn(foo: typed(null, named: 'foo')))... - // New API: - when(obj.fn(foo: argThat(isNull, named: 'foo')))... - ``` - -## 3.0.0-alpha+5 - +## 3.0.0 + +* Deprecate the `typed` API; instead of wrapping other Mockito API calls, like + `any`, `argThat`, `captureAny`, and `captureArgThat`, with a call to `typed`, + the regular API calls are to be used by themselves. Passing `any` and + `captureAny` as named arguments must be replaced with `anyNamed()` and + `captureAnyNamed`, respectively. Passing `argThat` and `captureThat` as named + arguments must include the `named` parameter. +* Introduce a backward-and-forward compatible API to help users migrate to + Mockito 3. See more details in the [upgrading-to-mockito-3] doc. +* `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` + is provided. `thenReturn` calls with futures and streams should be changed to + `thenAnswer`. See the README for more information. +* Support stubbing of void methods in Dart 2. +* `thenReturn` and `thenAnswer` now support generics and infer the correct + types from the `when` call. +* Completely remove the mirrors implementation of Mockito (`mirrors.dart`). * Fix compatibility with new [noSuchMethod Forwarding] feature of Dart 2. This is thankfully a mostly backwards-compatible change. This means that this version of Mockito should continue to work: @@ -73,31 +52,12 @@ This used to print `[2, 3]`, because only the second call matched the `verify` call. Now, it will print `[1, null, 2, 3]`, as both real calls contain a value for both the required argument, and the optional argument. - -[noSuchMethod Forwarding]: https://github.com/dart-lang/sdk/blob/master/docs/language/informal/nosuchmethod-forwarding.md - -## 3.0.0-alpha+4 - -* Introduce a backward-and-forward compatible API to help users migrate to - Mockito 3. See more details in the [upgrading-to-mockito-3] doc. +* Upgrade package dependencies. +* Throw an exception when attempting to stub a method on a Mock object that + already exists. [upgrading-to-mockito-3]: https://github.com/dart-lang/mockito/blob/master/upgrading-to-mockito-3.md - -## 3.0.0-alpha+3 - -* `thenReturn` and `thenAnswer` now support generics and infer the correct - types from the `when` call. -* Completely remove the mirrors implementation of Mockito (`mirrors.dart`). - -## 3.0.0-alpha+2 - -* Support stubbing of void methods in Dart 2. - -## 3.0.0-alpha - -* `thenReturn` now throws an `ArgumentError` if either a `Future` or `Stream` - is provided. `thenReturn` calls with futures and streams should be changed to - `thenAnswer`. See the README for more information. +[noSuchMethod Forwarding]: https://github.com/dart-lang/sdk/blob/master/docs/language/informal/nosuchmethod-forwarding.md ## 2.2.0 diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 89d5c4ad9..805351746 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0-beta+3 +version: 3.0.0 authors: - Dmitriy Fibulwinter From 2735188e39f4457c7d0037a6ee73c41e09d62035 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 6 Aug 2018 14:25:41 -0700 Subject: [PATCH 120/607] Add some more capture tests (dart-lang/mockito#162) Add some more capture tests --- pkgs/mockito/test/capture_test.dart | 38 +++++++++++++++++-- .../test/deprecated_apis/capture_test.dart | 3 -- pkgs/mockito/test/mockito_test.dart | 4 -- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 9c1e02b95..b1335b8b0 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -22,6 +22,7 @@ class _RealClass { String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; set setter(String arg) { throw new StateError('I must be mocked'); } @@ -67,6 +68,11 @@ void main() { equals([42])); }); + test('should capture setter invocations', () { + mock.setter = 'value'; + expect(verify(mock.setter = captureAny).captured, equals(['value'])); + }); + test('should capture multiple arguments', () { mock.methodWithPositionalArgs(1, 2); expect( @@ -91,8 +97,34 @@ void main() { expect(verify(mock.methodWithNormalArgs(captureAny)).captured, equals([1, 2])); }); - }); - // TODO(srawlins): Test capturing in a setter. - // TODO(srawlins): Test capturing named arguments. + test('should capture invocations with named arguments', () { + mock.methodWithTwoNamedArgs(1, y: 42, z: 43); + expect( + verify(mock.methodWithTwoNamedArgs(any, + y: captureAnyNamed('y'), z: captureAnyNamed('z'))) + .captured, + equals([42, 43])); + }); + + test('should capture invocations with named arguments', () { + mock.methodWithTwoNamedArgs(1, y: 42, z: 43); + mock.methodWithTwoNamedArgs(1, y: 44, z: 45); + expect( + verify(mock.methodWithTwoNamedArgs(any, + y: captureAnyNamed('y'), z: captureAnyNamed('z'))) + .captured, + equals([42, 43, 44, 45])); + }); + + test('should capture invocations with named arguments', () { + mock.methodWithTwoNamedArgs(1, z: 42, y: 43); + mock.methodWithTwoNamedArgs(1, y: 44, z: 45); + expect( + verify(mock.methodWithTwoNamedArgs(any, + y: captureAnyNamed('y'), z: captureAnyNamed('z'))) + .captured, + equals([43, 42, 44, 45])); + }); + }); } diff --git a/pkgs/mockito/test/deprecated_apis/capture_test.dart b/pkgs/mockito/test/deprecated_apis/capture_test.dart index d20c069c0..11725a9d3 100644 --- a/pkgs/mockito/test/deprecated_apis/capture_test.dart +++ b/pkgs/mockito/test/deprecated_apis/capture_test.dart @@ -101,7 +101,4 @@ void main() { equals([1, 2])); }); }); - - // TODO(srawlins): Test capturing in a setter. - // TODO(srawlins): Test capturing named arguments. } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index fc2b62d72..3369522c8 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -31,9 +31,6 @@ class _RealClass { Future methodReturningFuture() => new Future.value("Real"); Stream methodReturningStream() => new Stream.fromIterable(["Real"]); String get getter => "Real"; - set setter(String arg) { - throw new StateError("I must be mocked"); - } } abstract class _Foo { @@ -213,7 +210,6 @@ void main() { expect(mock == mock, isTrue); }); - //no need to mock setter, except if we will have spies later... test("should mock method with thrown result", () { when(mock.methodWithNormalArgs(any)).thenThrow(new StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); From 7c10009893bed0301f309428a90cec1fc30e233e Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 8 Oct 2018 13:52:24 -0700 Subject: [PATCH 121/607] support latest pkg:build_runner (dart-lang/mockito#165) --- pkgs/mockito/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 805351746..6af1ddf27 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.0 +version: 3.0.1-dev authors: - Dmitriy Fibulwinter @@ -18,6 +18,6 @@ dependencies: test: ^1.2.0 dev_dependencies: - build_runner: ^0.9.1 + build_runner: ^1.0.0 build_test: ^0.10.0 build_web_compilers: ^0.4.0 From a5fb7e0822af3f9552cce3c6541a0265ac642a2c Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 23 Oct 2018 07:36:47 -0700 Subject: [PATCH 122/607] enable pedantic lints (dart-lang/mockito#167) --- pkgs/mockito/analysis_options.yaml | 1 + pkgs/mockito/lib/src/invocation_matcher.dart | 16 ++++++++-------- pkgs/mockito/pubspec.yaml | 3 ++- .../test/deprecated_apis/mockito_test.dart | 2 +- .../test/deprecated_apis/verify_test.dart | 2 +- pkgs/mockito/test/mockito_test.dart | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 02a06eebd..d7cb8a282 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,3 +1,4 @@ +include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-casts: false diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index 085ab7972..9fc9d817a 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -33,10 +33,10 @@ import 'package:mockito/src/mock.dart'; /// what a user expects to be called. Matcher invokes( Symbol memberName, { - List positionalArguments: const [], - Map namedArguments: const {}, - bool isGetter: false, - bool isSetter: false, + List positionalArguments = const [], + Map namedArguments = const {}, + bool isGetter = false, + bool isSetter = false, }) { if (isGetter && isSetter) { throw new ArgumentError('Cannot set isGetter and iSetter'); @@ -80,10 +80,10 @@ class _InvocationSignature extends Invocation { _InvocationSignature({ @required this.memberName, - this.positionalArguments: const [], - this.namedArguments: const {}, - this.isGetter: false, - this.isSetter: false, + this.positionalArguments = const [], + this.namedArguments = const {}, + this.isGetter = false, + this.isSetter = false, }); @override diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 6af1ddf27..b7e2307fe 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -9,7 +9,7 @@ description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.0.0-dev.16.0 <3.0.0' + sdk: '>=2.0.0 <3.0.0' dependencies: collection: ^1.1.0 @@ -21,3 +21,4 @@ dev_dependencies: build_runner: ^1.0.0 build_test: ^0.10.0 build_web_compilers: ^0.4.0 + pedantic: ^1.3.0 diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index 2091b9f7b..e14cfdc50 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -60,7 +60,7 @@ void expectFail(String expectedMessage, dynamic expectedToFail()) { fail("It was expected to fail!"); } catch (e) { if (!(e is TestFailure)) { - throw e; + rethrow; } else { if (expectedMessage != e.message) { throw new TestFailure("Failed, but with wrong message: ${e.message}"); diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index 219bc31d7..abb5b7e4a 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -63,7 +63,7 @@ void expectFail(String expectedMessage, dynamic expectedToFail()) { fail('It was expected to fail!'); } catch (e) { if (!(e is TestFailure)) { - throw e; + rethrow; } else { if (expectedMessage != e.message) { throw new TestFailure('Failed, but with wrong message: ${e.message}'); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 3369522c8..4c2765e55 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -56,7 +56,7 @@ void expectFail(String expectedMessage, dynamic expectedToFail()) { fail("It was expected to fail!"); } catch (e) { if (!(e is TestFailure)) { - throw e; + rethrow; } else { if (expectedMessage != e.message) { throw new TestFailure("Failed, but with wrong message: ${e.message}"); From 185fc6ef1b1a0fef20d605b77fcab459e43f805c Mon Sep 17 00:00:00 2001 From: Will Stubbs Date: Wed, 24 Oct 2018 17:22:15 -0700 Subject: [PATCH 123/607] Fix capture example, parenthesis weren't matched. (dart-lang/mockito#168) Also removed 'new' throughout. --- pkgs/mockito/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index ee3af3a37..18bbc822a 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -33,7 +33,7 @@ class Cat { class MockCat extends Mock implements Cat {} // mock creation -var cat = new MockCat(); +var cat = MockCat(); ``` ## Let's verify some behaviour! @@ -70,7 +70,7 @@ when(cat.lives).thenReturn(9); expect(cat.lives, 9); // You can stub a method to throw: -when(cat.lives).thenThrow(new RangeError('Boo')); +when(cat.lives).thenThrow(RangeError('Boo')); expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time: @@ -107,15 +107,15 @@ Instead, use `thenAnswer` to stub methods that return a `Future` or `Stream`. ``` // BAD when(mock.methodThatReturnsAFuture()) - .thenReturn(new Future.value('Stub')); + .thenReturn(Future.value('Stub')); when(mock.methodThatReturnsAStream()) - .thenReturn(new Stream.fromIterable(['Stub'])); + .thenReturn(Stream.fromIterable(['Stub'])); // GOOD when(mock.methodThatReturnsAFuture()) - .thenAnswer((_) => new Future.value('Stub')); + .thenAnswer((_) => Future.value('Stub')); when(mock.methodThatReturnsAStream()) - .thenAnswer((_) => new Stream.fromIterable(['Stub'])); + .thenAnswer((_) => Stream.fromIterable(['Stub'])); ```` @@ -125,7 +125,7 @@ pre-defined instance. ``` // Use the above method unless you're sure you want to create the Future ahead // of time. -final future = new Future.value('Stub'); +final future = Future.value('Stub'); when(mock.methodThatReturnsAFuture()).thenAnswer((_) => future); ``` @@ -263,7 +263,7 @@ expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); // Conditional capture: cat.eatFood("Milk"); cat.eatFood("Fish"); -expect(verify(cat.eatFood(captureThat(startsWith("F")).captured, ["Fish"]); +expect(verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); ``` ## Waiting for an interaction @@ -348,8 +348,8 @@ matcher for an invocation under the name 'foo'. The same goes for "chaining" mock objects in a test call. This will fail: ```dart -var mockUtils = new MockUtils(); -var mockStringUtils = new MockStringUtils(); +var mockUtils = MockUtils(); +var mockStringUtils = MockStringUtils(); // Setting up mockUtils.stringUtils to return a mock StringUtils implementation when(mockUtils.stringUtils).thenReturn(mockStringUtils); From 87eea880e56fc416d33266f9e906734fdaf0cd2b Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Wed, 31 Oct 2018 13:31:19 -0700 Subject: [PATCH 124/607] Change dependency from test to test_api (dart-lang/mockito#170) change dep from test to test_api --- pkgs/mockito/lib/src/mock.dart | 2 +- pkgs/mockito/pubspec.yaml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 760107221..43b2ec050 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,7 +17,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; -import 'package:test/test.dart'; +import 'package:test_api/test_api.dart'; bool _whenInProgress = false; bool _untilCalledInProgress = false; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b7e2307fe..45ef09b85 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -15,9 +15,10 @@ dependencies: collection: ^1.1.0 matcher: ^0.12.3 meta: ^1.0.4 - test: ^1.2.0 + test_api: ^0.1.1 dev_dependencies: + test: ^1.4.0 build_runner: ^1.0.0 build_test: ^0.10.0 build_web_compilers: ^0.4.0 From 07124421c499cfa0ba1a1c617c37dd9f8ec5a102 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 1 Nov 2018 09:21:22 -0700 Subject: [PATCH 125/607] Bump to 3.0.1 (dart-lang/mockito#171) --- pkgs/mockito/CHANGELOG.md | 8 ++++++++ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 8ba65add3..8c954c8e7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,11 @@ +## 3.0.1 + +* Replace the dependency on the + _[test](https://pub.dartlang.org/packages/test)_ package with a dependency on + the new _[test_api](https://pub.dartlang.org/packages/test_api)_ package. + This dramatically reduces mockito's transitive dependencies. +* Internal improvements to tests and examples. + ## 3.0.0 * Deprecate the `typed` API; instead of wrapping other Mockito API calls, like diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 45ef09b85..db8ed0a34 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.1-dev +version: 3.0.1 authors: - Dmitriy Fibulwinter From b1431cde7a9d91e1b6fc8ecf66f06335d971a008 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Mon, 5 Nov 2018 08:14:29 -0800 Subject: [PATCH 126/607] roll test_api version (dart-lang/mockito#173) --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/pubspec.yaml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 8c954c8e7..1b4b6bc86 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.2 + +* Update the internal version of _[test_api](https://pub.dartlang.org/packages/test_api)_ to + be compatible with _[test_core](https://pub.dartlang.org/packages/test_core)_. + ## 3.0.1 * Replace the dependency on the diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index db8ed0a34..c7a3c62cc 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.1 +version: 3.0.2 authors: - Dmitriy Fibulwinter @@ -15,7 +15,7 @@ dependencies: collection: ^1.1.0 matcher: ^0.12.3 meta: ^1.0.4 - test_api: ^0.1.1 + test_api: ^0.2.0 dev_dependencies: test: ^1.4.0 From 85d767fc61fca68ed6e6b3de62852aef2f474c7e Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 5 Nov 2018 10:44:55 -0800 Subject: [PATCH 127/607] Back to test 1.2.0 for Mockito 3.x. (dart-lang/mockito#174) --- pkgs/mockito/CHANGELOG.md | 5 +++-- pkgs/mockito/lib/src/mock.dart | 2 +- pkgs/mockito/pubspec.yaml | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 1b4b6bc86..1bc586e24 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,7 +1,8 @@ ## 3.0.2 -* Update the internal version of _[test_api](https://pub.dartlang.org/packages/test_api)_ to - be compatible with _[test_core](https://pub.dartlang.org/packages/test_core)_. +* Rollback the _[test_api](https://pub.dartlang.org/packages/test_api)_ part of + the 3.0.1 release. This was breaking tests that use Flutter's current test + tools, and will instead be released as part of Mockito 4.0.0. ## 3.0.1 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 43b2ec050..760107221 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,7 +17,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; -import 'package:test_api/test_api.dart'; +import 'package:test/test.dart'; bool _whenInProgress = false; bool _untilCalledInProgress = false; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c7a3c62cc..768857e24 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -15,10 +15,9 @@ dependencies: collection: ^1.1.0 matcher: ^0.12.3 meta: ^1.0.4 - test_api: ^0.2.0 + test: ^1.2.0 dev_dependencies: - test: ^1.4.0 build_runner: ^1.0.0 build_test: ^0.10.0 build_web_compilers: ^0.4.0 From 914260fc61b0e20d1139117a82e26f14230e5542 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 5 Nov 2018 16:21:07 -0800 Subject: [PATCH 128/607] Use the new test_api package, checking for version incompatibility (dart-lang/mockito#176) Bump to 4.0.0, using the new test_api package --- pkgs/mockito/CHANGELOG.md | 10 +++++++ pkgs/mockito/lib/src/mock.dart | 49 +++++++++++++++++++++++++++++++++- pkgs/mockito/pubspec.yaml | 5 ++-- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 1bc586e24..7548459d6 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,13 @@ +## 4.0.0 + +* Replace the dependency on the + _[test](https://pub.dartlang.org/packages/test)_ package with a dependency on + the new _[test_api](https://pub.dartlang.org/packages/test_api)_ package. + This dramatically reduces mockito's transitive dependencies. + + This bump can result in runtime errors when coupled with a version of the + test package older than 1.4.0. + ## 3.0.2 * Rollback the _[test_api](https://pub.dartlang.org/packages/test_api)_ part of diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 760107221..7df111a89 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,7 +17,11 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; -import 'package:test/test.dart'; +import 'package:test_api/test_api.dart'; +// TODO(srawlins): Remove this when we no longer need to check for an +// incompatiblity between test_api and test. +// https://github.com/dart-lang/mockito/issues/175 +import 'package:test_api/src/backend/invoker.dart'; bool _whenInProgress = false; bool _untilCalledInProgress = false; @@ -659,12 +663,55 @@ class VerificationResult { List captured = []; int callCount; + // Whether the test API mismatch has been checked. + bool _testApiMismatchHasBeenChecked = false; + VerificationResult(this.callCount) { captured = new List.from(_capturedArgs, growable: false); _capturedArgs.clear(); } + /// Check for a version incompatibility between mockito, test, and test_api. + /// + /// This incompatibility results in an inscrutible error for users. Catching + /// it here allows us to give some steps to fix. + // TODO(srawlins): Remove this when we don't need to check for an + // incompatiblity between test_api and test any more. + // https://github.com/dart-lang/mockito/issues/175 + void _checkTestApiMismatch() { + try { + Invoker.current; + } on CastError catch (e) { + if (!e + .toString() + .contains("type 'Invoker' is not a subtype of type 'Invoker'")) { + // Hmm. This is a different CastError from the one we're trying to + // protect against. Let it go. + return; + } + print('Error: Package incompatibility between mockito, test, and ' + 'test_api packages:'); + print(''); + print('* mockito ^4.0.0 is incompatible with test <1.4.0'); + print('* mockito <4.0.0 is incompatible with test ^1.4.0'); + print(''); + print('As mockito does not have a dependency on the test package, ' + 'nothing stopped you from landing in this situation. :( ' + 'Apologies.'); + print(''); + print('To fix: bump your dependency on the test package to something ' + 'like: ^1.4.0'); + rethrow; + } + } + void called(matcher) { + if (!_testApiMismatchHasBeenChecked) { + // Only execute the check below once. `Invoker.current` may look like a + // cheap getter, but it involves Zones and casting. + _testApiMismatchHasBeenChecked = true; + _checkTestApiMismatch(); + } expect(callCount, wrapMatcher(matcher), reason: "Unexpected number of calls"); } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 768857e24..bcb809ca2 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 3.0.2 +version: 4.0.0 authors: - Dmitriy Fibulwinter @@ -15,10 +15,11 @@ dependencies: collection: ^1.1.0 matcher: ^0.12.3 meta: ^1.0.4 - test: ^1.2.0 + test_api: ^0.2.1 dev_dependencies: build_runner: ^1.0.0 build_test: ^0.10.0 build_web_compilers: ^0.4.0 pedantic: ^1.3.0 + test: ^1.5.1 From b9f145cd0d27182999c23b95b93d29718299765a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 5 Nov 2018 16:38:57 -0800 Subject: [PATCH 129/607] Touch up message (dart-lang/mockito#177) --- pkgs/mockito/lib/src/mock.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 7df111a89..0aa604708 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -700,7 +700,8 @@ class VerificationResult { 'Apologies.'); print(''); print('To fix: bump your dependency on the test package to something ' - 'like: ^1.4.0'); + 'like: ^1.4.0, or downgrade your dependency on mockito to something ' + 'like: ^3.0.0'); rethrow; } } From 515db96e8c11d3eccf52c7ea9562f1913296e102 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 27 Nov 2018 10:51:01 -0800 Subject: [PATCH 130/607] Fix pub badge link (dart-lang/mockito#179) --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 18bbc822a..c1b468cdc 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,6 +1,6 @@ Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). -[![Pub](https://img.shields.io/pub/v/mockito.svg)]() +[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dartlang.org/packages/mockito) [![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) Current mock libraries suffer from specifying method names as strings, which From 363db83e7e14f527d4844e189a035993138df461 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 10 Dec 2018 19:37:38 -0800 Subject: [PATCH 131/607] dev dep: latest build_web_compilers (dart-lang/mockito#180) --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index bcb809ca2..8903ae8c7 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -20,6 +20,6 @@ dependencies: dev_dependencies: build_runner: ^1.0.0 build_test: ^0.10.0 - build_web_compilers: ^0.4.0 + build_web_compilers: ^1.0.0 pedantic: ^1.3.0 test: ^1.5.1 From c41ba86f7e42aed1ace0d5f2b2bea9a2b22cf05a Mon Sep 17 00:00:00 2001 From: Ted Sander Date: Tue, 12 Mar 2019 14:23:36 -0700 Subject: [PATCH 132/607] Fix Typo in README. Remove extra ` in code example. --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index c1b468cdc..96daf0143 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -117,7 +117,7 @@ when(mock.methodThatReturnsAFuture()) when(mock.methodThatReturnsAStream()) .thenAnswer((_) => Stream.fromIterable(['Stub'])); -```` +``` If, for some reason, you desire the behavior of `thenReturn`, you can return a pre-defined instance. From 4047cf99beb50670b7d4f67aade29697d3d38392 Mon Sep 17 00:00:00 2001 From: Brandon Duffany Date: Sun, 17 Mar 2019 14:35:12 -0700 Subject: [PATCH 133/607] Improve error-message when verify* is called with null (dart-lang/mockito#190) --- pkgs/mockito/lib/src/mock.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 0aa604708..4d094cb8f 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -828,8 +828,12 @@ _InOrderVerification get verifyInOrder { }; } -void _throwMockArgumentError(method) => - throw new ArgumentError('$method must only be given a Mock object'); +void _throwMockArgumentError(String method, var nonMockInstance) { + if (nonMockInstance == null) { + throw ArgumentError('$method was called with a null argument'); + } + throw ArgumentError('$method must only be given a Mock object'); +} void verifyNoMoreInteractions(var mock) { if (mock is Mock) { @@ -838,7 +842,7 @@ void verifyNoMoreInteractions(var mock) { fail("No more calls expected, but following found: " + unverified.join()); } } else { - _throwMockArgumentError('verifyNoMoreInteractions'); + _throwMockArgumentError('verifyNoMoreInteractions', mock); } } @@ -849,7 +853,7 @@ void verifyZeroInteractions(var mock) { mock._realCalls.join()); } } else { - _throwMockArgumentError('verifyZeroInteractions'); + _throwMockArgumentError('verifyZeroInteractions', mock); } } From 1af90b82e4c850f572f2eb3f6b209edf1cc62be4 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 9 Apr 2019 08:01:32 -0700 Subject: [PATCH 134/607] fix formatting --- pkgs/mockito/analysis_options.yaml | 3 + pkgs/mockito/lib/mockito.dart | 6 +- pkgs/mockito/lib/src/call_pair.dart | 2 +- pkgs/mockito/lib/src/invocation_matcher.dart | 17 ++-- pkgs/mockito/lib/src/mock.dart | 79 +++++++++---------- pkgs/mockito/test/capture_test.dart | 4 +- .../test/deprecated_apis/capture_test.dart | 4 +- .../test/deprecated_apis/mockito_test.dart | 13 ++- .../deprecated_apis/until_called_test.dart | 22 +++--- .../test/deprecated_apis/verify_test.dart | 6 +- pkgs/mockito/test/example/iss/iss.dart | 2 +- pkgs/mockito/test/example/iss/iss_test.dart | 24 +++--- .../mockito/test/invocation_matcher_test.dart | 26 +++--- pkgs/mockito/test/mockito_test.dart | 34 ++++---- pkgs/mockito/test/until_called_test.dart | 34 ++++---- pkgs/mockito/test/utils.dart | 2 +- pkgs/mockito/test/verify_test.dart | 24 +++--- 17 files changed, 148 insertions(+), 154 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index d7cb8a282..1cb9f53f4 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -32,6 +32,9 @@ linter: - library_names - library_prefixes - non_constant_identifier_names + - prefer_generic_function_type_aliases - prefer_is_not_empty - slash_for_doc_comments - type_init_formals + - unnecessary_const + - unnecessary_new diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 14a506f43..046f68c5f 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -39,9 +39,9 @@ export 'src/mock.dart' Verification, // -- deprecated - typed, // ignore: deprecated_member_use - typedArgThat, // ignore: deprecated_member_use - typedCaptureThat, // ignore: deprecated_member_use + typed, // ignore: deprecated_member_use_from_same_package + typedArgThat, // ignore: deprecated_member_use_from_same_package + typedCaptureThat, // ignore: deprecated_member_use_from_same_package // -- misc throwOnMissingStub, diff --git a/pkgs/mockito/lib/src/call_pair.dart b/pkgs/mockito/lib/src/call_pair.dart index 26b258b6d..3c9610c85 100644 --- a/pkgs/mockito/lib/src/call_pair.dart +++ b/pkgs/mockito/lib/src/call_pair.dart @@ -15,7 +15,7 @@ import 'package:matcher/matcher.dart'; /// Returns a value dependent on the details of an [invocation]. -typedef T Answer(Invocation invocation); +typedef Answer = T Function(Invocation invocation); /// A captured method or property accessor -> a function that returns a value. class CallPair { diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index 9fc9d817a..0b34f4644 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -39,15 +39,15 @@ Matcher invokes( bool isSetter = false, }) { if (isGetter && isSetter) { - throw new ArgumentError('Cannot set isGetter and iSetter'); + throw ArgumentError('Cannot set isGetter and iSetter'); } if (positionalArguments == null) { - throw new ArgumentError.notNull('positionalArguments'); + throw ArgumentError.notNull('positionalArguments'); } if (namedArguments == null) { - throw new ArgumentError.notNull('namedArguments'); + throw ArgumentError.notNull('namedArguments'); } - return new _InvocationMatcher(new _InvocationSignature( + return _InvocationMatcher(_InvocationSignature( memberName: memberName, positionalArguments: positionalArguments, namedArguments: namedArguments, @@ -59,8 +59,7 @@ Matcher invokes( /// Returns a matcher that matches the name and arguments of an [invocation]. /// /// To expect the same _signature_ see [invokes]. -Matcher isInvocation(Invocation invocation) => - new _InvocationMatcher(invocation); +Matcher isInvocation(Invocation invocation) => _InvocationMatcher(invocation); class _InvocationSignature extends Invocation { @override @@ -131,7 +130,7 @@ class _InvocationMatcher implements Matcher { _InvocationMatcher(this._invocation) { if (_invocation == null) { - throw new ArgumentError.notNull(); + throw ArgumentError.notNull(); } } @@ -156,10 +155,10 @@ class _InvocationMatcher implements Matcher { _invocation.memberName == item.memberName && _invocation.isSetter == item.isSetter && _invocation.isGetter == item.isGetter && - const ListEquality(const _MatcherEquality()) + const ListEquality(_MatcherEquality()) .equals(_invocation.positionalArguments, item.positionalArguments) && const MapEquality( - values: const _MatcherEquality()) + values: _MatcherEquality()) .equals(_invocation.namedArguments, item.namedArguments); } diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 4d094cb8f..9672cc6bd 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -29,7 +29,7 @@ bool _verificationInProgress = false; _WhenCall _whenCall; _UntilCall _untilCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; -final _TimeStampProvider _timer = new _TimeStampProvider(); +final _TimeStampProvider _timer = _TimeStampProvider(); final List _capturedArgs = []; final List _storedArgs = []; final Map _storedNamedArgs = {}; @@ -44,7 +44,7 @@ void setDefaultResponse(Mock mock, CallPair defaultResponse()) { /// The default behavior when not using this is to always return `null`. void throwOnMissingStub(Mock mock) { mock._defaultResponse = - () => new CallPair.allInvocations(mock._noSuchMethod); + () => CallPair.allInvocations(mock._noSuchMethod); } /// Extend or mixin this class to mark the implementation as a [Mock]. @@ -82,10 +82,10 @@ void throwOnMissingStub(Mock mock) { class Mock { static Null _answerNull(_) => null; - static const _nullResponse = const CallPair.allInvocations(_answerNull); + static const _nullResponse = CallPair.allInvocations(_answerNull); final StreamController _invocationStreamController = - new StreamController.broadcast(); + StreamController.broadcast(); final _realCalls = []; final _responses = >[]; @@ -106,16 +106,16 @@ class Mock { // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH invocation = _useMatchedInvocationIfSet(invocation); if (_whenInProgress) { - _whenCall = new _WhenCall(this, invocation); + _whenCall = _WhenCall(this, invocation); return null; } else if (_verificationInProgress) { - _verifyCalls.add(new _VerifyCall(this, invocation)); + _verifyCalls.add(_VerifyCall(this, invocation)); return null; } else if (_untilCalledInProgress) { - _untilCall = new _UntilCall(this, invocation); + _untilCall = _UntilCall(this, invocation); return null; } else { - _realCalls.add(new RealCall(this, invocation)); + _realCalls.add(RealCall(this, invocation)); _invocationStreamController.add(invocation); var cannedResponse = _responses.lastWhere( (cr) => cr.call.matches(invocation, {}), @@ -152,14 +152,14 @@ class Mock { } } -typedef CallPair _ReturnsCannedResponse(); +typedef _ReturnsCannedResponse = CallPair Function(); // When using an [ArgMatcher], we transform our invocation to have knowledge of // which arguments are wrapped, and which ones are not. Otherwise we just use // the existing invocation object. Invocation _useMatchedInvocationIfSet(Invocation invocation) { if (_storedArgs.isNotEmpty || _storedNamedArgs.isNotEmpty) { - invocation = new _InvocationForMatchedArguments(invocation); + invocation = _InvocationForMatchedArguments(invocation); } return invocation; } @@ -182,7 +182,7 @@ class _InvocationForMatchedArguments extends Invocation { factory _InvocationForMatchedArguments(Invocation invocation) { if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) { - throw new StateError( + throw StateError( "_InvocationForMatchedArguments called when no ArgMatchers have been saved."); } @@ -196,7 +196,7 @@ class _InvocationForMatchedArguments extends Invocation { _storedArgs.clear(); _storedNamedArgs.clear(); - return new _InvocationForMatchedArguments._( + return _InvocationForMatchedArguments._( invocation.memberName, positionalArguments, namedArguments, @@ -213,7 +213,7 @@ class _InvocationForMatchedArguments extends Invocation { static Map _reconstituteNamedArgs(Invocation invocation) { var namedArguments = {}; var _storedNamedArgSymbols = - _storedNamedArgs.keys.map((name) => new Symbol(name)); + _storedNamedArgs.keys.map((name) => Symbol(name)); // Iterate through [invocation]'s named args, validate them, and add them // to the return map. @@ -234,12 +234,12 @@ class _InvocationForMatchedArguments extends Invocation { // Iterate through the stored named args, validate them, and add them to // the return map. _storedNamedArgs.forEach((name, arg) { - Symbol nameSymbol = new Symbol(name); + Symbol nameSymbol = Symbol(name); if (!invocation.namedArguments.containsKey(nameSymbol)) { // Clear things out for the next call. _storedArgs.clear(); _storedNamedArgs.clear(); - throw new ArgumentError( + throw ArgumentError( 'An ArgumentMatcher was declared as named $name, but was not ' 'passed as an argument named $name.\n\n' 'BAD: when(obj.fn(anyNamed: "a")))\n' @@ -249,7 +249,7 @@ class _InvocationForMatchedArguments extends Invocation { // Clear things out for the next call. _storedArgs.clear(); _storedNamedArgs.clear(); - throw new ArgumentError( + throw ArgumentError( 'An ArgumentMatcher was declared as named $name, but a different ' 'value (${invocation.namedArguments[nameSymbol]}) was passed as ' '$name.\n\n' @@ -274,7 +274,7 @@ class _InvocationForMatchedArguments extends Invocation { // `when(obj.fn(a: any))`. _storedArgs.clear(); _storedNamedArgs.clear(); - throw new ArgumentError( + throw ArgumentError( 'An argument matcher (like `any`) was used as a named argument, but ' 'did not use a Mockito "named" API. Each argument matcher that is ' 'used as a named argument needs to specify the name of the argument ' @@ -329,13 +329,11 @@ void clearInteractions(var mock) { class PostExpectation { void thenReturn(T expected) { if (expected is Future) { - throw new ArgumentError( - '`thenReturn` should not be used to return a Future. ' + throw ArgumentError('`thenReturn` should not be used to return a Future. ' 'Instead, use `thenAnswer((_) => future)`.'); } if (expected is Stream) { - throw new ArgumentError( - '`thenReturn` should not be used to return a Stream. ' + throw ArgumentError('`thenReturn` should not be used to return a Stream. ' 'Instead, use `thenAnswer((_) => stream)`.'); } return _completeWhen((_) => expected); @@ -353,7 +351,7 @@ class PostExpectation { void _completeWhen(Answering answer) { if (_whenCall == null) { - throw new StateError( + throw StateError( 'Mock method was not called within `when()`. Was a real method called?'); } _whenCall._setExpected(answer); @@ -453,9 +451,9 @@ class InvocationMatcher { class _TimeStampProvider { int _now = 0; DateTime now() { - var candidate = new DateTime.now(); + var candidate = DateTime.now(); if (candidate.millisecondsSinceEpoch <= _now) { - candidate = new DateTime.fromMillisecondsSinceEpoch(_now + 1); + candidate = DateTime.fromMillisecondsSinceEpoch(_now + 1); } _now = candidate.millisecondsSinceEpoch; return candidate; @@ -511,8 +509,7 @@ class RealCall { } else if (invocation.isSetter) { method = '$method=$argString'; } else { - throw new StateError( - 'Invocation should be getter, setter or a method call.'); + throw StateError('Invocation should be getter, setter or a method call.'); } var verifiedText = verified ? '[VERIFIED] ' : ''; @@ -534,7 +531,7 @@ class _WhenCall { _WhenCall(this.mock, this.whenInvocation); void _setExpected(Answering answer) { - mock._setExpected(new CallPair(isInvocation(whenInvocation), answer)); + mock._setExpected(CallPair(isInvocation(whenInvocation), answer)); } } @@ -543,7 +540,7 @@ class _UntilCall { final Mock _mock; _UntilCall(this._mock, Invocation invocation) - : _invocationMatcher = new InvocationMatcher(invocation); + : _invocationMatcher = InvocationMatcher(invocation); bool _matchesInvocation(RealCall realCall) => _invocationMatcher.matches(realCall.invocation); @@ -552,8 +549,7 @@ class _UntilCall { Future get invocationFuture { if (_realCalls.any(_matchesInvocation)) { - return new Future.value( - _realCalls.firstWhere(_matchesInvocation).invocation); + return Future.value(_realCalls.firstWhere(_matchesInvocation).invocation); } return _mock._invocationStreamController.stream @@ -567,7 +563,7 @@ class _VerifyCall { List matchingInvocations; _VerifyCall(this.mock, this.verifyInvocation) { - var expectedMatcher = new InvocationMatcher(verifyInvocation); + var expectedMatcher = InvocationMatcher(verifyInvocation); matchingInvocations = mock._realCalls.where((RealCall recordedInvocation) { return !recordedInvocation.verified && expectedMatcher.matches(recordedInvocation.invocation); @@ -650,7 +646,7 @@ Null typedCaptureThat(Matcher matcher, {String named}) => captureThat(matcher, named: named); Null _registerMatcher(Matcher matcher, bool capture, {String named}) { - var argMatcher = new ArgMatcher(matcher, capture); + var argMatcher = ArgMatcher(matcher, capture); if (named == null) { _storedArgs.add(argMatcher); } else { @@ -667,7 +663,7 @@ class VerificationResult { bool _testApiMismatchHasBeenChecked = false; VerificationResult(this.callCount) { - captured = new List.from(_capturedArgs, growable: false); + captured = List.from(_capturedArgs, growable: false); _capturedArgs.clear(); } @@ -718,7 +714,7 @@ class VerificationResult { } } -typedef T Answering(Invocation realInvocation); +typedef Answering = T Function(Invocation realInvocation); typedef Verification = VerificationResult Function(T matchingInvocations); @@ -774,15 +770,14 @@ Verification _makeVerify(bool never) { '$message ${_verifyCalls.length} verify calls have been stored. ' '[${_verifyCalls.first}, ..., ${_verifyCalls.last}]'; } - throw new StateError(message); + throw StateError(message); } _verificationInProgress = true; return (T mock) { _verificationInProgress = false; if (_verifyCalls.length == 1) { _VerifyCall verifyCall = _verifyCalls.removeLast(); - var result = - new VerificationResult(verifyCall.matchingInvocations.length); + var result = VerificationResult(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; } else { @@ -793,13 +788,13 @@ Verification _makeVerify(bool never) { _InOrderVerification get verifyInOrder { if (_verifyCalls.isNotEmpty) { - throw new StateError(_verifyCalls.join()); + throw StateError(_verifyCalls.join()); } _verificationInProgress = true; return (List _) { _verificationInProgress = false; - DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0); - var tmpVerifyCalls = new List<_VerifyCall>.from(_verifyCalls); + DateTime dt = DateTime.fromMillisecondsSinceEpoch(0); + var tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); _verifyCalls.clear(); List matchedCalls = []; for (_VerifyCall verifyCall in tmpVerifyCalls) { @@ -878,12 +873,12 @@ typedef Expectation = PostExpectation Function(T x); /// See the README for more information. Expectation get when { if (_whenCall != null) { - throw new StateError('Cannot call `when` within a stub response'); + throw StateError('Cannot call `when` within a stub response'); } _whenInProgress = true; return (T _) { _whenInProgress = false; - return new PostExpectation(); + return PostExpectation(); }; } diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index b1335b8b0..0a3ae7b66 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -24,7 +24,7 @@ class _RealClass { String methodWithPositionalArgs(int x, [int y]) => 'Real'; String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; set setter(String arg) { - throw new StateError('I must be mocked'); + throw StateError('I must be mocked'); } } @@ -36,7 +36,7 @@ void main() { var isNsmForwarding = assessNsmForwarding(); setUp(() { - mock = new _MockedClass(); + mock = _MockedClass(); }); tearDown(() { diff --git a/pkgs/mockito/test/deprecated_apis/capture_test.dart b/pkgs/mockito/test/deprecated_apis/capture_test.dart index 11725a9d3..c4b265792 100644 --- a/pkgs/mockito/test/deprecated_apis/capture_test.dart +++ b/pkgs/mockito/test/deprecated_apis/capture_test.dart @@ -28,7 +28,7 @@ class _RealClass { String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; set setter(String arg) { - throw new StateError('I must be mocked'); + throw StateError('I must be mocked'); } } @@ -40,7 +40,7 @@ void main() { var isNsmForwarding = assessNsmForwarding(); setUp(() { - mock = new MockedClass(); + mock = MockedClass(); }); tearDown(() { diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index e14cfdc50..f7bf7bc35 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -31,11 +31,11 @@ class _RealClass { String methodWithNamedArgs(int x, {int y}) => "Real"; String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; String methodWithObjArgs(_RealClass x) => "Real"; - Future methodReturningFuture() => new Future.value("Real"); - Stream methodReturningStream() => new Stream.fromIterable(["Real"]); + Future methodReturningFuture() => Future.value("Real"); + Stream methodReturningStream() => Stream.fromIterable(["Real"]); String get getter => "Real"; set setter(String arg) { - throw new StateError("I must be mocked"); + throw StateError("I must be mocked"); } } @@ -63,7 +63,7 @@ void expectFail(String expectedMessage, dynamic expectedToFail()) { rethrow; } else { if (expectedMessage != e.message) { - throw new TestFailure("Failed, but with wrong message: ${e.message}"); + throw TestFailure("Failed, but with wrong message: ${e.message}"); } } } @@ -76,7 +76,7 @@ void main() { _MockedClass mock; setUp(() { - mock = new _MockedClass(); + mock = _MockedClass(); }); tearDown(() { @@ -116,8 +116,7 @@ void main() { //no need to mock setter, except if we will have spies later... test("should mock method with thrown result", () { - when(mock.methodWithNormalArgs(typed(any))) - .thenThrow(new StateError('Boo')); + when(mock.methodWithNormalArgs(typed(any))).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart index 9263d6f96..92ed48759 100644 --- a/pkgs/mockito/test/deprecated_apis/until_called_test.dart +++ b/pkgs/mockito/test/deprecated_apis/until_called_test.dart @@ -39,7 +39,7 @@ class _RealClass { 'Real'; String get getter => 'Real'; set setter(String arg) { - throw new StateError('I must be mocked'); + throw StateError('I must be mocked'); } } @@ -62,7 +62,7 @@ class _RealClassController { ..methodWithPositionalArgs(1, 2) ..methodWithNamedArgs(1, y: 2) ..methodWithTwoNamedArgs(1, y: 2, z: 3) - ..methodWithObjArgs(new _RealClass()) + ..methodWithObjArgs(_RealClass()) ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) ..getter @@ -76,7 +76,7 @@ void main() { MockedClass mock; setUp(() { - mock = new MockedClass(); + mock = MockedClass(); }); tearDown(() { @@ -87,7 +87,7 @@ void main() { group('untilCalled', () { StreamController streamController = - new StreamController.broadcast(); + StreamController.broadcast(); group('on methods already called', () { test('waits for method with normal args', () async { @@ -124,7 +124,7 @@ void main() { }); test('waits for method with obj args', () async { - mock.methodWithObjArgs(new _RealClass()); + mock.methodWithObjArgs(_RealClass()); await untilCalled(mock.methodWithObjArgs(typed(any))); @@ -145,11 +145,11 @@ void main() { group('on methods not yet called', () { setUp(() { - new _RealClassController(mock, streamController); + _RealClassController(mock, streamController); }); test('waits for method with normal args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithNormalArgs(typed(any))); await untilCalled(mock.methodWithNormalArgs(typed(any))); @@ -158,7 +158,7 @@ void main() { }); test('waits for method with list args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithListArgs(typed(any))); await untilCalled(mock.methodWithListArgs(typed(any))); @@ -167,7 +167,7 @@ void main() { }); test('waits for method with positional args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithPositionalArgs(typed(any), typed(any))); await untilCalled( @@ -177,7 +177,7 @@ void main() { }); test('waits for method with obj args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithObjArgs(typed(any))); await untilCalled(mock.methodWithObjArgs(typed(any))); @@ -186,7 +186,7 @@ void main() { }); test('waits for function with positional parameters', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.typeParameterizedFn( typed(any), typed(any), typed(any), typed(any))); diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index abb5b7e4a..21a5fb4ba 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -32,7 +32,7 @@ class _RealClass { String methodWithObjArgs(_RealClass x) => 'Real'; String get getter => 'Real'; set setter(String arg) { - throw new StateError('I must be mocked'); + throw StateError('I must be mocked'); } String methodWithLongArgs(LongToString a, LongToString b, @@ -66,7 +66,7 @@ void expectFail(String expectedMessage, dynamic expectedToFail()) { rethrow; } else { if (expectedMessage != e.message) { - throw new TestFailure('Failed, but with wrong message: ${e.message}'); + throw TestFailure('Failed, but with wrong message: ${e.message}'); } } } @@ -79,7 +79,7 @@ void main() { _MockedClass mock; setUp(() { - mock = new _MockedClass(); + mock = _MockedClass(); }); tearDown(() { diff --git a/pkgs/mockito/test/example/iss/iss.dart b/pkgs/mockito/test/example/iss/iss.dart index 889988d5f..e932fd9e0 100644 --- a/pkgs/mockito/test/example/iss/iss.dart +++ b/pkgs/mockito/test/example/iss/iss.dart @@ -45,7 +45,7 @@ class IssLocator { var data = jsonDecode(rs.body); var latitude = double.parse(data['iss_position']['latitude'] as String); var longitude = double.parse(data['iss_position']['longitude'] as String); - _position = new Point(latitude, longitude); + _position = Point(latitude, longitude); } } diff --git a/pkgs/mockito/test/example/iss/iss_test.dart b/pkgs/mockito/test/example/iss/iss_test.dart index a971b7bf9..3aa2a6247 100644 --- a/pkgs/mockito/test/example/iss/iss_test.dart +++ b/pkgs/mockito/test/example/iss/iss_test.dart @@ -27,8 +27,8 @@ void main() { // verify the calculated distance between them. group('Spherical distance', () { test('London - Paris', () { - Point london = new Point(51.5073, -0.1277); - Point paris = new Point(48.8566, 2.3522); + Point london = Point(51.5073, -0.1277); + Point paris = Point(48.8566, 2.3522); double d = sphericalDistanceKm(london, paris); // London should be approximately 343.5km // (+/- 0.1km) from Paris. @@ -36,8 +36,8 @@ void main() { }); test('San Francisco - Mountain View', () { - Point sf = new Point(37.783333, -122.416667); - Point mtv = new Point(37.389444, -122.081944); + Point sf = Point(37.783333, -122.416667); + Point mtv = Point(37.389444, -122.081944); double d = sphericalDistanceKm(sf, mtv); // San Francisco should be approximately 52.8km // (+/- 0.1km) from Mountain View. @@ -52,24 +52,24 @@ void main() { // second predefined location. This test runs asynchronously. group('ISS spotter', () { test('ISS visible', () async { - Point sf = new Point(37.783333, -122.416667); - Point mtv = new Point(37.389444, -122.081944); - IssLocator locator = new MockIssLocator(); + Point sf = Point(37.783333, -122.416667); + Point mtv = Point(37.389444, -122.081944); + IssLocator locator = MockIssLocator(); // Mountain View should be visible from San Francisco. when(locator.currentPosition).thenReturn(sf); - var spotter = new IssSpotter(locator, mtv); + var spotter = IssSpotter(locator, mtv); expect(spotter.isVisible, true); }); test('ISS not visible', () async { - Point london = new Point(51.5073, -0.1277); - Point mtv = new Point(37.389444, -122.081944); - IssLocator locator = new MockIssLocator(); + Point london = Point(51.5073, -0.1277); + Point mtv = Point(37.389444, -122.081944); + IssLocator locator = MockIssLocator(); // London should not be visible from Mountain View. when(locator.currentPosition).thenReturn(london); - var spotter = new IssSpotter(locator, mtv); + var spotter = IssSpotter(locator, mtv); expect(spotter.isVisible, false); }); }); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index ef5efa502..12eae076b 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -18,7 +18,7 @@ import 'package:test/test.dart'; Invocation lastInvocation; void main() { - const stub = const Stub(); + const stub = Stub(); group('$isInvocation', () { test('positional arguments', () { @@ -33,8 +33,8 @@ void main() { call1, isInvocation(call3), "Expected: say('Guten Tag') " - "Actual: " - "Which: Does not match say('Hello')", + "Actual: " + "Which: Does not match say('Hello')", ); }); @@ -50,8 +50,8 @@ void main() { call1, isInvocation(call3), "Expected: eat('Chicken', 'alsoDrink: false') " - "Actual: " - "Which: Does not match eat('Chicken', 'alsoDrink: true')", + "Actual: " + "Which: Does not match eat('Chicken', 'alsoDrink: true')", ); }); @@ -67,8 +67,8 @@ void main() { call1, isInvocation(call3), "Expected: lie() " - "Actual: " - "Which: Does not match lie()", + "Actual: " + "Which: Does not match lie()", ); }); @@ -84,7 +84,7 @@ void main() { call1, isInvocation(call3), // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - new RegExp('Expected: set value=? ' + RegExp('Expected: set value=? ' "Actual: " 'Which: Does not match get value'), ); @@ -102,7 +102,7 @@ void main() { call1, isInvocation(call3), // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - new RegExp("Expected: set value=? " + RegExp("Expected: set value=? " "Actual: " "Which: Does not match set value=? "), ); @@ -119,8 +119,8 @@ void main() { call, invokes(#say, positionalArguments: [isNull]), "Expected: say(null) " - "Actual: " - "Which: Does not match say('Hello')", + "Actual: " + "Which: Does not match say('Hello')", ); }); @@ -133,8 +133,8 @@ void main() { call, invokes(#fly, namedArguments: {#miles: 11}), "Expected: fly('miles: 11') " - "Actual: " - "Which: Does not match fly('miles: 10')", + "Actual: " + "Which: Does not match fly('miles: 10')", ); }); }); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 4c2765e55..a41300983 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -28,8 +28,8 @@ class _RealClass { String methodWithNamedArgs(int x, {int y}) => "Real"; String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; String methodWithObjArgs(_RealClass x) => "Real"; - Future methodReturningFuture() => new Future.value("Real"); - Stream methodReturningStream() => new Stream.fromIterable(["Real"]); + Future methodReturningFuture() => Future.value("Real"); + Stream methodReturningStream() => Stream.fromIterable(["Real"]); String get getter => "Real"; } @@ -59,7 +59,7 @@ void expectFail(String expectedMessage, dynamic expectedToFail()) { rethrow; } else { if (expectedMessage != e.message) { - throw new TestFailure("Failed, but with wrong message: ${e.message}"); + throw TestFailure("Failed, but with wrong message: ${e.message}"); } } } @@ -74,7 +74,7 @@ void main() { var isNsmForwarding = assessNsmForwarding(); setUp(() { - mock = new _MockedClass(); + mock = _MockedClass(); }); tearDown(() { @@ -85,7 +85,7 @@ void main() { group("mixin support", () { test("should work", () { - var foo = new _MockFoo(); + var foo = _MockFoo(); when(foo.baz()).thenReturn('baz'); expect(foo.bar(), 'baz'); }); @@ -104,9 +104,9 @@ void main() { }); test("should mock method with mock args", () { - var m1 = new _MockedClass(); + var m1 = _MockedClass(); when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); - expect(mock.methodWithObjArgs(new _MockedClass()), isNull); + expect(mock.methodWithObjArgs(_MockedClass()), isNull); expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); }); @@ -199,19 +199,19 @@ void main() { }); test("should mock equals between mocks when givenHashCode is equals", () { - var anotherMock = named(new _MockedClass(), hashCode: 42); + var anotherMock = named(_MockedClass(), hashCode: 42); named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); }); test("should use identical equality between it is not mocked", () { - var anotherMock = new _MockedClass(); + var anotherMock = _MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); test("should mock method with thrown result", () { - when(mock.methodWithNormalArgs(any)).thenThrow(new StateError('Boo')); + when(mock.methodWithNormalArgs(any)).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); @@ -223,7 +223,7 @@ void main() { }); test("should return mock to make simple oneline mocks", () { - _RealClass mockWithSetup = new _MockedClass(); + _RealClass mockWithSetup = _MockedClass(); when(mockWithSetup.methodWithoutArgs()).thenReturn("oneline"); expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); }); @@ -244,7 +244,7 @@ void main() { test("should throw if `when` is called while stubbing", () { expect(() { var responseHelper = () { - var mock2 = new _MockedClass(); + var mock2 = _MockedClass(); when(mock2.getter).thenReturn("A"); return mock2; }; @@ -255,27 +255,27 @@ void main() { test("thenReturn throws if provided Future", () { expect( () => when(mock.methodReturningFuture()) - .thenReturn(new Future.value("stub")), + .thenReturn(Future.value("stub")), throwsArgumentError); }); test("thenReturn throws if provided Stream", () { expect( () => when(mock.methodReturningStream()) - .thenReturn(new Stream.fromIterable(["stub"])), + .thenReturn(Stream.fromIterable(["stub"])), throwsArgumentError); }); test("thenAnswer supports stubbing method returning a Future", () async { when(mock.methodReturningFuture()) - .thenAnswer((_) => new Future.value("stub")); + .thenAnswer((_) => Future.value("stub")); expect(await mock.methodReturningFuture(), "stub"); }); test("thenAnswer supports stubbing method returning a Stream", () async { when(mock.methodReturningStream()) - .thenAnswer((_) => new Stream.fromIterable(["stub"])); + .thenAnswer((_) => Stream.fromIterable(["stub"])); expect(await mock.methodReturningStream().toList(), ["stub"]); }); @@ -288,7 +288,7 @@ void main() { }); test("should throw if attempting to stub a real method", () { - var foo = new _MockFoo(); + var foo = _MockFoo(); expect(() { when(foo.quux()).thenReturn("Stub"); }, throwsStateError); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index 24e31a3c1..634c59c0f 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -34,7 +34,7 @@ class _RealClass { 'Real'; String get getter => 'Real'; set setter(String arg) { - throw new StateError('I must be mocked'); + throw StateError('I must be mocked'); } } @@ -57,7 +57,7 @@ class _RealClassController { ..methodWithPositionalArgs(1, 2) ..methodWithNamedArgs(1, y: 2) ..methodWithTwoNamedArgs(1, y: 2, z: 3) - ..methodWithObjArgs(new _RealClass()) + ..methodWithObjArgs(_RealClass()) ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) ..getter @@ -71,7 +71,7 @@ void main() { _MockedClass mock; setUp(() { - mock = new _MockedClass(); + mock = _MockedClass(); }); tearDown(() { @@ -82,7 +82,7 @@ void main() { group('untilCalled', () { StreamController streamController = - new StreamController.broadcast(); + StreamController.broadcast(); group('on methods already called', () { test('waits for method without args', () async { @@ -137,7 +137,7 @@ void main() { }); test('waits for method with obj args', () async { - mock.methodWithObjArgs(new _RealClass()); + mock.methodWithObjArgs(_RealClass()); await untilCalled(mock.methodWithObjArgs(any)); @@ -182,11 +182,11 @@ void main() { group('on methods not yet called', () { setUp(() { - new _RealClassController(mock, streamController); + _RealClassController(mock, streamController); }); test('waits for method without args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithoutArgs()); await untilCalled(mock.methodWithoutArgs()); @@ -195,7 +195,7 @@ void main() { }); test('waits for method with normal args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithNormalArgs(any)); await untilCalled(mock.methodWithNormalArgs(any)); @@ -204,7 +204,7 @@ void main() { }); test('waits for method with list args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithListArgs(any)); await untilCalled(mock.methodWithListArgs(any)); @@ -213,7 +213,7 @@ void main() { }); test('waits for method with positional args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithPositionalArgs(any, any)); await untilCalled(mock.methodWithPositionalArgs(any, any)); @@ -222,7 +222,7 @@ void main() { }); test('waits for method with named args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithNamedArgs(any, y: anyNamed('y'))); await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); @@ -231,7 +231,7 @@ void main() { }); test('waits for method with two named args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'), z: anyNamed('z'))); @@ -244,7 +244,7 @@ void main() { }); test('waits for method with obj args', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.methodWithObjArgs(any)); await untilCalled(mock.methodWithObjArgs(any)); @@ -253,7 +253,7 @@ void main() { }); test('waits for function with positional parameters', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.typeParameterizedFn(any, any, any, any)); await untilCalled(mock.typeParameterizedFn(any, any, any, any)); @@ -262,7 +262,7 @@ void main() { }); test('waits for function with named parameters', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.typeParameterizedNamedFn(any, any, y: anyNamed('y'), z: anyNamed('z'))); @@ -275,7 +275,7 @@ void main() { }); test('waits for getter', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.getter); await untilCalled(mock.getter); @@ -284,7 +284,7 @@ void main() { }); test('waits for setter', () async { - streamController.add(new CallMethodsEvent()); + streamController.add(CallMethodsEvent()); verifyNever(mock.setter = 'A'); await untilCalled(mock.setter = 'A'); diff --git a/pkgs/mockito/test/utils.dart b/pkgs/mockito/test/utils.dart index 715ba3b79..86cb4ecf0 100644 --- a/pkgs/mockito/test/utils.dart +++ b/pkgs/mockito/test/utils.dart @@ -7,7 +7,7 @@ abstract class NsmForwardingSignal { class MockNsmForwardingSignal extends Mock implements NsmForwardingSignal {} bool assessNsmForwarding() { - var signal = new MockNsmForwardingSignal(); + var signal = MockNsmForwardingSignal(); signal.fn(); try { verify(signal.fn(any)); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 43e6df8e0..2b0b176e5 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -29,7 +29,7 @@ class _RealClass { String methodWithObjArgs(_RealClass x) => 'Real'; String get getter => 'Real'; set setter(String arg) { - throw new StateError('I must be mocked'); + throw StateError('I must be mocked'); } String methodWithLongArgs(LongToString a, LongToString b, @@ -74,7 +74,7 @@ void main() { var isNsmForwarding = assessNsmForwarding(); setUp(() { - mock = new _MockedClass(); + mock = _MockedClass(); }); tearDown(() { @@ -129,12 +129,12 @@ void main() { }); test('should mock method with mock args', () { - var m1 = named(new _MockedClass(), name: 'm1'); + var m1 = named(_MockedClass(), name: 'm1'); mock.methodWithObjArgs(m1); expectFail( 'No matching calls. All calls: _MockedClass.methodWithObjArgs(m1)\n' '$noMatchingCallsFooter', () { - verify(mock.methodWithObjArgs(new _MockedClass())); + verify(mock.methodWithObjArgs(_MockedClass())); }); verify(mock.methodWithObjArgs(m1)); }); @@ -191,8 +191,7 @@ void main() { final expectedMessage = RegExp.escape('No matching calls. ' 'All calls: _MockedClass.setter==A\n$noMatchingCallsFooter'); // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - var expectedPattern = - new RegExp(expectedMessage.replaceFirst('==', '=?=')); + var expectedPattern = RegExp(expectedMessage.replaceFirst('==', '=?=')); expectFail(expectedPattern, () => verify(mock.setter = 'B')); verify(mock.setter = 'A'); @@ -213,7 +212,7 @@ void main() { verify(mock.methodWithNamedArgs(42, y: 17)); fail('verify call was expected to throw!'); } catch (e) { - expect(e, new TypeMatcher()); + expect(e, TypeMatcher()); expect( e.message, contains('Verification appears to be in progress. ' @@ -450,8 +449,7 @@ void main() { }); test('throws if given a real object', () { - expect(() => verifyNoMoreInteractions(new _RealClass()), - throwsArgumentError); + expect(() => verifyNoMoreInteractions(_RealClass()), throwsArgumentError); }); }); @@ -507,11 +505,11 @@ void main() { test( '"No matching calls" message visibly separates unmatched calls, ' 'if an arg\'s string representations is multiline', () { - mock.methodWithLongArgs(new LongToString([1, 2], {1: 'a', 2: 'b'}, 'c'), - new LongToString([4, 5], {3: 'd', 4: 'e'}, 'f')); + mock.methodWithLongArgs(LongToString([1, 2], {1: 'a', 2: 'b'}, 'c'), + LongToString([4, 5], {3: 'd', 4: 'e'}, 'f')); mock.methodWithLongArgs(null, null, - c: new LongToString([5, 6], {5: 'g', 6: 'h'}, 'i'), - d: new LongToString([7, 8], {7: 'j', 8: 'k'}, 'l')); + c: LongToString([5, 6], {5: 'g', 6: 'h'}, 'i'), + d: LongToString([7, 8], {7: 'j', 8: 'k'}, 'l')); var nsmForwardedNamedArgs = isNsmForwarding ? '>, {c: null, d: null}),' : '>),'; expectFail( From 141ec2fd0dc62f0cc8a91f497f9727fac696554c Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 30 Apr 2019 16:15:22 -0700 Subject: [PATCH 135/607] Add a Fake class with a throwing noSuchMethod (dart-lang/mockito#191) Closes dart-lang/mockito#169 Adds a slightly better pattern for current abuses of the `Mock` class when it is used only to avoid implementing an entire interface rather than for it's actual mocking capabilities. --- pkgs/mockito/CHANGELOG.md | 5 ++++ pkgs/mockito/lib/mockito.dart | 1 + pkgs/mockito/lib/src/mock.dart | 53 ++++++++++++++++++++++++++++++++++ pkgs/mockito/pubspec.yaml | 2 +- 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 7548459d6..9d1e31afc 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.1.0 + +* Add a `Fake` class for implementing a subset of a class API as overrides + without misusing the `Mock` class. + ## 4.0.0 * Replace the dependency on the diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 046f68c5f..87da9da41 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -14,6 +14,7 @@ export 'src/mock.dart' show + Fake, Mock, named, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 9672cc6bd..2d43a95a4 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -74,6 +74,11 @@ void throwOnMissingStub(Mock mock) { /// 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 +/// [Fake] instead. +/// /// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of /// runtime reflection, and causes sub-standard code to be generated. As such, /// [Mock] should strictly _not_ be used in any production code, especially if @@ -152,6 +157,54 @@ class Mock { } } +/// Extend or mixin this class to mark the implementation as a [Fake]. +/// +/// 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]); + } +} + typedef _ReturnsCannedResponse = CallPair Function(); // When using an [ArgMatcher], we transform our invocation to have knowledge of diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8903ae8c7..3570b414f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.0.0 +version: 4.1.0 authors: - Dmitriy Fibulwinter From 580c17cdcdb6e327a77fe5924e4050df0cdde692 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 21 May 2019 11:38:08 -0700 Subject: [PATCH 136/607] fix lints --- pkgs/mockito/test/mockito_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index a41300983..754b76d75 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -154,10 +154,11 @@ void main() { .thenReturn("x y"); when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) .thenReturn("x z"); - if (isNsmForwarding) + if (isNsmForwarding) { expect(mock.methodWithTwoNamedArgs(42), "x z"); - else + } else { expect(mock.methodWithTwoNamedArgs(42), isNull); + } expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); From cca4ef3bdf6ffd84cb1f4523cb3649b6a6d0e03a Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 21 May 2019 11:39:17 -0700 Subject: [PATCH 137/607] Travis: clean up SDK --- pkgs/mockito/.travis.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 6d661f459..a21349b37 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -6,6 +6,9 @@ sudo: required addons: chrome: stable +dart: + - dev + # Build stages: https://docs.travis-ci.com/user/build-stages/. stages: - presubmit @@ -19,25 +22,18 @@ jobs: include: - stage: presubmit script: ./tool/travis.sh dartfmt - dart: dev - stage: presubmit script: ./tool/travis.sh dartanalyzer - dart: dev - stage: presubmit script: ./tool/travis.sh vm_test - dart: dev - stage: build script: ./tool/travis.sh dartdevc_build - dart: dev - stage: testing script: ./tool/travis.sh dartdevc_test - dart: dev - stage: testing script: ./tool/travis.sh dart2js_test - dart: dev - stage: testing script: ./tool/travis.sh coverage - dart: dev # Only building master means that we don't run two builds for each pull request. branches: From fa438923d16117213e2f46b0558da0946480f904 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 21 May 2019 11:39:30 -0700 Subject: [PATCH 138/607] support latest version of build_web_compilers --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 3570b414f..b319c1abc 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: test_api: ^0.2.1 dev_dependencies: - build_runner: ^1.0.0 + build_runner: '>=1.0.0 <3.0.0' build_test: ^0.10.0 build_web_compilers: ^1.0.0 pedantic: ^1.3.0 From 06a87b2fad7a8ebbb2fe4c35a038fff1b52c59bb Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 21 May 2019 15:30:58 -0700 Subject: [PATCH 139/607] Fix incorrect version constraint from fa43892 (dart-lang/mockito#196) --- pkgs/mockito/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b319c1abc..e777a8c4b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -18,8 +18,8 @@ dependencies: test_api: ^0.2.1 dev_dependencies: - build_runner: '>=1.0.0 <3.0.0' + build_runner: ^1.0.0 build_test: ^0.10.0 - build_web_compilers: ^1.0.0 + build_web_compilers: '>=1.0.0 <3.0.0' pedantic: ^1.3.0 test: ^1.5.1 From 473bd81e5268a37ea42f06c72f267609ae731758 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 31 May 2019 09:53:24 -0700 Subject: [PATCH 140/607] Fix some typos, it's -> its (dart-lang/mockito#197) It's is a contraction, not possessive. --- pkgs/mockito/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 96daf0143..4bd7e1c80 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -7,7 +7,7 @@ Current mock libraries suffer from specifying method names as strings, which cause a lot of problems: * Poor refactoring support: rename method and you need manually search/replace - it's usage in when/verify clauses. + its usage in when/verify clauses. * Poor support from IDE: no code-completion, no hints on argument types, can't jump to definition @@ -182,7 +182,7 @@ verify(cat.eatFood("Milk", hungry: argThat(isNull))); // BAD: null as named argu ## Named arguments -Mockito currently has an awkward nuisance to it's syntax: named arguments and +Mockito currently has an awkward nuisance to its syntax: named arguments and argument matchers require more specification than you might think: you must declare the name of the argument in the argument matcher. This is because we can't rely on the position of a named argument, and the language doesn't @@ -338,7 +338,7 @@ should be preserved for matching an invocation. Named arguments are trickier: their evaluation order is not specified, so if Mockito blindly stored them in the order of their evaluation, it wouldn't be able to match up each argument matcher with the correct name. This is why each named argument matcher must -repeat it's own name. `foo: anyNamed('foo')` tells Mockito to store an argument +repeat its own name. `foo: anyNamed('foo')` tells Mockito to store an argument matcher for an invocation under the name 'foo'. > **Be careful** never to write `when;` (without the function call) anywhere. From 6b81b265c5405350fe77b5be523281905bc761ec Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 17 Jul 2019 21:15:04 -0700 Subject: [PATCH 141/607] Deprecate two APIs (dart-lang/mockito#202) Deprecate two APIs; 4.1.1 --- pkgs/mockito/CHANGELOG.md | 6 ++++++ pkgs/mockito/lib/mockito.dart | 2 +- pkgs/mockito/lib/src/mock.dart | 8 +++++++- pkgs/mockito/pubspec.yaml | 2 +- .../test/deprecated_apis/mockito_test.dart | 16 ++++++++++++++++ .../test/deprecated_apis/verify_test.dart | 11 +++++++++++ pkgs/mockito/test/mockito_test.dart | 16 ---------------- pkgs/mockito/test/verify_test.dart | 11 ----------- 8 files changed, 42 insertions(+), 30 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9d1e31afc..5f99597e9 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.1.1 + +* Mark the unexported and accidentally public `setDefaultResponse` as + deprecated. +* Mark the not useful, and not generally used, `named` function as deprecated. + ## 4.1.0 * Add a `Fake` class for implementing a subset of a class API as overrides diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 87da9da41..10fb4c503 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -16,7 +16,7 @@ export 'src/mock.dart' show Fake, Mock, - named, + named, // ignore: deprecated_member_use_from_same_package // -- setting behaviour when, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 2d43a95a4..b18f187f5 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -34,7 +34,9 @@ final List _capturedArgs = []; final List _storedArgs = []; final Map _storedNamedArgs = {}; -// Hidden from the public API, used by spy.dart. +@Deprecated( + 'This function is not a supported function, and may be deleted as early as ' + 'Mockito 5.0.0') void setDefaultResponse(Mock mock, CallPair defaultResponse()) { mock._defaultResponse = defaultResponse; } @@ -364,6 +366,10 @@ class _InvocationForMatchedArguments extends Invocation { this.namedArguments, this.isGetter, this.isMethod, this.isSetter); } +@Deprecated( + 'This function does not provide value; hashCode and toString() can be ' + 'stubbed individually. This function may be deleted as early as Mockito ' + '5.0.0') T named(T mock, {String name, int hashCode}) => mock .._givenName = name .._givenHashCode = hashCode; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index e777a8c4b..b291b6d9f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.0 +version: 4.1.1 authors: - Dmitriy Fibulwinter diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index f7bf7bc35..95dbcb492 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -134,5 +134,21 @@ void main() { .thenReturn("42"); expect(mock.methodWithNormalArgs(43), equals("43")); }); + + test("should mock hashCode", () { + named(mock, hashCode: 42); + expect(mock.hashCode, equals(42)); + }); + + test("should have toString as name when it is not mocked", () { + named(mock, name: "Cat"); + expect(mock.toString(), equals("Cat")); + }); + + test("should mock equals between mocks when givenHashCode is equals", () { + var anotherMock = named(_MockedClass(), hashCode: 42); + named(mock, hashCode: 42); + expect(mock == anotherMock, isTrue); + }); }); } diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index 21a5fb4ba..a8c870c17 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -121,5 +121,16 @@ void main() { verify(mock.methodWithPositionalArgs( typed(argThat(greaterThanOrEqualTo(100))), 17)); }); + + test('should mock method with mock args', () { + var m1 = named(_MockedClass(), name: 'm1'); + mock.methodWithObjArgs(m1); + expectFail( + 'No matching calls. All calls: _MockedClass.methodWithObjArgs(m1)\n' + '$noMatchingCallsFooter', () { + verify(mock.methodWithObjArgs(_MockedClass())); + }); + verify(mock.methodWithObjArgs(m1)); + }); }); } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 754b76d75..ff872f42c 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -181,11 +181,6 @@ void main() { expect(mock.getter, equals("A")); }); - test("should mock hashCode", () { - named(mock, hashCode: 42); - expect(mock.hashCode, equals(42)); - }); - test("should have hashCode when it is not mocked", () { expect(mock.hashCode, isNotNull); }); @@ -194,17 +189,6 @@ void main() { expect(mock.toString(), equals("_MockedClass")); }); - test("should have toString as name when it is not mocked", () { - named(mock, name: "Cat"); - expect(mock.toString(), equals("Cat")); - }); - - test("should mock equals between mocks when givenHashCode is equals", () { - var anotherMock = named(_MockedClass(), hashCode: 42); - named(mock, hashCode: 42); - expect(mock == anotherMock, isTrue); - }); - test("should use identical equality between it is not mocked", () { var anotherMock = _MockedClass(); expect(mock == anotherMock, isFalse); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 2b0b176e5..0c41c2941 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -128,17 +128,6 @@ void main() { verify(mock.methodWithNamedArgs(42, y: 17)); }); - test('should mock method with mock args', () { - var m1 = named(_MockedClass(), name: 'm1'); - mock.methodWithObjArgs(m1); - expectFail( - 'No matching calls. All calls: _MockedClass.methodWithObjArgs(m1)\n' - '$noMatchingCallsFooter', () { - verify(mock.methodWithObjArgs(_MockedClass())); - }); - verify(mock.methodWithObjArgs(m1)); - }); - test('should mock method with list args', () { mock.methodWithListArgs([42]); expectFail( From cbb293d7bb2094187c62c862abbefe6f7af73a73 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 19 Jul 2019 11:10:32 -0700 Subject: [PATCH 142/607] Travis: fix xvfb config (dart-lang/mockito#203) --- pkgs/mockito/.travis.yml | 6 ------ pkgs/mockito/tool/travis.sh | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index a21349b37..12aac8822 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -44,9 +44,3 @@ cache: directories: - $HOME/.pub-cache - .dart_tool - -# Necessary for Chrome and Firefox to run -before_install: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start - - "t=0; until (xdpyinfo -display :99 &> /dev/null || test $t -gt 10); do sleep 1; let t=$t+1; done" diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 748d6196a..36e52164c 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -47,12 +47,12 @@ while (( "$#" )); do dartdevc_test) echo echo -e '\033[1mTASK: dartdevc_test\033[22m' echo -e 'pub run build_runner test -- -p chrome' - pub run build_runner test -- -p chrome || EXIT_CODE=$? + xvfb-run pub run build_runner test -- -p chrome || EXIT_CODE=$? ;; dart2js_test) echo echo -e '\033[1mTASK: dart2js_test\033[22m' echo -e 'pub run test -p chrome' - pub run test -p chrome || EXIT_CODE=$? + xvfb-run pub run test -p chrome || EXIT_CODE=$? ;; coverage) echo echo -e '\033[1mTASK: coverage\033[22m' From a88022adf6f7b65acf62add34300689d56526581 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 22 Jul 2019 14:15:13 -0700 Subject: [PATCH 143/607] Document anyNamed and captureAnyNamed. (dart-lang/mockito#204) Document anyNamed and captureAnyNamed. --- pkgs/mockito/lib/src/mock.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index b18f187f5..c7b186c4b 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -662,6 +662,12 @@ class _VerifyCall { 'VerifyCall'; } +// An argument matcher that acts like an argument during stubbing or +// verification, and stores "matching" information. +// +/// Users do not need to construct this manually; users can instead use the +/// built-in values, [any], [anyNamed], [captureAny], [captureAnyNamed], or the +/// functions [argThat] and [captureThat]. class ArgMatcher { final Matcher matcher; final bool _capture; @@ -675,12 +681,17 @@ class ArgMatcher { /// An argument matcher that matches any argument passed in "this" position. Null get any => _registerMatcher(anything, false); +/// An argument matcher that matches any named argument passed in for the +/// parameter named [named]. Null anyNamed(String named) => _registerMatcher(anything, false, named: named); /// An argument matcher that matches any argument passed in "this" position, and /// captures the argument for later access with `captured`. Null get captureAny => _registerMatcher(anything, true); +/// An argument matcher that matches any named argument passed in for the +/// parameter named [named], and captures the argument for later access with +/// `captured`. Null captureAnyNamed(String named) => _registerMatcher(anything, true, named: named); From c8f78ac199ada904ca8a88301d560cd725186e08 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 26 Jul 2019 17:40:50 -0700 Subject: [PATCH 144/607] Improve docs for VerificationResult (dart-lang/mockito#205) Improve docs for VerificationResult --- pkgs/mockito/lib/src/mock.dart | 77 ++++++++++++++++++++++++++--- pkgs/mockito/test/capture_test.dart | 2 +- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index c7b186c4b..82d309bc7 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -30,7 +30,7 @@ _WhenCall _whenCall; _UntilCall _untilCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; final _TimeStampProvider _timer = _TimeStampProvider(); -final List _capturedArgs = []; +final List _capturedArgs = []; final List _storedArgs = []; final Map _storedNamedArgs = {}; @@ -411,7 +411,8 @@ class PostExpectation { void _completeWhen(Answering answer) { if (_whenCall == null) { throw StateError( - 'Mock method was not called within `when()`. Was a real method called?'); + 'Mock method was not called within `when()`. Was a real method ' + 'called?'); } _whenCall._setExpected(answer); _whenCall = null; @@ -725,15 +726,66 @@ Null _registerMatcher(Matcher matcher, bool capture, {String named}) { return null; } +/// Information about a stub call verification. +/// +/// This class is most useful to users in two ways: +/// +/// * verifying call count, via [called], +/// * collecting captured arguments, via [captured]. class VerificationResult { - List captured = []; + List _captured; + + /// List of all arguments captured in real calls. + /// + /// This list will include any captured default arguments and has no + /// structure differentiating the arguments of one call from another. Given + /// the following class: + /// + /// ```dart + /// class C { + /// String methodWithPositionalArgs(int x, [int y]) => ''; + /// String methodWithTwoNamedArgs(int x, {int y, int z}) => ''; + /// } + /// ``` + /// + /// the following stub calls will result in the following captured arguments: + /// + /// ```dart + /// mock.methodWithPositionalArgs(1); + /// mock.methodWithPositionalArgs(2, 3); + /// var captured = verify( + /// mock.methodWithPositionalArgs(captureAny, captureAny)).captured; + /// print(captured); // Prints "[1, null, 2, 3]" + /// + /// mock.methodWithTwoNamedArgs(1, y: 42, z: 43); + /// mock.methodWithTwoNamedArgs(1, y: 44, z: 45); + /// var captured = verify( + /// mock.methodWithTwoNamedArgs(any, + /// y: captureAnyNamed('y'), z: captureAnyNamed('z'))).captured; + /// print(captured); // Prints "[42, 43, 44, 45]" + /// ``` + /// + /// Named arguments are listed in the order they are captured in, not the + /// order in which they were passed. + List get captured => _captured; + + @Deprecated( + 'captured should be considered final - assigning this field may be ' + 'removed as early as Mockito 5.0.0') + set captured(List captured) => _captured = captured; + + /// The number of calls matched in this verification. int callCount; - // Whether the test API mismatch has been checked. bool _testApiMismatchHasBeenChecked = false; - VerificationResult(this.callCount) { - captured = List.from(_capturedArgs, growable: false); + @Deprecated( + 'User-constructed VerificationResult is deprecated; this constructor may ' + 'be deleted as early as Mockito 5.0.0') + VerificationResult(int callCount) : this._(callCount); + + VerificationResult._(this.callCount) + : _captured = List.from(_capturedArgs, growable: false) { _capturedArgs.clear(); } @@ -772,7 +824,16 @@ class VerificationResult { } } - void called(matcher) { + /// Assert that the number of calls matches [matcher]. + /// + /// Examples: + /// + /// * `verify(mock.m()).called(1)` asserts that `m()` is called exactly once. + /// * `verify(mock.m()).called(greaterThan(2))` asserts that `m()` is called + /// more than two times. + /// + /// To assert that a method was called zero times, use [verifyNever]. + void called(dynamic matcher) { if (!_testApiMismatchHasBeenChecked) { // Only execute the check below once. `Invoker.current` may look like a // cheap getter, but it involves Zones and casting. @@ -847,7 +908,7 @@ Verification _makeVerify(bool never) { _verificationInProgress = false; if (_verifyCalls.length == 1) { _VerifyCall verifyCall = _verifyCalls.removeLast(); - var result = VerificationResult(verifyCall.matchingInvocations.length); + var result = VerificationResult._(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; } else { diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 0a3ae7b66..09da04522 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -117,7 +117,7 @@ void main() { equals([42, 43, 44, 45])); }); - test('should capture invocations with named arguments', () { + test('should capture invocations with out-of-order named arguments', () { mock.methodWithTwoNamedArgs(1, z: 42, y: 43); mock.methodWithTwoNamedArgs(1, y: 44, z: 45); expect( From 6c00655e4c1ee609d8764c4c70e8793121b9296a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 5 Aug 2019 10:07:28 -0700 Subject: [PATCH 145/607] Add examples for pub; clean up README (dart-lang/mockito#207) Add examples for pub; clean up README --- pkgs/mockito/README.md | 82 ++++----- pkgs/mockito/example/example.dart | 172 ++++++++++++++++++ pkgs/mockito/{test => }/example/iss/README.md | 0 pkgs/mockito/{test => }/example/iss/iss.dart | 0 .../{test => }/example/iss/iss_test.dart | 0 5 files changed, 213 insertions(+), 41 deletions(-) create mode 100644 pkgs/mockito/example/example.dart rename pkgs/mockito/{test => }/example/iss/README.md (100%) rename pkgs/mockito/{test => }/example/iss/iss.dart (100%) rename pkgs/mockito/{test => }/example/iss/iss_test.dart (100%) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 4bd7e1c80..3625da5ff 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -32,16 +32,16 @@ class Cat { // Mock class class MockCat extends Mock implements Cat {} -// mock creation +// Create mock object. var cat = MockCat(); ``` ## Let's verify some behaviour! ```dart -//using mock object +// Interact with the mock object. cat.sound(); -//verify interaction +// Verify the interaction. verify(cat.sound()); ``` @@ -51,29 +51,29 @@ verify whatever interaction you are interested in. ## How about some stubbing? ```dart -// Unstubbed methods return null: +// Unstubbed methods return null. expect(cat.sound(), nullValue); -// Stubbing - before execution: +// Stub a mock method before interacting. when(cat.sound()).thenReturn("Purr"); expect(cat.sound(), "Purr"); -// You can call it again: +// You can call it again. expect(cat.sound(), "Purr"); -// Let's change the stub: +// Let's change the stub. when(cat.sound()).thenReturn("Meow"); expect(cat.sound(), "Meow"); -// You can stub getters: +// You can stub getters. when(cat.lives).thenReturn(9); expect(cat.lives, 9); -// You can stub a method to throw: +// You can stub a method to throw. when(cat.lives).thenThrow(RangeError('Boo')); expect(() => cat.lives, throwsRangeError); -// We can calculate a response at call time: +// We can calculate a response at call time. var responses = ["Purr", "Meow"]; when(cat.sound()).thenAnswer(() => responses.removeAt(0)); expect(cat.sound(), "Purr"); @@ -138,28 +138,28 @@ In most cases, both plain arguments and argument matchers can be passed into mock methods: ```dart -// You can use plain arguments themselves: +// You can use plain arguments themselves when(cat.eatFood("fish")).thenReturn(true); -// ... including collections: +// ... including collections when(cat.walk(["roof","tree"])).thenReturn(2); -// ... or matchers: -when(cat.eatFood(argThat(startsWith("dry"))).thenReturn(false); +// ... or matchers +when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); -// ... or mix aguments with matchers: -when(cat.eatFood(argThat(startsWith("dry")), true).thenReturn(true); +// ... or mix aguments with matchers +when(cat.eatFood(argThat(startsWith("dry")), hungry: true)).thenReturn(true); expect(cat.eatFood("fish"), isTrue); expect(cat.walk(["roof","tree"]), equals(2)); expect(cat.eatFood("dry food"), isFalse); expect(cat.eatFood("dry food", hungry: true), isTrue); -// You can also verify using an argument matcher: +// You can also verify using an argument matcher. verify(cat.eatFood("fish")); verify(cat.walk(["roof","tree"])); verify(cat.eatFood(argThat(contains("food")))); -// You can verify setters: +// You can verify setters. cat.lives = 9; verify(cat.lives=9); ``` @@ -173,11 +173,11 @@ However, note that `null` cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example: ```dart -verify(cat.hunt("back yard", null)); // OK: no ArgMatchers. +verify(cat.hunt("backyard", null)); // OK: no arg matchers. verify(cat.hunt(argThat(contains("yard")), null)); // BAD: adjacent null. -verify(cat.hunt(argThat(contains("yard")), argThat(isNull))); // OK: wrapped in ArgMatcher. -verify(cat.eatFood("Milk", hungry: null)); // BAD: null as named argument. -verify(cat.eatFood("Milk", hungry: argThat(isNull))); // BAD: null as named argument. +verify(cat.hunt(argThat(contains("yard")), argThat(isNull))); // OK: wrapped in an arg matcher. +verify(cat.eatFood("Milk", hungry: null)); // BAD: null as a named argument. +verify(cat.eatFood("Milk", hungry: argThat(isNull))); // BAD: null as a named argument. ``` ## Named arguments @@ -190,16 +190,16 @@ provide a mechanism to answer "Is this element being used as a named element?" ```dart // GOOD: argument matchers include their names. -when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(0); -when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))).thenReturn(0); -when(cat.eatFood(any, hungry: captureAnyNamed('hungry'))).thenReturn(0); -when(cat.eatFood(any, hungry: captureArgThat(isNotNull, named: 'hungry'))).thenReturn(0); +when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(true); +when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))).thenReturn(false); +when(cat.eatFood(any, hungry: captureAnyNamed('hungry'))).thenReturn(false); +when(cat.eatFood(any, hungry: captureThat(isNotNull, named: 'hungry'))).thenReturn(true); // BAD: argument matchers do not include their names. -when(cat.eatFood(any, hungry: any)).thenReturn(0); -when(cat.eatFood(any, hungry: argThat(isNotNull))).thenReturn(0); -when(cat.eatFood(any, hungry: captureAny)).thenReturn(0); -when(cat.eatFood(any, hungry: captureArgThat(isNotNull))).thenReturn(0); +when(cat.eatFood(any, hungry: any)).thenReturn(true); +when(cat.eatFood(any, hungry: argThat(isNotNull))).thenReturn(false); +when(cat.eatFood(any, hungry: captureAny)).thenReturn(false); +when(cat.eatFood(any, hungry: captureThat(isNotNull))).thenReturn(true); ``` ## Verifying exact number of invocations / at least x / never @@ -208,13 +208,13 @@ when(cat.eatFood(any, hungry: captureArgThat(isNotNull))).thenReturn(0); cat.sound(); cat.sound(); -// Exact number of invocations: +// Exact number of invocations verify(cat.sound()).called(2); -// Or using matcher: +// Or using matcher verify(cat.sound()).called(greaterThan(1)); -// Or never called: +// Or never called verifyNever(cat.eatFood(any)); ``` @@ -237,7 +237,7 @@ one-by-one but only those that you are interested in testing in order. ## Making sure interaction(s) never happened on mock ```dart - verifyZeroInteractions(cat); +verifyZeroInteractions(cat); ``` ## Finding redundant invocations @@ -251,16 +251,16 @@ verifyNoMoreInteractions(cat); ## Capturing arguments for further assertions ```dart -// Simple capture: +// Simple capture cat.eatFood("Fish"); expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); -// Capture multiple calls: +// Capture multiple calls cat.eatFood("Milk"); cat.eatFood("Fish"); expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); -// Conditional capture: +// Conditional capture cat.eatFood("Milk"); cat.eatFood("Fish"); expect(verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); @@ -269,13 +269,13 @@ expect(verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); ## Waiting for an interaction ```dart -// Waiting for a call: +// Waiting for a call. cat.eatFood("Fish"); -await untilCalled(cat.chew()); //completes when cat.chew() is called +await untilCalled(cat.chew()); // Completes when cat.chew() is called. -// Waiting for a call that has already happened: +// Waiting for a call that has already happened. cat.eatFood("Fish"); -await untilCalled(cat.eatFood(any)); //will complete immediately +await untilCalled(cat.eatFood(any)); // Completes immediately. ``` ## Resetting mocks diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart new file mode 100644 index 000000000..76d1174ad --- /dev/null +++ b/pkgs/mockito/example/example.dart @@ -0,0 +1,172 @@ +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +// Real class +class Cat { + String sound() => "Meow"; + bool eatFood(String food, {bool hungry}) => true; + Future chew() {} + int walk(List places) {} + void sleep() {} + void hunt(String place, String prey) {} + int lives = 9; +} + +// Mock class +class MockCat extends Mock implements Cat {} + +void main() { + Cat cat; + + setUp(() { + // Create mock object. + cat = MockCat(); + }); + + test("Let's verify some behaviour!", () { + // Interact with the mock object. + cat.sound(); + + // Verify the interaction. + verify(cat.sound()); + }); + + test("How about some stubbing?", () { + // Unstubbed methods return null. + expect(cat.sound(), null); + + // Stub a method before interacting with it. + when(cat.sound()).thenReturn("Purr"); + expect(cat.sound(), "Purr"); + + // You can call it again. + expect(cat.sound(), "Purr"); + + // Let's change the stub. + when(cat.sound()).thenReturn("Meow"); + expect(cat.sound(), "Meow"); + + // You can stub getters. + when(cat.lives).thenReturn(9); + expect(cat.lives, 9); + + // You can stub a method to throw. + when(cat.lives).thenThrow(RangeError('Boo')); + expect(() => cat.lives, throwsRangeError); + + // We can calculate a response at call time. + var responses = ["Purr", "Meow"]; + when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); + expect(cat.sound(), "Purr"); + expect(cat.sound(), "Meow"); + }); + + test("Argument matchers", () { + // You can use plain arguments themselves + when(cat.eatFood("fish")).thenReturn(true); + + // ... including collections + when(cat.walk(["roof", "tree"])).thenReturn(2); + + // ... or matchers + when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); + + // ... or mix aguments with matchers + when(cat.eatFood(argThat(startsWith("dry")), hungry: true)) + .thenReturn(true); + expect(cat.eatFood("fish"), isTrue); + expect(cat.walk(["roof", "tree"]), equals(2)); + expect(cat.eatFood("dry food"), isFalse); + expect(cat.eatFood("dry food", hungry: true), isTrue); + + // You can also verify using an argument matcher. + verify(cat.eatFood("fish")); + verify(cat.walk(["roof", "tree"])); + verify(cat.eatFood(argThat(contains("food")))); + + // You can verify setters. + cat.lives = 9; + verify(cat.lives = 9); + + cat.hunt("backyard", null); + verify(cat.hunt("backyard", null)); // OK: no arg matchers. + + cat.hunt("backyard", null); + verify(cat.hunt(argThat(contains("yard")), + argThat(isNull))); // OK: null is wrapped in an arg matcher. + }); + + test("Named arguments", () { + // GOOD: argument matchers include their names. + when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(true); + when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))) + .thenReturn(false); + when(cat.eatFood(any, hungry: captureAnyNamed('hungry'))).thenReturn(false); + when(cat.eatFood(any, hungry: captureThat(isNotNull, named: 'hungry'))) + .thenReturn(true); + }); + + test("Verifying exact number of invocations / at least x / never", () { + cat.sound(); + cat.sound(); + // Exact number of invocations + verify(cat.sound()).called(2); + + cat.sound(); + cat.sound(); + cat.sound(); + // Or using matcher + verify(cat.sound()).called(greaterThan(1)); + + // Or never called + verifyNever(cat.eatFood(any)); + }); + + test("Verification in order", () { + cat.eatFood("Milk"); + cat.sound(); + cat.eatFood("Fish"); + verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood("Fish")]); + }); + + test("Making sure interaction(s) never happened on mock", () { + verifyZeroInteractions(cat); + }); + + test("Finding redundant invocations", () { + cat.sound(); + verify(cat.sound()); + verifyNoMoreInteractions(cat); + }); + + test("Capturing arguments for further assertions", () { + // Simple capture: + cat.eatFood("Fish"); + expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); + + // Capture multiple calls: + cat.eatFood("Milk"); + cat.eatFood("Fish"); + expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); + + // Conditional capture: + cat.eatFood("Milk"); + cat.eatFood("Fish"); + expect( + verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); + }); + + test("Waiting for an interaction", () async { + Future chewHelper(Cat cat) { + return cat.chew(); + } + + // Waiting for a call. + chewHelper(cat); + await untilCalled(cat.chew()); // This completes when cat.chew() is called. + + // Waiting for a call that has already happened. + cat.eatFood("Fish"); + await untilCalled(cat.eatFood(any)); // This completes immediately. + }); +} diff --git a/pkgs/mockito/test/example/iss/README.md b/pkgs/mockito/example/iss/README.md similarity index 100% rename from pkgs/mockito/test/example/iss/README.md rename to pkgs/mockito/example/iss/README.md diff --git a/pkgs/mockito/test/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart similarity index 100% rename from pkgs/mockito/test/example/iss/iss.dart rename to pkgs/mockito/example/iss/iss.dart diff --git a/pkgs/mockito/test/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart similarity index 100% rename from pkgs/mockito/test/example/iss/iss_test.dart rename to pkgs/mockito/example/iss/iss_test.dart From 093481fad63be8b66ada7dbb4961f570803137cf Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 8 Aug 2019 09:47:04 -0700 Subject: [PATCH 146/607] Add docs in README and example/ for Fake. --- pkgs/mockito/README.md | 30 ++++++++++++++++++++++++++++++ pkgs/mockito/example/example.dart | 17 +++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 3625da5ff..70ad10739 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -278,6 +278,36 @@ cat.eatFood("Fish"); await untilCalled(cat.eatFood(any)); // Completes immediately. ``` +## Writing a fake + +You can also write a simple fake class that implements a real class, by +extending [Fake]. Fake allows your subclass to satisfy the implementation of +your real class, without overriding each method with a fake; the Fake class +implements the default behavior of throwing [UnimplementedError] (which you can +override in your fake class): + +```dart +// Fake class +class FakeCat extends Fake implements Cat { + @override + bool eatFood(String food, {bool hungry}) { + print('Fake eat $food'); + return true; + } +} + +void main() { + // Create a new fake Cat at runtime. + var cat = new FakeCat(); + + cat.eatFood("Milk"); // Prints 'Fake eat Milk'. + cat.sleep(); // Throws. +} +``` + +[Fake]: https://pub.dev/documentation/mockito/latest/mockito/Fake-class.html +[UnimplementedError]: https://api.dartlang.org/stable/dart-core/UnimplementedError-class.html + ## Resetting mocks ```dart diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 76d1174ad..b4439fb20 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -15,6 +15,15 @@ class Cat { // Mock class class MockCat extends Mock implements Cat {} +// Fake class +class FakeCat extends Fake implements Cat { + @override + bool eatFood(String food, {bool hungry}) { + print('Fake eat $food'); + return true; + } +} + void main() { Cat cat; @@ -169,4 +178,12 @@ void main() { cat.eatFood("Fish"); await untilCalled(cat.eatFood(any)); // This completes immediately. }); + + test("Fake class", () { + // Create a new fake Cat at runtime. + var cat = new FakeCat(); + + cat.eatFood("Milk"); // Prints 'Fake eat Milk'. + expect(() => cat.sleep(), throwsUnimplementedError); + }); } From 50a0acbb8af931c4d66af907621560ccf92bb87c Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 8 Aug 2019 10:27:25 -0700 Subject: [PATCH 147/607] wording --- pkgs/mockito/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 70ad10739..de162fe8d 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -282,9 +282,9 @@ await untilCalled(cat.eatFood(any)); // Completes immediately. You can also write a simple fake class that implements a real class, by extending [Fake]. Fake allows your subclass to satisfy the implementation of -your real class, without overriding each method with a fake; the Fake class -implements the default behavior of throwing [UnimplementedError] (which you can -override in your fake class): +your real class, without overriding the methods that aren't used in your test; +the Fake class implements the default behavior of throwing [UnimplementedError] +(which you can override in your fake class): ```dart // Fake class From 42d7a842120d78d3cba68fdf0f59cced3b73af96 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 8 Aug 2019 13:28:58 -0700 Subject: [PATCH 148/607] New docs for verifyInOrder (dart-lang/mockito#208) Add docs for verifyInOrder --- pkgs/mockito/lib/src/mock.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 82d309bc7..11db4033d 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -917,6 +917,20 @@ Verification _makeVerify(bool never) { }; } +/// Verify that a list of methods on a mock object have been called with the +/// given arguments. For example: +/// +/// ```dart +/// verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood(any)]); +/// ``` +/// +/// This verifies that `eatFood` was called with `"Milk"`, sound` was called +/// with no arguments, and `eatFood` was then called with some argument. +/// +/// Note: [verifyInOrder] only verifies that each call was made in the order +/// given, but not that those were the only calls. In the example above, if +/// other calls were made to `eatFood` or `sound` between the three given +/// calls, or before or after them, the verification will still succeed. _InOrderVerification get verifyInOrder { if (_verifyCalls.isNotEmpty) { throw StateError(_verifyCalls.join()); From 0f5565dc5e387124973f92f7c22d03e9233eaf1c Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 8 Aug 2019 15:30:02 -0700 Subject: [PATCH 149/607] Remove comparison against other mock libraries (dart-lang/mockito#210) The "Current mock libraries" referenced in this statement don't exist anymore and mockito has become the standard. --- pkgs/mockito/README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index de162fe8d..3b7d52825 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -3,17 +3,6 @@ Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). [![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dartlang.org/packages/mockito) [![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) -Current mock libraries suffer from specifying method names as strings, which -cause a lot of problems: - -* Poor refactoring support: rename method and you need manually search/replace - its usage in when/verify clauses. -* Poor support from IDE: no code-completion, no hints on argument types, can't - jump to definition - -Dart's mockito package fixes these issues - stubbing and verifying are -first-class citizens. - ## Let's create mocks ```dart From 90c772da040a8face546e46555ab82846a2cb18f Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 9 Aug 2019 09:18:49 -0700 Subject: [PATCH 150/607] Add a section on best practices (dart-lang/mockito#211) These follow common guidelines of preferring real objects over fakes over mocks. They also set some boundaries to avoid blurring the line between mock or stub, and fake. --- pkgs/mockito/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 3b7d52825..77fbcecce 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -324,6 +324,31 @@ logInvocations([catOne, catTwo]); throwOnMissingStub(cat); ``` +## Best Practices + +Testing with real objects is preferred over testing with mocks - if you can +construct a real instance for your tests, you should! If there are no calls to +`verify` in your test, it is a strong signal that you may not need mocks at all, +though it's also OK to use a `Mock` like a stub. When it's not possible to use +the real object, a tested implementation of a fake is the next best thing - it's +more likely to behave similarly to the real class than responses stubbed out in +tests. Finally an object which `extends Fake` using manually overridden methods +is preferred over an object which `extends Mock` used as either a stub or a +mock. + +A class which `extends Mock` should _never_ stub out it's own responses with +`when` in it's constructor or anywhere else. Stubbed responses should be defined +in the tests where they are used. For responses controlled outside of the test +use `@override` methods for either the entire interface, or with `extends Fake` +to skip some parts of the interface. + +Similarly, a class which `extends Mock` should _never_ have any `@override` +methods. These can't be stubbed by tests and can't be tracked and verified by +Mockito. A mix of test defined stubbed responses and mock defined overrides will +lead to confusion. It is OK to define _static_ utilities on a class which +`extends Mock` if it helps with code structure. + + ## How it works The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to From fe9c48c9c180a17e50056546081a58bf7c0d96ef Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 26 Aug 2019 12:54:33 -0700 Subject: [PATCH 151/607] Throw an error if an arg matcher is used improperly; 4.1.2 (dart-lang/mockito#216) Throw an error if an arg matcher is used improperly --- pkgs/mockito/CHANGELOG.md | 5 ++++ pkgs/mockito/analysis_options.yaml | 6 +++++ pkgs/mockito/lib/src/mock.dart | 39 +++++++++++++++++++++++------ pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/mockito_test.dart | 6 +++++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 5f99597e9..58dc8e21c 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.1.2 + +* Produce a meaningful error message if an argument matcher is used outside of + stubbing (`when`) or verification (`verify` and `untilCalled`). + ## 4.1.1 * Mark the unexported and accidentally public `setDefaultResponse` as diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 1cb9f53f4..bbe63be80 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -4,6 +4,12 @@ analyzer: implicit-casts: false implicit-dynamic: false errors: + # These are for 2.5.0-dev.2.0 and after + implicit_dynamic_list_literal: ignore + implicit_dynamic_map_literal: ignore + implicit_dynamic_parameter: ignore + implicit_dynamic_variable: ignore + # These are pre-2.5.0-dev.2.0 strong_mode_implicit_dynamic_list_literal: ignore strong_mode_implicit_dynamic_map_literal: ignore strong_mode_implicit_dynamic_parameter: ignore diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 11db4033d..e8c6c4608 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -680,30 +680,33 @@ class ArgMatcher { } /// An argument matcher that matches any argument passed in "this" position. -Null get any => _registerMatcher(anything, false); +Null get any => _registerMatcher(anything, false, argumentMatcher: 'any'); /// An argument matcher that matches any named argument passed in for the /// parameter named [named]. -Null anyNamed(String named) => _registerMatcher(anything, false, named: named); +Null anyNamed(String named) => _registerMatcher(anything, false, + named: named, argumentMatcher: 'anyNamed'); /// An argument matcher that matches any argument passed in "this" position, and /// captures the argument for later access with `captured`. -Null get captureAny => _registerMatcher(anything, true); +Null get captureAny => + _registerMatcher(anything, true, argumentMatcher: 'captureAny'); /// An argument matcher that matches any named argument passed in for the /// parameter named [named], and captures the argument for later access with /// `captured`. -Null captureAnyNamed(String named) => - _registerMatcher(anything, true, named: named); +Null captureAnyNamed(String named) => _registerMatcher(anything, true, + named: named, argumentMatcher: 'captureAnyNamed'); /// An argument matcher that matches an argument that matches [matcher]. Null argThat(Matcher matcher, {String named}) => - _registerMatcher(matcher, false, named: named); + _registerMatcher(matcher, false, named: named, argumentMatcher: 'argThat'); /// An argument matcher that matches an argument that matches [matcher], and /// captures the argument for later access with `captured`. Null captureThat(Matcher matcher, {String named}) => - _registerMatcher(matcher, true, named: named); + _registerMatcher(matcher, true, + named: named, argumentMatcher: 'captureThat'); @Deprecated('ArgMatchers no longer need to be wrapped in Mockito 3.0') Null typed(ArgMatcher matcher, {String named}) => null; @@ -716,7 +719,27 @@ Null typedArgThat(Matcher matcher, {String named}) => Null typedCaptureThat(Matcher matcher, {String named}) => captureThat(matcher, named: named); -Null _registerMatcher(Matcher matcher, bool capture, {String named}) { +/// Registers [matcher] into the stored arguments collections. +/// +/// Creates an [ArgMatcher] with [matcher] and [capture], then if [named] is +/// non-null, stores that into the positional stored arguments list; otherwise +/// stores it into the named stored arguments map, keyed on [named]. +/// [argumentMatcher] is the name of the public API used to register [matcher], +/// for error messages. +Null _registerMatcher(Matcher matcher, bool capture, + {String named, String argumentMatcher}) { + if (!_whenInProgress && !_untilCalledInProgress && !_verificationInProgress) { + // It is not meaningful to store argument matchers outside of stubbing + // (`when`), or verification (`verify` and `untilCalled`). Such argument + // matchers will be processed later erroneously. + _storedArgs.clear(); + _storedNamedArgs.clear(); + throw ArgumentError( + 'The "$argumentMatcher" argument matcher is used outside of method ' + 'stubbing (via `when`) or verification (via `verify` or `untilCalled`). ' + 'This is invalid, and results in bad behavior during the next stubbing ' + 'or verification.'); + } var argMatcher = ArgMatcher(matcher, capture); if (named == null) { _storedArgs.add(argMatcher); diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b291b6d9f..affbad8c3 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.1 +version: 4.1.2 authors: - Dmitriy Fibulwinter diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index ff872f42c..99da123be 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -296,4 +296,10 @@ void main() { expect(() => mock.methodWithoutArgs(), returnsNormally); }); }); + + test( + "reports an error when using an argument matcher outside of stubbing or " + "verification", () { + expect(() => mock.methodWithNormalArgs(any), throwsArgumentError); + }); } From cd803f9b0836251f5706673b7b09c6585c250f7d Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 26 Aug 2019 14:29:54 -0700 Subject: [PATCH 152/607] Fix static analysis issues in example/ (dart-lang/mockito#217) --- pkgs/mockito/README.md | 5 +++-- pkgs/mockito/analysis_options.yaml | 1 + pkgs/mockito/example/example.dart | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 77fbcecce..5b70df8ab 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -12,7 +12,8 @@ import 'package:mockito/mockito.dart'; class Cat { String sound() => "Meow"; bool eatFood(String food, {bool hungry}) => true; - int walk(List places); + Future chew() async => print("Chewing..."); + int walk(List places) => 7; void sleep() {} void hunt(String place, String prey) {} int lives = 9; @@ -287,7 +288,7 @@ class FakeCat extends Fake implements Cat { void main() { // Create a new fake Cat at runtime. - var cat = new FakeCat(); + var cat = FakeCat(); cat.eatFood("Milk"); // Prints 'Fake eat Milk'. cat.sleep(); // Throws. diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index bbe63be80..969b27133 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -5,6 +5,7 @@ analyzer: implicit-dynamic: false errors: # These are for 2.5.0-dev.2.0 and after + implicit_dynamic_function: ignore implicit_dynamic_list_literal: ignore implicit_dynamic_map_literal: ignore implicit_dynamic_parameter: ignore diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index b4439fb20..9742c3a94 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -1,3 +1,5 @@ +// ignore_for_file: sdk_version_async_exported_from_core +// ignore_for_file: unawaited_futures import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; @@ -5,8 +7,8 @@ import 'package:test/test.dart'; class Cat { String sound() => "Meow"; bool eatFood(String food, {bool hungry}) => true; - Future chew() {} - int walk(List places) {} + Future chew() async => print("Chewing..."); + int walk(List places) => 7; void sleep() {} void hunt(String place, String prey) {} int lives = 9; @@ -181,7 +183,7 @@ void main() { test("Fake class", () { // Create a new fake Cat at runtime. - var cat = new FakeCat(); + var cat = FakeCat(); cat.eatFood("Milk"); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); From 28b9075a76b501faaa005c0d44455d6b185a7899 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 3 Sep 2019 15:40:39 -0700 Subject: [PATCH 153/607] Prepare to publish 4.1.1 (dart-lang/mockito#220) Bump version back to 4.1.1 and merge the changelog entries since that version was never published to pub. --- pkgs/mockito/CHANGELOG.md | 7 ++----- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 58dc8e21c..a7b21e70b 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,13 +1,10 @@ -## 4.1.2 - -* Produce a meaningful error message if an argument matcher is used outside of - stubbing (`when`) or verification (`verify` and `untilCalled`). - ## 4.1.1 * Mark the unexported and accidentally public `setDefaultResponse` as deprecated. * Mark the not useful, and not generally used, `named` function as deprecated. +* Produce a meaningful error message if an argument matcher is used outside of + stubbing (`when`) or verification (`verify` and `untilCalled`). ## 4.1.0 diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index affbad8c3..b291b6d9f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.2 +version: 4.1.1 authors: - Dmitriy Fibulwinter From 920423a57b89def4c31c4ed3e420e204693bf99e Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 16 Oct 2019 15:05:35 -0700 Subject: [PATCH 154/607] Improve message when using argument matchers wrong (dart-lang/mockito#224) --- pkgs/mockito/lib/src/mock.dart | 35 ++++++++++++++++++----------- pkgs/mockito/test/mockito_test.dart | 7 ++++++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index e8c6c4608..320586e1a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -266,15 +266,15 @@ class _InvocationForMatchedArguments extends Invocation { // The `namedArguments` in [invocation] which are null should be represented // by a stored value in [_storedNamedArgs]. static Map _reconstituteNamedArgs(Invocation invocation) { - var namedArguments = {}; - var _storedNamedArgSymbols = + final namedArguments = {}; + final storedNamedArgSymbols = _storedNamedArgs.keys.map((name) => Symbol(name)); // Iterate through [invocation]'s named args, validate them, and add them // to the return map. invocation.namedArguments.forEach((name, arg) { if (arg == null) { - if (!_storedNamedArgSymbols.contains(name)) { + if (!storedNamedArgSymbols.contains(name)) { // Either this is a parameter with default value `null`, or a `null` // argument was passed, or an unnamed ArgMatcher was used. Just use // `null`. @@ -318,22 +318,31 @@ class _InvocationForMatchedArguments extends Invocation { } static List _reconstitutePositionalArgs(Invocation invocation) { - var positionalArguments = []; - var nullPositionalArguments = + final positionalArguments = []; + final nullPositionalArguments = invocation.positionalArguments.where((arg) => arg == null); if (_storedArgs.length > nullPositionalArguments.length) { // More _positional_ ArgMatchers were stored than were actually passed as - // positional arguments. The only way this call was parsed and resolved is - // if an ArgMatcher was passed as a named argument, but without a name, - // and thus stored in [_storedArgs], something like - // `when(obj.fn(a: any))`. + // positional arguments. There are three ways this call could have been + // parsed and resolved: + // + // * an ArgMatcher was passed in [invocation] as a named argument, but + // without a name, and thus stored in [_storedArgs], something like + // `when(obj.fn(a: any))`, + // * an ArgMatcher was passed in an expression which was passed in + // [invocation], and thus stored in [_storedArgs], something like + // `when(obj.fn(Foo(any)))`, or + // * a combination of the above. _storedArgs.clear(); _storedNamedArgs.clear(); throw ArgumentError( - 'An argument matcher (like `any`) was used as a named argument, but ' - 'did not use a Mockito "named" API. Each argument matcher that is ' - 'used as a named argument needs to specify the name of the argument ' - 'it is being used in. For example: `when(obj.fn(x: anyNamed("x")))`.'); + 'An argument matcher (like `any`) was either not used as an ' + 'immediate argument to ${invocation.memberName} (argument matchers ' + 'can only be used as an argument for the very method being stubbed ' + 'or verified), or was used as a named argument without the Mockito ' + '"named" API (Each argument matcher that is used as a named argument ' + 'needs to specify the name of the argument it is being used in. For ' + 'example: `when(obj.fn(x: anyNamed("x")))`).'); } int storedIndex = 0; int positionalIndex = 0; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 99da123be..3ac032b44 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -302,4 +302,11 @@ void main() { "verification", () { expect(() => mock.methodWithNormalArgs(any), throwsArgumentError); }); + + test( + "reports an error when using an argument matcher in a position other " + "than an argument for the stubbed method", () { + expect(() => when(mock.methodWithListArgs(List.filled(7, any))), + throwsArgumentError); + }); } From 993be878939208b28b9e09b4a422a624f23727a9 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 16 Dec 2019 21:07:05 -0800 Subject: [PATCH 155/607] Fix newly enforced package:pedantic lints (dart-lang/mockito#230) - omit_local_variable_types - prefer_conditional_assignment - prefer_if_null_operators - prefer_single_quotes - use_function_type_syntax_for_parameters Also ignore a deprecated import. Change a check against null to the string "null" to use string interpolation which already handles `null`. Remove some inferrable argument types on function literals. --- pkgs/mockito/example/example.dart | 96 ++++---- pkgs/mockito/example/iss/iss.dart | 8 +- pkgs/mockito/example/iss/iss_test.dart | 20 +- pkgs/mockito/lib/src/mock.dart | 62 ++--- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/capture_test.dart | 2 +- .../test/deprecated_apis/mockito_test.dart | 88 +++---- .../deprecated_apis/until_called_test.dart | 3 +- .../test/deprecated_apis/verify_test.dart | 2 +- .../mockito/test/invocation_matcher_test.dart | 10 +- pkgs/mockito/test/mockito_test.dart | 214 +++++++++--------- pkgs/mockito/test/until_called_test.dart | 3 +- pkgs/mockito/test/verify_test.dart | 2 +- 13 files changed, 254 insertions(+), 258 deletions(-) diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 9742c3a94..3a708b561 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -5,9 +5,9 @@ import 'package:test/test.dart'; // Real class class Cat { - String sound() => "Meow"; + String sound() => 'Meow'; bool eatFood(String food, {bool hungry}) => true; - Future chew() async => print("Chewing..."); + Future chew() async => print('Chewing...'); int walk(List places) => 7; void sleep() {} void hunt(String place, String prey) {} @@ -42,20 +42,20 @@ void main() { verify(cat.sound()); }); - test("How about some stubbing?", () { + test('How about some stubbing?', () { // Unstubbed methods return null. expect(cat.sound(), null); // Stub a method before interacting with it. - when(cat.sound()).thenReturn("Purr"); - expect(cat.sound(), "Purr"); + when(cat.sound()).thenReturn('Purr'); + expect(cat.sound(), 'Purr'); // You can call it again. - expect(cat.sound(), "Purr"); + expect(cat.sound(), 'Purr'); // Let's change the stub. - when(cat.sound()).thenReturn("Meow"); - expect(cat.sound(), "Meow"); + when(cat.sound()).thenReturn('Meow'); + expect(cat.sound(), 'Meow'); // You can stub getters. when(cat.lives).thenReturn(9); @@ -66,48 +66,48 @@ void main() { expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time. - var responses = ["Purr", "Meow"]; + var responses = ['Purr', 'Meow']; when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); - expect(cat.sound(), "Purr"); - expect(cat.sound(), "Meow"); + expect(cat.sound(), 'Purr'); + expect(cat.sound(), 'Meow'); }); - test("Argument matchers", () { + test('Argument matchers', () { // You can use plain arguments themselves - when(cat.eatFood("fish")).thenReturn(true); + when(cat.eatFood('fish')).thenReturn(true); // ... including collections - when(cat.walk(["roof", "tree"])).thenReturn(2); + when(cat.walk(['roof', 'tree'])).thenReturn(2); // ... or matchers - when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); + when(cat.eatFood(argThat(startsWith('dry')))).thenReturn(false); // ... or mix aguments with matchers - when(cat.eatFood(argThat(startsWith("dry")), hungry: true)) + when(cat.eatFood(argThat(startsWith('dry')), hungry: true)) .thenReturn(true); - expect(cat.eatFood("fish"), isTrue); - expect(cat.walk(["roof", "tree"]), equals(2)); - expect(cat.eatFood("dry food"), isFalse); - expect(cat.eatFood("dry food", hungry: true), isTrue); + expect(cat.eatFood('fish'), isTrue); + expect(cat.walk(['roof', 'tree']), equals(2)); + expect(cat.eatFood('dry food'), isFalse); + expect(cat.eatFood('dry food', hungry: true), isTrue); // You can also verify using an argument matcher. - verify(cat.eatFood("fish")); - verify(cat.walk(["roof", "tree"])); - verify(cat.eatFood(argThat(contains("food")))); + verify(cat.eatFood('fish')); + verify(cat.walk(['roof', 'tree'])); + verify(cat.eatFood(argThat(contains('food')))); // You can verify setters. cat.lives = 9; verify(cat.lives = 9); - cat.hunt("backyard", null); - verify(cat.hunt("backyard", null)); // OK: no arg matchers. + cat.hunt('backyard', null); + verify(cat.hunt('backyard', null)); // OK: no arg matchers. - cat.hunt("backyard", null); - verify(cat.hunt(argThat(contains("yard")), + cat.hunt('backyard', null); + verify(cat.hunt(argThat(contains('yard')), argThat(isNull))); // OK: null is wrapped in an arg matcher. }); - test("Named arguments", () { + test('Named arguments', () { // GOOD: argument matchers include their names. when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(true); when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))) @@ -117,7 +117,7 @@ void main() { .thenReturn(true); }); - test("Verifying exact number of invocations / at least x / never", () { + test('Verifying exact number of invocations / at least x / never', () { cat.sound(); cat.sound(); // Exact number of invocations @@ -133,41 +133,41 @@ void main() { verifyNever(cat.eatFood(any)); }); - test("Verification in order", () { - cat.eatFood("Milk"); + test('Verification in order', () { + cat.eatFood('Milk'); cat.sound(); - cat.eatFood("Fish"); - verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood("Fish")]); + cat.eatFood('Fish'); + verifyInOrder([cat.eatFood('Milk'), cat.sound(), cat.eatFood('Fish')]); }); - test("Making sure interaction(s) never happened on mock", () { + test('Making sure interaction(s) never happened on mock', () { verifyZeroInteractions(cat); }); - test("Finding redundant invocations", () { + test('Finding redundant invocations', () { cat.sound(); verify(cat.sound()); verifyNoMoreInteractions(cat); }); - test("Capturing arguments for further assertions", () { + test('Capturing arguments for further assertions', () { // Simple capture: - cat.eatFood("Fish"); - expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); + cat.eatFood('Fish'); + expect(verify(cat.eatFood(captureAny)).captured.single, 'Fish'); // Capture multiple calls: - cat.eatFood("Milk"); - cat.eatFood("Fish"); - expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); + cat.eatFood('Milk'); + cat.eatFood('Fish'); + expect(verify(cat.eatFood(captureAny)).captured, ['Milk', 'Fish']); // Conditional capture: - cat.eatFood("Milk"); - cat.eatFood("Fish"); + cat.eatFood('Milk'); + cat.eatFood('Fish'); expect( - verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); + verify(cat.eatFood(captureThat(startsWith('F')))).captured, ['Fish']); }); - test("Waiting for an interaction", () async { + test('Waiting for an interaction', () async { Future chewHelper(Cat cat) { return cat.chew(); } @@ -177,15 +177,15 @@ void main() { await untilCalled(cat.chew()); // This completes when cat.chew() is called. // Waiting for a call that has already happened. - cat.eatFood("Fish"); + cat.eatFood('Fish'); await untilCalled(cat.eatFood(any)); // This completes immediately. }); - test("Fake class", () { + test('Fake class', () { // Create a new fake Cat at runtime. var cat = FakeCat(); - cat.eatFood("Milk"); // Prints 'Fake eat Milk'. + cat.eatFood('Milk'); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); }); } diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index e932fd9e0..eef8df43d 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -31,9 +31,7 @@ class IssLocator { /// Returns the current GPS position in [latitude, longitude] format. Future update() async { - if (_ongoingRequest == null) { - _ongoingRequest = _doUpdate(); - } + _ongoingRequest ??= _doUpdate(); await _ongoingRequest; _ongoingRequest = null; } @@ -41,7 +39,7 @@ class IssLocator { Future _doUpdate() async { // Returns the point on the earth directly under the space station // at this moment. - Response rs = await client.get('http://api.open-notify.org/iss-now.json'); + var rs = await client.get('http://api.open-notify.org/iss-now.json'); var data = jsonDecode(rs.body); var latitude = double.parse(data['iss_position']['latitude'] as String); var longitude = double.parse(data['iss_position']['longitude'] as String); @@ -60,7 +58,7 @@ class IssSpotter { // The ISS is defined to be visible if the distance from the observer to // the point on the earth directly under the space station is less than 80km. bool get isVisible { - double distance = sphericalDistanceKm(locator.currentPosition, observer); + var distance = sphericalDistanceKm(locator.currentPosition, observer); return distance < 80.0; } } diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index 3aa2a6247..dc4f3cd86 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -27,18 +27,18 @@ void main() { // verify the calculated distance between them. group('Spherical distance', () { test('London - Paris', () { - Point london = Point(51.5073, -0.1277); - Point paris = Point(48.8566, 2.3522); - double d = sphericalDistanceKm(london, paris); + var london = Point(51.5073, -0.1277); + var paris = Point(48.8566, 2.3522); + var d = sphericalDistanceKm(london, paris); // London should be approximately 343.5km // (+/- 0.1km) from Paris. expect(d, closeTo(343.5, 0.1)); }); test('San Francisco - Mountain View', () { - Point sf = Point(37.783333, -122.416667); - Point mtv = Point(37.389444, -122.081944); - double d = sphericalDistanceKm(sf, mtv); + var sf = Point(37.783333, -122.416667); + var mtv = Point(37.389444, -122.081944); + var d = sphericalDistanceKm(sf, mtv); // San Francisco should be approximately 52.8km // (+/- 0.1km) from Mountain View. expect(d, closeTo(52.8, 0.1)); @@ -52,8 +52,8 @@ void main() { // second predefined location. This test runs asynchronously. group('ISS spotter', () { test('ISS visible', () async { - Point sf = Point(37.783333, -122.416667); - Point mtv = Point(37.389444, -122.081944); + var sf = Point(37.783333, -122.416667); + var mtv = Point(37.389444, -122.081944); IssLocator locator = MockIssLocator(); // Mountain View should be visible from San Francisco. when(locator.currentPosition).thenReturn(sf); @@ -63,8 +63,8 @@ void main() { }); test('ISS not visible', () async { - Point london = Point(51.5073, -0.1277); - Point mtv = Point(37.389444, -122.081944); + var london = Point(51.5073, -0.1277); + var mtv = Point(37.389444, -122.081944); IssLocator locator = MockIssLocator(); // London should not be visible from Mountain View. when(locator.currentPosition).thenReturn(london); diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 320586e1a..d83c0701d 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,6 +17,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; +// ignore: deprecated_member_use import 'package:test_api/test_api.dart'; // TODO(srawlins): Remove this when we no longer need to check for an // incompatiblity between test_api and test. @@ -37,7 +38,8 @@ final Map _storedNamedArgs = {}; @Deprecated( 'This function is not a supported function, and may be deleted as early as ' 'Mockito 5.0.0') -void setDefaultResponse(Mock mock, CallPair defaultResponse()) { +void setDefaultResponse( + Mock mock, CallPair Function() defaultResponse) { mock._defaultResponse = defaultResponse; } @@ -136,7 +138,7 @@ class Mock { const Object().noSuchMethod(invocation); @override - int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; + int get hashCode => _givenHashCode ?? 0; @override bool operator ==(other) => (_givenHashCode != null && other is Mock) @@ -144,7 +146,7 @@ class Mock { : identical(this, other); @override - String toString() => _givenName != null ? _givenName : runtimeType.toString(); + String toString() => _givenName ?? runtimeType.toString(); String _realCallsToString() { var stringRepresentations = _realCalls.map((call) => call.toString()); @@ -238,7 +240,7 @@ class _InvocationForMatchedArguments extends Invocation { factory _InvocationForMatchedArguments(Invocation invocation) { if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) { throw StateError( - "_InvocationForMatchedArguments called when no ArgMatchers have been saved."); + '_InvocationForMatchedArguments called when no ArgMatchers have been saved.'); } // Handle named arguments first, so that we can provide useful errors for @@ -289,7 +291,7 @@ class _InvocationForMatchedArguments extends Invocation { // Iterate through the stored named args, validate them, and add them to // the return map. _storedNamedArgs.forEach((name, arg) { - Symbol nameSymbol = Symbol(name); + var nameSymbol = Symbol(name); if (!invocation.namedArguments.containsKey(nameSymbol)) { // Clear things out for the next call. _storedArgs.clear(); @@ -344,8 +346,8 @@ class _InvocationForMatchedArguments extends Invocation { 'needs to specify the name of the argument it is being used in. For ' 'example: `when(obj.fn(x: anyNamed("x")))`).'); } - int storedIndex = 0; - int positionalIndex = 0; + var storedIndex = 0; + var positionalIndex = 0; while (storedIndex < _storedArgs.length && positionalIndex < invocation.positionalArguments.length) { var arg = _storedArgs[storedIndex]; @@ -456,7 +458,7 @@ class InvocationMatcher { } void _captureArguments(Invocation invocation) { - int index = 0; + var index = 0; for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; if (roleArg is ArgMatcher && roleArg._capture) { @@ -484,7 +486,7 @@ class InvocationMatcher { roleInvocation.namedArguments.length) { return false; } - int index = 0; + var index = 0; for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; if (!isMatchingArg(roleArg, actArg)) { @@ -541,8 +543,7 @@ class RealCall { @override String toString() { var argString = ''; - var args = invocation.positionalArguments - .map((v) => v == null ? "null" : v.toString()); + var args = invocation.positionalArguments.map((v) => '$v'); if (args.any((arg) => arg.contains('\n'))) { // As one or more arg contains newlines, put each on its own line, and // indent each, for better readability. @@ -649,18 +650,18 @@ class _VerifyCall { if (!never && matchingInvocations.isEmpty) { var message; if (mock._realCalls.isEmpty) { - message = "No matching calls (actually, no calls at all)."; + message = 'No matching calls (actually, no calls at all).'; } else { var otherCalls = mock._realCallsToString(); - message = "No matching calls. All calls: $otherCalls"; + message = 'No matching calls. All calls: $otherCalls'; } - fail("$message\n" - "(If you called `verify(...).called(0);`, please instead use " - "`verifyNever(...);`.)"); + fail('$message\n' + '(If you called `verify(...).called(0);`, please instead use ' + '`verifyNever(...);`.)'); } if (never && matchingInvocations.isNotEmpty) { var calls = mock._realCallsToString(); - fail("Unexpected calls. All calls: $calls"); + fail('Unexpected calls. All calls: $calls'); } matchingInvocations.forEach((inv) { inv.verified = true; @@ -873,7 +874,7 @@ class VerificationResult { _checkTestApiMismatch(); } expect(callCount, wrapMatcher(matcher), - reason: "Unexpected number of calls"); + reason: 'Unexpected number of calls'); } } @@ -939,12 +940,12 @@ Verification _makeVerify(bool never) { return (T mock) { _verificationInProgress = false; if (_verifyCalls.length == 1) { - _VerifyCall verifyCall = _verifyCalls.removeLast(); + var verifyCall = _verifyCalls.removeLast(); var result = VerificationResult._(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; } else { - fail("Used on a non-mockito object"); + fail('Used on a non-mockito object'); } }; } @@ -970,23 +971,22 @@ _InOrderVerification get verifyInOrder { _verificationInProgress = true; return (List _) { _verificationInProgress = false; - DateTime dt = DateTime.fromMillisecondsSinceEpoch(0); + var dt = DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); _verifyCalls.clear(); - List matchedCalls = []; - for (_VerifyCall verifyCall in tmpVerifyCalls) { - RealCall matched = verifyCall._findAfter(dt); + var matchedCalls = []; + for (var verifyCall in tmpVerifyCalls) { + var matched = verifyCall._findAfter(dt); if (matched != null) { matchedCalls.add(matched); dt = matched.timeStamp; } else { - Set mocks = - tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); - List allInvocations = + var mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); + var allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); - String otherCalls = ""; + var otherCalls = ''; if (allInvocations.isNotEmpty) { otherCalls = " All calls: ${allInvocations.join(", ")}"; } @@ -1011,7 +1011,7 @@ void verifyNoMoreInteractions(var mock) { if (mock is Mock) { var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); if (unverified.isNotEmpty) { - fail("No more calls expected, but following found: " + unverified.join()); + fail('No more calls expected, but following found: ' + unverified.join()); } } else { _throwMockArgumentError('verifyNoMoreInteractions', mock); @@ -1021,7 +1021,7 @@ void verifyNoMoreInteractions(var mock) { void verifyZeroInteractions(var mock) { if (mock is Mock) { if (mock._realCalls.isNotEmpty) { - fail("No interaction expected, but following found: " + + fail('No interaction expected, but following found: ' + mock._realCalls.join()); } } else { @@ -1084,7 +1084,7 @@ InvocationLoader get untilCalled { /// Print all collected invocations of any mock methods of [mocks]. void logInvocations(List mocks) { - List allInvocations = + var allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); allInvocations.forEach((inv) { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b291b6d9f..1f438d987 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.1 +version: 4.1.2-dev authors: - Dmitriy Fibulwinter diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 09da04522..7c597ba38 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -22,7 +22,7 @@ class _RealClass { String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; set setter(String arg) { throw StateError('I must be mocked'); } diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index 95dbcb492..ab9a4e33e 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -24,18 +24,18 @@ import 'package:test/test.dart'; class _RealClass { _RealClass innerObj; - String methodWithoutArgs() => "Real"; - String methodWithNormalArgs(int x) => "Real"; - String methodWithListArgs(List x) => "Real"; - String methodWithPositionalArgs(int x, [int y]) => "Real"; - String methodWithNamedArgs(int x, {int y}) => "Real"; - String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; - String methodWithObjArgs(_RealClass x) => "Real"; - Future methodReturningFuture() => Future.value("Real"); - Stream methodReturningStream() => Stream.fromIterable(["Real"]); - String get getter => "Real"; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; + Future methodReturningFuture() => Future.value('Real'); + Stream methodReturningStream() => Stream.fromIterable(['Real']); + String get getter => 'Real'; set setter(String arg) { - throw StateError("I must be mocked"); + throw StateError('I must be mocked'); } } @@ -54,23 +54,23 @@ class MockFoo extends AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, dynamic expectedToFail()) { +void expectFail(String expectedMessage, void Function() expectedToFail) { try { expectedToFail(); - fail("It was expected to fail!"); + fail('It was expected to fail!'); } catch (e) { if (!(e is TestFailure)) { rethrow; } else { if (expectedMessage != e.message) { - throw TestFailure("Failed, but with wrong message: ${e.message}"); + throw TestFailure('Failed, but with wrong message: ${e.message}'); } } } } -String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " - "please instead use `verifyNever(...);`.)"; +String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' + 'please instead use `verifyNever(...);`.)'; void main() { _MockedClass mock; @@ -85,67 +85,67 @@ void main() { resetMockitoState(); }); - group("when()", () { - test("should mock method with argument matcher", () { + group('when()', () { + test('should mock method with argument matcher', () { when(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))) - .thenReturn("A lot!"); + .thenReturn('A lot!'); expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any argument matcher", () { - when(mock.methodWithNormalArgs(typed(any))).thenReturn("A lot!"); - expect(mock.methodWithNormalArgs(100), equals("A lot!")); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + test('should mock method with any argument matcher', () { + when(mock.methodWithNormalArgs(typed(any))).thenReturn('A lot!'); + expect(mock.methodWithNormalArgs(100), equals('A lot!')); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any list argument matcher", () { - when(mock.methodWithListArgs(typed(any))).thenReturn("A lot!"); - expect(mock.methodWithListArgs([42]), equals("A lot!")); - expect(mock.methodWithListArgs([43]), equals("A lot!")); + test('should mock method with any list argument matcher', () { + when(mock.methodWithListArgs(typed(any))).thenReturn('A lot!'); + expect(mock.methodWithListArgs([42]), equals('A lot!')); + expect(mock.methodWithListArgs([43]), equals('A lot!')); }); - test("should mock method with mix of argument matchers and real things", + test('should mock method with mix of argument matchers and real things', () { when(mock.methodWithPositionalArgs(typed(argThat(greaterThan(100))), 17)) - .thenReturn("A lot with 17"); + .thenReturn('A lot with 17'); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); + expect(mock.methodWithPositionalArgs(101, 17), equals('A lot with 17')); }); //no need to mock setter, except if we will have spies later... - test("should mock method with thrown result", () { + test('should mock method with thrown result', () { when(mock.methodWithNormalArgs(typed(any))).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); - test("should mock method with calculated result", () { + test('should mock method with calculated result', () { when(mock.methodWithNormalArgs(typed(any))).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals("43")); - expect(mock.methodWithNormalArgs(42), equals("42")); + expect(mock.methodWithNormalArgs(43), equals('43')); + expect(mock.methodWithNormalArgs(42), equals('42')); }); - test("should mock method with calculated result", () { + test('should mock method with calculated result', () { when(mock.methodWithNormalArgs(typed(argThat(equals(43))))) - .thenReturn("43"); + .thenReturn('43'); when(mock.methodWithNormalArgs(typed(argThat(equals(42))))) - .thenReturn("42"); - expect(mock.methodWithNormalArgs(43), equals("43")); + .thenReturn('42'); + expect(mock.methodWithNormalArgs(43), equals('43')); }); - test("should mock hashCode", () { + test('should mock hashCode', () { named(mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); - test("should have toString as name when it is not mocked", () { - named(mock, name: "Cat"); - expect(mock.toString(), equals("Cat")); + test('should have toString as name when it is not mocked', () { + named(mock, name: 'Cat'); + expect(mock.toString(), equals('Cat')); }); - test("should mock equals between mocks when givenHashCode is equals", () { + test('should mock equals between mocks when givenHashCode is equals', () { var anotherMock = named(_MockedClass(), hashCode: 42); named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart index 92ed48759..f4e6801ed 100644 --- a/pkgs/mockito/test/deprecated_apis/until_called_test.dart +++ b/pkgs/mockito/test/deprecated_apis/until_called_test.dart @@ -86,8 +86,7 @@ void main() { }); group('untilCalled', () { - StreamController streamController = - StreamController.broadcast(); + var streamController = StreamController.broadcast(); group('on methods already called', () { test('waits for method with normal args', () async { diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index a8c870c17..74cfdcba8 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -57,7 +57,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, dynamic expectedToFail()) { +void expectFail(String expectedMessage, void Function() expectedToFail) { try { expectedToFail(); fail('It was expected to fail!'); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 12eae076b..dc5bb5252 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -66,9 +66,9 @@ void main() { shouldFail( call1, isInvocation(call3), - "Expected: lie() " + 'Expected: lie() ' "Actual: " - "Which: Does not match lie()", + 'Which: Does not match lie()', ); }); @@ -102,9 +102,9 @@ void main() { call1, isInvocation(call3), // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - RegExp("Expected: set value=? " + RegExp('Expected: set value=? ' "Actual: " - "Which: Does not match set value=? "), + 'Which: Does not match set value=? '), ); }); }); @@ -118,7 +118,7 @@ void main() { shouldFail( call, invokes(#say, positionalArguments: [isNull]), - "Expected: say(null) " + 'Expected: say(null) ' "Actual: " "Which: Does not match say('Hello')", ); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 3ac032b44..e31cde2b8 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -21,16 +21,16 @@ import 'utils.dart'; class _RealClass { _RealClass innerObj; - String methodWithoutArgs() => "Real"; - String methodWithNormalArgs(int x) => "Real"; - String methodWithListArgs(List x) => "Real"; - String methodWithPositionalArgs(int x, [int y]) => "Real"; - String methodWithNamedArgs(int x, {int y}) => "Real"; - String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; - String methodWithObjArgs(_RealClass x) => "Real"; - Future methodReturningFuture() => Future.value("Real"); - Stream methodReturningStream() => Stream.fromIterable(["Real"]); - String get getter => "Real"; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; + Future methodReturningFuture() => Future.value('Real'); + Stream methodReturningStream() => Stream.fromIterable(['Real']); + String get getter => 'Real'; } abstract class _Foo { @@ -43,30 +43,30 @@ abstract class _AbstractFoo implements _Foo { String baz(); - String quux() => "Real"; + String quux() => 'Real'; } class _MockFoo extends _AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, dynamic expectedToFail()) { +void expectFail(String expectedMessage, void Function() expectedToFail) { try { expectedToFail(); - fail("It was expected to fail!"); + fail('It was expected to fail!'); } catch (e) { if (!(e is TestFailure)) { rethrow; } else { if (expectedMessage != e.message) { - throw TestFailure("Failed, but with wrong message: ${e.message}"); + throw TestFailure('Failed, but with wrong message: ${e.message}'); } } } } -String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " - "please instead use `verifyNever(...);`.)"; +String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' + 'please instead use `verifyNever(...);`.)'; void main() { _MockedClass mock; @@ -83,229 +83,229 @@ void main() { resetMockitoState(); }); - group("mixin support", () { - test("should work", () { + group('mixin support', () { + test('should work', () { var foo = _MockFoo(); when(foo.baz()).thenReturn('baz'); expect(foo.bar(), 'baz'); }); }); - group("when()", () { - test("should mock method without args", () { - when(mock.methodWithoutArgs()).thenReturn("A"); - expect(mock.methodWithoutArgs(), equals("A")); + group('when()', () { + test('should mock method without args', () { + when(mock.methodWithoutArgs()).thenReturn('A'); + expect(mock.methodWithoutArgs(), equals('A')); }); - test("should mock method with normal args", () { - when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); + test('should mock method with normal args', () { + when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); expect(mock.methodWithNormalArgs(43), isNull); - expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); + expect(mock.methodWithNormalArgs(42), equals('Ultimate Answer')); }); - test("should mock method with mock args", () { + test('should mock method with mock args', () { var m1 = _MockedClass(); - when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); + when(mock.methodWithObjArgs(m1)).thenReturn('Ultimate Answer'); expect(mock.methodWithObjArgs(_MockedClass()), isNull); - expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); + expect(mock.methodWithObjArgs(m1), equals('Ultimate Answer')); }); - test("should mock method with positional args", () { - when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); + test('should mock method with positional args', () { + when(mock.methodWithPositionalArgs(42, 17)).thenReturn('Answer and...'); expect(mock.methodWithPositionalArgs(42), isNull); expect(mock.methodWithPositionalArgs(42, 18), isNull); - expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); + expect(mock.methodWithPositionalArgs(42, 17), equals('Answer and...')); }); - test("should mock method with named args", () { - when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); + test('should mock method with named args', () { + when(mock.methodWithNamedArgs(42, y: 17)).thenReturn('Why answer?'); expect(mock.methodWithNamedArgs(42), isNull); expect(mock.methodWithNamedArgs(42, y: 18), isNull); - expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); + expect(mock.methodWithNamedArgs(42, y: 17), equals('Why answer?')); }); - test("should mock method with List args", () { - when(mock.methodWithListArgs([42])).thenReturn("Ultimate answer"); + test('should mock method with List args', () { + when(mock.methodWithListArgs([42])).thenReturn('Ultimate answer'); expect(mock.methodWithListArgs([43]), isNull); - expect(mock.methodWithListArgs([42]), equals("Ultimate answer")); + expect(mock.methodWithListArgs([42]), equals('Ultimate answer')); }); - test("should mock method with argument matcher", () { + test('should mock method with argument matcher', () { when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) - .thenReturn("A lot!"); + .thenReturn('A lot!'); expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any argument matcher", () { - when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); - expect(mock.methodWithNormalArgs(100), equals("A lot!")); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + test('should mock method with any argument matcher', () { + when(mock.methodWithNormalArgs(any)).thenReturn('A lot!'); + expect(mock.methodWithNormalArgs(100), equals('A lot!')); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any list argument matcher", () { - when(mock.methodWithListArgs(any)).thenReturn("A lot!"); - expect(mock.methodWithListArgs([42]), equals("A lot!")); - expect(mock.methodWithListArgs([43]), equals("A lot!")); + test('should mock method with any list argument matcher', () { + when(mock.methodWithListArgs(any)).thenReturn('A lot!'); + expect(mock.methodWithListArgs([42]), equals('A lot!')); + expect(mock.methodWithListArgs([43]), equals('A lot!')); }); - test("should mock method with multiple named args and matchers", () { + test('should mock method with multiple named args and matchers', () { when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'))) - .thenReturn("x y"); + .thenReturn('x y'); when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) - .thenReturn("x z"); + .thenReturn('x z'); if (isNsmForwarding) { - expect(mock.methodWithTwoNamedArgs(42), "x z"); + expect(mock.methodWithTwoNamedArgs(42), 'x z'); } else { expect(mock.methodWithTwoNamedArgs(42), isNull); } - expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); - expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); + expect(mock.methodWithTwoNamedArgs(42, y: 18), equals('x y')); + expect(mock.methodWithTwoNamedArgs(42, z: 17), equals('x z')); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'), z: anyNamed('z'))) - .thenReturn("x y z"); - expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); + .thenReturn('x y z'); + expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals('x y z')); }); - test("should mock method with mix of argument matchers and real things", + test('should mock method with mix of argument matchers and real things', () { when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) - .thenReturn("A lot with 17"); + .thenReturn('A lot with 17'); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); + expect(mock.methodWithPositionalArgs(101, 17), equals('A lot with 17')); }); - test("should mock getter", () { - when(mock.getter).thenReturn("A"); - expect(mock.getter, equals("A")); + test('should mock getter', () { + when(mock.getter).thenReturn('A'); + expect(mock.getter, equals('A')); }); - test("should have hashCode when it is not mocked", () { + test('should have hashCode when it is not mocked', () { expect(mock.hashCode, isNotNull); }); - test("should have default toString when it is not mocked", () { - expect(mock.toString(), equals("_MockedClass")); + test('should have default toString when it is not mocked', () { + expect(mock.toString(), equals('_MockedClass')); }); - test("should use identical equality between it is not mocked", () { + test('should use identical equality between it is not mocked', () { var anotherMock = _MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); - test("should mock method with thrown result", () { + test('should mock method with thrown result', () { when(mock.methodWithNormalArgs(any)).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); - test("should mock method with calculated result", () { + test('should mock method with calculated result', () { when(mock.methodWithNormalArgs(any)).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals("43")); - expect(mock.methodWithNormalArgs(42), equals("42")); + expect(mock.methodWithNormalArgs(43), equals('43')); + expect(mock.methodWithNormalArgs(42), equals('42')); }); - test("should return mock to make simple oneline mocks", () { + test('should return mock to make simple oneline mocks', () { _RealClass mockWithSetup = _MockedClass(); - when(mockWithSetup.methodWithoutArgs()).thenReturn("oneline"); - expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); + when(mockWithSetup.methodWithoutArgs()).thenReturn('oneline'); + expect(mockWithSetup.methodWithoutArgs(), equals('oneline')); }); - test("should use latest matching when definition", () { - when(mock.methodWithoutArgs()).thenReturn("A"); - when(mock.methodWithoutArgs()).thenReturn("B"); - expect(mock.methodWithoutArgs(), equals("B")); + test('should use latest matching when definition', () { + when(mock.methodWithoutArgs()).thenReturn('A'); + when(mock.methodWithoutArgs()).thenReturn('B'); + expect(mock.methodWithoutArgs(), equals('B')); }); - test("should mock method with calculated result", () { - when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); - when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); - expect(mock.methodWithNormalArgs(43), equals("43")); + test('should mock method with calculated result', () { + when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn('43'); + when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn('42'); + expect(mock.methodWithNormalArgs(43), equals('43')); }); // Error path tests. - test("should throw if `when` is called while stubbing", () { + test('should throw if `when` is called while stubbing', () { expect(() { var responseHelper = () { var mock2 = _MockedClass(); - when(mock2.getter).thenReturn("A"); + when(mock2.getter).thenReturn('A'); return mock2; }; when(mock.innerObj).thenReturn(responseHelper()); }, throwsStateError); }); - test("thenReturn throws if provided Future", () { + test('thenReturn throws if provided Future', () { expect( () => when(mock.methodReturningFuture()) - .thenReturn(Future.value("stub")), + .thenReturn(Future.value('stub')), throwsArgumentError); }); - test("thenReturn throws if provided Stream", () { + test('thenReturn throws if provided Stream', () { expect( () => when(mock.methodReturningStream()) - .thenReturn(Stream.fromIterable(["stub"])), + .thenReturn(Stream.fromIterable(['stub'])), throwsArgumentError); }); - test("thenAnswer supports stubbing method returning a Future", () async { + test('thenAnswer supports stubbing method returning a Future', () async { when(mock.methodReturningFuture()) - .thenAnswer((_) => Future.value("stub")); + .thenAnswer((_) => Future.value('stub')); - expect(await mock.methodReturningFuture(), "stub"); + expect(await mock.methodReturningFuture(), 'stub'); }); - test("thenAnswer supports stubbing method returning a Stream", () async { + test('thenAnswer supports stubbing method returning a Stream', () async { when(mock.methodReturningStream()) - .thenAnswer((_) => Stream.fromIterable(["stub"])); + .thenAnswer((_) => Stream.fromIterable(['stub'])); - expect(await mock.methodReturningStream().toList(), ["stub"]); + expect(await mock.methodReturningStream().toList(), ['stub']); }); - test("should throw if named matcher is passed as the wrong name", () { + test('should throw if named matcher is passed as the wrong name', () { expect(() { - when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed("z"))) - .thenReturn("99"); + when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed('z'))) + .thenReturn('99'); }, throwsArgumentError); }); - test("should throw if attempting to stub a real method", () { + test('should throw if attempting to stub a real method', () { var foo = _MockFoo(); expect(() { - when(foo.quux()).thenReturn("Stub"); + when(foo.quux()).thenReturn('Stub'); }, throwsStateError); }); }); - group("throwOnMissingStub", () { - test("should throw when a mock was called without a matching stub", () { + group('throwOnMissingStub', () { + test('should throw when a mock was called without a matching stub', () { throwOnMissingStub(mock); - when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); + when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); expect( () => (mock).methodWithoutArgs(), throwsNoSuchMethodError, ); }); - test("should not throw when a mock was called with a matching stub", () { + test('should not throw when a mock was called with a matching stub', () { throwOnMissingStub(mock); - when(mock.methodWithoutArgs()).thenReturn("A"); + when(mock.methodWithoutArgs()).thenReturn('A'); expect(() => mock.methodWithoutArgs(), returnsNormally); }); }); test( - "reports an error when using an argument matcher outside of stubbing or " - "verification", () { + 'reports an error when using an argument matcher outside of stubbing or ' + 'verification', () { expect(() => mock.methodWithNormalArgs(any), throwsArgumentError); }); test( - "reports an error when using an argument matcher in a position other " - "than an argument for the stubbed method", () { + 'reports an error when using an argument matcher in a position other ' + 'than an argument for the stubbed method', () { expect(() => when(mock.methodWithListArgs(List.filled(7, any))), throwsArgumentError); }); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index 634c59c0f..4a0b03fd9 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -81,8 +81,7 @@ void main() { }); group('untilCalled', () { - StreamController streamController = - StreamController.broadcast(); + var streamController = StreamController.broadcast(); group('on methods already called', () { test('waits for method without args', () async { diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 0c41c2941..7622a2b0a 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -54,7 +54,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -void expectFail(Pattern expectedMessage, dynamic expectedToFail()) { +void expectFail(Pattern expectedMessage, void Function() expectedToFail) { try { expectedToFail(); fail('It was expected to fail!'); From 456040bf6461643bf34f25fe44b3445a33ef7665 Mon Sep 17 00:00:00 2001 From: Casey Hillers Date: Mon, 23 Dec 2019 11:34:28 -0800 Subject: [PATCH 156/607] Update README.md example to use Invocation (dart-lang/mockito#231) --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 5b70df8ab..09b6878d0 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -65,7 +65,7 @@ expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time. var responses = ["Purr", "Meow"]; -when(cat.sound()).thenAnswer(() => responses.removeAt(0)); +when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); expect(cat.sound(), "Purr"); expect(cat.sound(), "Meow"); ``` From f8129f41ee37c12d57929c883b4a33ede55e8086 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 12 Nov 2019 10:17:43 -0800 Subject: [PATCH 157/607] Add scaffolding of code-generation API for Mockito. PiperOrigin-RevId: 279997785 --- pkgs/mockito/README.md | 2 +- pkgs/mockito/bin/codegen.dart | 19 ++ pkgs/mockito/build.yaml | 6 + pkgs/mockito/example/example.dart | 96 +++---- pkgs/mockito/example/iss/iss.dart | 8 +- pkgs/mockito/example/iss/iss_test.dart | 20 +- pkgs/mockito/lib/annotations.dart | 19 ++ pkgs/mockito/lib/src/builder.dart | 228 +++++++++++++++ pkgs/mockito/lib/src/mock.dart | 62 ++-- pkgs/mockito/pubspec.yaml | 8 +- pkgs/mockito/test/all.dart | 2 + pkgs/mockito/test/builder_test.dart | 266 ++++++++++++++++++ pkgs/mockito/test/capture_test.dart | 2 +- .../test/deprecated_apis/mockito_test.dart | 88 +++--- .../deprecated_apis/until_called_test.dart | 3 +- .../test/deprecated_apis/verify_test.dart | 2 +- .../mockito/test/invocation_matcher_test.dart | 10 +- pkgs/mockito/test/mockito_test.dart | 212 +++++++------- pkgs/mockito/test/until_called_test.dart | 3 +- pkgs/mockito/test/verify_test.dart | 2 +- 20 files changed, 803 insertions(+), 255 deletions(-) create mode 100644 pkgs/mockito/bin/codegen.dart create mode 100644 pkgs/mockito/build.yaml create mode 100644 pkgs/mockito/lib/annotations.dart create mode 100644 pkgs/mockito/lib/src/builder.dart create mode 100644 pkgs/mockito/test/builder_test.dart diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 09b6878d0..5b70df8ab 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -65,7 +65,7 @@ expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time. var responses = ["Purr", "Meow"]; -when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); +when(cat.sound()).thenAnswer(() => responses.removeAt(0)); expect(cat.sound(), "Purr"); expect(cat.sound(), "Meow"); ``` diff --git a/pkgs/mockito/bin/codegen.dart b/pkgs/mockito/bin/codegen.dart new file mode 100644 index 000000000..f3ec2b59d --- /dev/null +++ b/pkgs/mockito/bin/codegen.dart @@ -0,0 +1,19 @@ +// Copyright 2019 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:mockito/src/builder.dart'; + +import 'package:build/build.dart'; + +Builder buildMocks(BuilderOptions options) => MockBuilder(); diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml new file mode 100644 index 000000000..755c286db --- /dev/null +++ b/pkgs/mockito/build.yaml @@ -0,0 +1,6 @@ +builders: + mockBuilder: + import: "package:mockito/src/builder.dart" + builder_factories: ["buildMocks"] + build_extensions: {".dart": [".mocks.dart"]} + build_to: source diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 3a708b561..9742c3a94 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -5,9 +5,9 @@ import 'package:test/test.dart'; // Real class class Cat { - String sound() => 'Meow'; + String sound() => "Meow"; bool eatFood(String food, {bool hungry}) => true; - Future chew() async => print('Chewing...'); + Future chew() async => print("Chewing..."); int walk(List places) => 7; void sleep() {} void hunt(String place, String prey) {} @@ -42,20 +42,20 @@ void main() { verify(cat.sound()); }); - test('How about some stubbing?', () { + test("How about some stubbing?", () { // Unstubbed methods return null. expect(cat.sound(), null); // Stub a method before interacting with it. - when(cat.sound()).thenReturn('Purr'); - expect(cat.sound(), 'Purr'); + when(cat.sound()).thenReturn("Purr"); + expect(cat.sound(), "Purr"); // You can call it again. - expect(cat.sound(), 'Purr'); + expect(cat.sound(), "Purr"); // Let's change the stub. - when(cat.sound()).thenReturn('Meow'); - expect(cat.sound(), 'Meow'); + when(cat.sound()).thenReturn("Meow"); + expect(cat.sound(), "Meow"); // You can stub getters. when(cat.lives).thenReturn(9); @@ -66,48 +66,48 @@ void main() { expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time. - var responses = ['Purr', 'Meow']; + var responses = ["Purr", "Meow"]; when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); - expect(cat.sound(), 'Purr'); - expect(cat.sound(), 'Meow'); + expect(cat.sound(), "Purr"); + expect(cat.sound(), "Meow"); }); - test('Argument matchers', () { + test("Argument matchers", () { // You can use plain arguments themselves - when(cat.eatFood('fish')).thenReturn(true); + when(cat.eatFood("fish")).thenReturn(true); // ... including collections - when(cat.walk(['roof', 'tree'])).thenReturn(2); + when(cat.walk(["roof", "tree"])).thenReturn(2); // ... or matchers - when(cat.eatFood(argThat(startsWith('dry')))).thenReturn(false); + when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); // ... or mix aguments with matchers - when(cat.eatFood(argThat(startsWith('dry')), hungry: true)) + when(cat.eatFood(argThat(startsWith("dry")), hungry: true)) .thenReturn(true); - expect(cat.eatFood('fish'), isTrue); - expect(cat.walk(['roof', 'tree']), equals(2)); - expect(cat.eatFood('dry food'), isFalse); - expect(cat.eatFood('dry food', hungry: true), isTrue); + expect(cat.eatFood("fish"), isTrue); + expect(cat.walk(["roof", "tree"]), equals(2)); + expect(cat.eatFood("dry food"), isFalse); + expect(cat.eatFood("dry food", hungry: true), isTrue); // You can also verify using an argument matcher. - verify(cat.eatFood('fish')); - verify(cat.walk(['roof', 'tree'])); - verify(cat.eatFood(argThat(contains('food')))); + verify(cat.eatFood("fish")); + verify(cat.walk(["roof", "tree"])); + verify(cat.eatFood(argThat(contains("food")))); // You can verify setters. cat.lives = 9; verify(cat.lives = 9); - cat.hunt('backyard', null); - verify(cat.hunt('backyard', null)); // OK: no arg matchers. + cat.hunt("backyard", null); + verify(cat.hunt("backyard", null)); // OK: no arg matchers. - cat.hunt('backyard', null); - verify(cat.hunt(argThat(contains('yard')), + cat.hunt("backyard", null); + verify(cat.hunt(argThat(contains("yard")), argThat(isNull))); // OK: null is wrapped in an arg matcher. }); - test('Named arguments', () { + test("Named arguments", () { // GOOD: argument matchers include their names. when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(true); when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))) @@ -117,7 +117,7 @@ void main() { .thenReturn(true); }); - test('Verifying exact number of invocations / at least x / never', () { + test("Verifying exact number of invocations / at least x / never", () { cat.sound(); cat.sound(); // Exact number of invocations @@ -133,41 +133,41 @@ void main() { verifyNever(cat.eatFood(any)); }); - test('Verification in order', () { - cat.eatFood('Milk'); + test("Verification in order", () { + cat.eatFood("Milk"); cat.sound(); - cat.eatFood('Fish'); - verifyInOrder([cat.eatFood('Milk'), cat.sound(), cat.eatFood('Fish')]); + cat.eatFood("Fish"); + verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood("Fish")]); }); - test('Making sure interaction(s) never happened on mock', () { + test("Making sure interaction(s) never happened on mock", () { verifyZeroInteractions(cat); }); - test('Finding redundant invocations', () { + test("Finding redundant invocations", () { cat.sound(); verify(cat.sound()); verifyNoMoreInteractions(cat); }); - test('Capturing arguments for further assertions', () { + test("Capturing arguments for further assertions", () { // Simple capture: - cat.eatFood('Fish'); - expect(verify(cat.eatFood(captureAny)).captured.single, 'Fish'); + cat.eatFood("Fish"); + expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); // Capture multiple calls: - cat.eatFood('Milk'); - cat.eatFood('Fish'); - expect(verify(cat.eatFood(captureAny)).captured, ['Milk', 'Fish']); + cat.eatFood("Milk"); + cat.eatFood("Fish"); + expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); // Conditional capture: - cat.eatFood('Milk'); - cat.eatFood('Fish'); + cat.eatFood("Milk"); + cat.eatFood("Fish"); expect( - verify(cat.eatFood(captureThat(startsWith('F')))).captured, ['Fish']); + verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); }); - test('Waiting for an interaction', () async { + test("Waiting for an interaction", () async { Future chewHelper(Cat cat) { return cat.chew(); } @@ -177,15 +177,15 @@ void main() { await untilCalled(cat.chew()); // This completes when cat.chew() is called. // Waiting for a call that has already happened. - cat.eatFood('Fish'); + cat.eatFood("Fish"); await untilCalled(cat.eatFood(any)); // This completes immediately. }); - test('Fake class', () { + test("Fake class", () { // Create a new fake Cat at runtime. var cat = FakeCat(); - cat.eatFood('Milk'); // Prints 'Fake eat Milk'. + cat.eatFood("Milk"); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); }); } diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index eef8df43d..e932fd9e0 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -31,7 +31,9 @@ class IssLocator { /// Returns the current GPS position in [latitude, longitude] format. Future update() async { - _ongoingRequest ??= _doUpdate(); + if (_ongoingRequest == null) { + _ongoingRequest = _doUpdate(); + } await _ongoingRequest; _ongoingRequest = null; } @@ -39,7 +41,7 @@ class IssLocator { Future _doUpdate() async { // Returns the point on the earth directly under the space station // at this moment. - var rs = await client.get('http://api.open-notify.org/iss-now.json'); + Response rs = await client.get('http://api.open-notify.org/iss-now.json'); var data = jsonDecode(rs.body); var latitude = double.parse(data['iss_position']['latitude'] as String); var longitude = double.parse(data['iss_position']['longitude'] as String); @@ -58,7 +60,7 @@ class IssSpotter { // The ISS is defined to be visible if the distance from the observer to // the point on the earth directly under the space station is less than 80km. bool get isVisible { - var distance = sphericalDistanceKm(locator.currentPosition, observer); + double distance = sphericalDistanceKm(locator.currentPosition, observer); return distance < 80.0; } } diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index dc4f3cd86..3aa2a6247 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -27,18 +27,18 @@ void main() { // verify the calculated distance between them. group('Spherical distance', () { test('London - Paris', () { - var london = Point(51.5073, -0.1277); - var paris = Point(48.8566, 2.3522); - var d = sphericalDistanceKm(london, paris); + Point london = Point(51.5073, -0.1277); + Point paris = Point(48.8566, 2.3522); + double d = sphericalDistanceKm(london, paris); // London should be approximately 343.5km // (+/- 0.1km) from Paris. expect(d, closeTo(343.5, 0.1)); }); test('San Francisco - Mountain View', () { - var sf = Point(37.783333, -122.416667); - var mtv = Point(37.389444, -122.081944); - var d = sphericalDistanceKm(sf, mtv); + Point sf = Point(37.783333, -122.416667); + Point mtv = Point(37.389444, -122.081944); + double d = sphericalDistanceKm(sf, mtv); // San Francisco should be approximately 52.8km // (+/- 0.1km) from Mountain View. expect(d, closeTo(52.8, 0.1)); @@ -52,8 +52,8 @@ void main() { // second predefined location. This test runs asynchronously. group('ISS spotter', () { test('ISS visible', () async { - var sf = Point(37.783333, -122.416667); - var mtv = Point(37.389444, -122.081944); + Point sf = Point(37.783333, -122.416667); + Point mtv = Point(37.389444, -122.081944); IssLocator locator = MockIssLocator(); // Mountain View should be visible from San Francisco. when(locator.currentPosition).thenReturn(sf); @@ -63,8 +63,8 @@ void main() { }); test('ISS not visible', () async { - var london = Point(51.5073, -0.1277); - var mtv = Point(37.389444, -122.081944); + Point london = Point(51.5073, -0.1277); + Point mtv = Point(37.389444, -122.081944); IssLocator locator = MockIssLocator(); // London should not be visible from Mountain View. when(locator.currentPosition).thenReturn(london); diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart new file mode 100644 index 000000000..bf1f5daad --- /dev/null +++ b/pkgs/mockito/lib/annotations.dart @@ -0,0 +1,19 @@ +// Copyright 2019 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +class GenerateMocks { + final List classes; + + const GenerateMocks(this.classes); +} diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart new file mode 100644 index 000000000..16bd59f6f --- /dev/null +++ b/pkgs/mockito/lib/src/builder.dart @@ -0,0 +1,228 @@ +// Copyright 2019 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:build/build.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:dart_style/dart_style.dart'; + +/// For a source Dart library, generate the mocks referenced therein. +/// +/// Given an input library, 'foo.dart', this builder will search the top-level +/// elements for an annotation, `@GenerateMocks`, from the mockito package. For +/// example: +/// +/// ```dart +/// @GenerateMocks[Foo] +/// void main() {} +/// ``` +/// +/// If this builder finds any classes to mock (for example, `Foo`, above), it +/// will produce a "'.mocks.dart' file with such mocks. In this example, +/// 'foo.mocks.dart' will be created. +class MockBuilder implements Builder { + @override + Future build(BuildStep buildStep) async { + final entryLib = await buildStep.inputLibrary; + final resolver = buildStep.resolver; + + final mockLibrary = buildStep.inputId.changeExtension('.mocks.dart'); + + final mockClasses = []; + + for (final element in entryLib.topLevelElements) { + final annotation = element.metadata.firstWhere( + (annotation) => + annotation.element is ConstructorElement && + annotation.element.enclosingElement.name == 'GenerateMocks', + orElse: () => null); + if (annotation == null) continue; + final generateMocksValue = annotation.computeConstantValue(); + // TODO(srawlins): handle `generateMocksValue == null`? + final classesToMock = generateMocksValue.getField('classes'); + if (classesToMock.isNull) + // TODO(srawlins): Log severe instead? How? How to test? + throw StateError( + 'Unresolved "classes" argument for GenerateMocks has errors: ' + '"${generateMocksValue}'); + for (final classToMock in classesToMock.toListValue()) { + final dartTypeToMock = classToMock.toTypeValue(); + // TODO(srawlins): Import the library which declares [dartTypeToMock]. + // TODO(srawlins): Import all supporting libraries, used in type + // signatures. + _buildCodeForClass(dartTypeToMock, mockClasses); + } + } + + if (mockClasses.isEmpty) { + // Nothing to mock here! + return; + } + + final emitter = DartEmitter(); + final mockLibraryContent = DartFormatter() + .format(mockClasses.map((c) => c.accept(emitter)).join('\n')); + + await buildStep.writeAsString(mockLibrary, mockLibraryContent); + } + + void _buildCodeForClass(final DartType dartType, List mockClasses) { + final elementToMock = dartType.element; + // TODO(srawlins): Log/throw here. + if (elementToMock is! ClassElement) return; + final classToMock = elementToMock as ClassElement; + final className = dartType.displayName; + + // TODO(srawlins): Add a dartdoc to the Mock class. + final mockClass = Class((cBuilder) { + cBuilder + ..name = 'Mock$className' + ..extend = refer('Mock') + ..implements.add(refer(className)) + ..docs.add('/// A class which mocks [$className].') + ..docs.add('///') + ..docs.add('/// See the documentation for Mockito\'s code generation ' + 'for more information.'); + for (final field in classToMock.fields) { + if (field.isPrivate || field.isStatic) { + continue; + } + // Handle getters when we handle non-nullable return types. + final setter = field.setter; + if (setter != null) { + cBuilder.methods.add( + Method((mBuilder) => _buildOverridingSetter(mBuilder, setter))); + } + } + for (final method in classToMock.methods) { + if (method.parameters.isEmpty || method.isPrivate || method.isStatic) { + continue; + } + cBuilder.methods.add( + Method((mBuilder) => _buildOverridingMethod(mBuilder, method))); + } + }); + + mockClasses.add(mockClass); + } + + /// Build a method which overrides [method], with all non-nullable + /// parameter types widened to be nullable. + /// + /// This new method just calls `super.noSuchMethod`, optionally passing a + /// return value for methods with a non-nullable return type. + // TODO(srawlins): This method does no widening yet. Widen parameters. Include + // tests for typedefs, old-style function parameters, and function types. + // TODO(srawlins): This method declares no specific non-null return values + // yet. + void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { + // TODO(srawlins): generator methods like async*, sync*. + // TODO(srawlins): abstract methods? + builder + ..name = method.displayName + ..returns = refer(method.returnType.displayName); + + if (method.isAsynchronous) { + builder.modifier = + method.isGenerator ? MethodModifier.asyncStar : MethodModifier.async; + } else if (method.isGenerator) { + builder.modifier = MethodModifier.syncStar; + } + + // These two variables store the arguments that will be passed to the + // [Invocation] built for `noSuchMethod`. + final invocationPositionalArgs = []; + final invocationNamedArgs = {}; + + for (final parameter in method.parameters) { + if (parameter.isRequiredPositional) { + builder.requiredParameters.add(_matchingParameter(parameter)); + invocationPositionalArgs.add(refer(parameter.displayName)); + } else if (parameter.isOptionalPositional) { + builder.optionalParameters.add(_matchingParameter(parameter)); + invocationPositionalArgs.add(refer(parameter.displayName)); + } else if (parameter.isNamed) { + builder.optionalParameters.add(_matchingParameter(parameter)); + invocationNamedArgs[refer('#${parameter.displayName}')] = + refer(parameter.displayName); + } + } + // TODO(srawlins): Optionally pass a non-null return value to `noSuchMethod` + // which `Mock.noSuchMethod` will simply return, in order to satisfy runtime + // type checks. + // TODO(srawlins): Handle getter invocations with `Invocation.getter`, + // and operators??? + // TODO(srawlins): Handle generic methods with `Invocation.genericMethod`. + final invocation = refer('Invocation').property('method').call([ + refer('#${method.displayName}'), + literalList(invocationPositionalArgs), + if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs), + ]); + final returnNoSuchMethod = + refer('super').property('noSuchMethod').call([invocation]); + + builder.body = returnNoSuchMethod.code; + } + + /// Returns a [Parameter] which matches [parameter]. + Parameter _matchingParameter(ParameterElement parameter) => + Parameter((pBuilder) { + pBuilder + ..name = parameter.displayName + ..type = refer(parameter.type.displayName); + if (parameter.isNamed) pBuilder.named = true; + if (parameter.defaultValueCode != null) { + pBuilder.defaultTo = Code(parameter.defaultValueCode); + } + }); + + /// Build a setter which overrides [setter], widening the single parameter + /// type to be nullable if it is non-nullable. + /// + /// This new setter just calls `super.noSuchMethod`. + // TODO(srawlins): This method does no widening yet. + void _buildOverridingSetter( + MethodBuilder builder, PropertyAccessorElement setter) { + builder + ..name = setter.displayName + ..type = MethodType.setter; + + final invocationPositionalArgs = []; + // There should only be one required positional parameter. Should we assert + // on that? Leave it alone? + for (final parameter in setter.parameters) { + if (parameter.isRequiredPositional) { + builder.requiredParameters.add(Parameter((pBuilder) => pBuilder + ..name = parameter.displayName + ..type = refer(parameter.type.displayName))); + invocationPositionalArgs.add(refer(parameter.displayName)); + } + } + + final invocation = refer('Invocation').property('setter').call([ + refer('#${setter.displayName}'), + literalList(invocationPositionalArgs), + ]); + final returnNoSuchMethod = + refer('super').property('noSuchMethod').call([invocation]); + + builder.body = returnNoSuchMethod.code; + } + + @override + final buildExtensions = const { + '.dart': ['.mocks.dart'] + }; +} diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index d83c0701d..320586e1a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,7 +17,6 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; -// ignore: deprecated_member_use import 'package:test_api/test_api.dart'; // TODO(srawlins): Remove this when we no longer need to check for an // incompatiblity between test_api and test. @@ -38,8 +37,7 @@ final Map _storedNamedArgs = {}; @Deprecated( 'This function is not a supported function, and may be deleted as early as ' 'Mockito 5.0.0') -void setDefaultResponse( - Mock mock, CallPair Function() defaultResponse) { +void setDefaultResponse(Mock mock, CallPair defaultResponse()) { mock._defaultResponse = defaultResponse; } @@ -138,7 +136,7 @@ class Mock { const Object().noSuchMethod(invocation); @override - int get hashCode => _givenHashCode ?? 0; + int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; @override bool operator ==(other) => (_givenHashCode != null && other is Mock) @@ -146,7 +144,7 @@ class Mock { : identical(this, other); @override - String toString() => _givenName ?? runtimeType.toString(); + String toString() => _givenName != null ? _givenName : runtimeType.toString(); String _realCallsToString() { var stringRepresentations = _realCalls.map((call) => call.toString()); @@ -240,7 +238,7 @@ class _InvocationForMatchedArguments extends Invocation { factory _InvocationForMatchedArguments(Invocation invocation) { if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) { throw StateError( - '_InvocationForMatchedArguments called when no ArgMatchers have been saved.'); + "_InvocationForMatchedArguments called when no ArgMatchers have been saved."); } // Handle named arguments first, so that we can provide useful errors for @@ -291,7 +289,7 @@ class _InvocationForMatchedArguments extends Invocation { // Iterate through the stored named args, validate them, and add them to // the return map. _storedNamedArgs.forEach((name, arg) { - var nameSymbol = Symbol(name); + Symbol nameSymbol = Symbol(name); if (!invocation.namedArguments.containsKey(nameSymbol)) { // Clear things out for the next call. _storedArgs.clear(); @@ -346,8 +344,8 @@ class _InvocationForMatchedArguments extends Invocation { 'needs to specify the name of the argument it is being used in. For ' 'example: `when(obj.fn(x: anyNamed("x")))`).'); } - var storedIndex = 0; - var positionalIndex = 0; + int storedIndex = 0; + int positionalIndex = 0; while (storedIndex < _storedArgs.length && positionalIndex < invocation.positionalArguments.length) { var arg = _storedArgs[storedIndex]; @@ -458,7 +456,7 @@ class InvocationMatcher { } void _captureArguments(Invocation invocation) { - var index = 0; + int index = 0; for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; if (roleArg is ArgMatcher && roleArg._capture) { @@ -486,7 +484,7 @@ class InvocationMatcher { roleInvocation.namedArguments.length) { return false; } - var index = 0; + int index = 0; for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; if (!isMatchingArg(roleArg, actArg)) { @@ -543,7 +541,8 @@ class RealCall { @override String toString() { var argString = ''; - var args = invocation.positionalArguments.map((v) => '$v'); + var args = invocation.positionalArguments + .map((v) => v == null ? "null" : v.toString()); if (args.any((arg) => arg.contains('\n'))) { // As one or more arg contains newlines, put each on its own line, and // indent each, for better readability. @@ -650,18 +649,18 @@ class _VerifyCall { if (!never && matchingInvocations.isEmpty) { var message; if (mock._realCalls.isEmpty) { - message = 'No matching calls (actually, no calls at all).'; + message = "No matching calls (actually, no calls at all)."; } else { var otherCalls = mock._realCallsToString(); - message = 'No matching calls. All calls: $otherCalls'; + message = "No matching calls. All calls: $otherCalls"; } - fail('$message\n' - '(If you called `verify(...).called(0);`, please instead use ' - '`verifyNever(...);`.)'); + fail("$message\n" + "(If you called `verify(...).called(0);`, please instead use " + "`verifyNever(...);`.)"); } if (never && matchingInvocations.isNotEmpty) { var calls = mock._realCallsToString(); - fail('Unexpected calls. All calls: $calls'); + fail("Unexpected calls. All calls: $calls"); } matchingInvocations.forEach((inv) { inv.verified = true; @@ -874,7 +873,7 @@ class VerificationResult { _checkTestApiMismatch(); } expect(callCount, wrapMatcher(matcher), - reason: 'Unexpected number of calls'); + reason: "Unexpected number of calls"); } } @@ -940,12 +939,12 @@ Verification _makeVerify(bool never) { return (T mock) { _verificationInProgress = false; if (_verifyCalls.length == 1) { - var verifyCall = _verifyCalls.removeLast(); + _VerifyCall verifyCall = _verifyCalls.removeLast(); var result = VerificationResult._(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; } else { - fail('Used on a non-mockito object'); + fail("Used on a non-mockito object"); } }; } @@ -971,22 +970,23 @@ _InOrderVerification get verifyInOrder { _verificationInProgress = true; return (List _) { _verificationInProgress = false; - var dt = DateTime.fromMillisecondsSinceEpoch(0); + DateTime dt = DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); _verifyCalls.clear(); - var matchedCalls = []; - for (var verifyCall in tmpVerifyCalls) { - var matched = verifyCall._findAfter(dt); + List matchedCalls = []; + for (_VerifyCall verifyCall in tmpVerifyCalls) { + RealCall matched = verifyCall._findAfter(dt); if (matched != null) { matchedCalls.add(matched); dt = matched.timeStamp; } else { - var mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); - var allInvocations = + Set mocks = + tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); + List allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); - var otherCalls = ''; + String otherCalls = ""; if (allInvocations.isNotEmpty) { otherCalls = " All calls: ${allInvocations.join(", ")}"; } @@ -1011,7 +1011,7 @@ void verifyNoMoreInteractions(var mock) { if (mock is Mock) { var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); if (unverified.isNotEmpty) { - fail('No more calls expected, but following found: ' + unverified.join()); + fail("No more calls expected, but following found: " + unverified.join()); } } else { _throwMockArgumentError('verifyNoMoreInteractions', mock); @@ -1021,7 +1021,7 @@ void verifyNoMoreInteractions(var mock) { void verifyZeroInteractions(var mock) { if (mock is Mock) { if (mock._realCalls.isNotEmpty) { - fail('No interaction expected, but following found: ' + + fail("No interaction expected, but following found: " + mock._realCalls.join()); } } else { @@ -1084,7 +1084,7 @@ InvocationLoader get untilCalled { /// Print all collected invocations of any mock methods of [mocks]. void logInvocations(List mocks) { - var allInvocations = + List allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); allInvocations.forEach((inv) { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1f438d987..521dd803f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.2-dev +version: 4.1.1 authors: - Dmitriy Fibulwinter @@ -9,10 +9,14 @@ description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.0.0 <3.0.0' + sdk: '>=2.3.0 <3.0.0' dependencies: + analyzer: ^0.36.0 + build: ^1.1.3 + code_builder: ^3.2.0 collection: ^1.1.0 + dart_style: ^1.2.5 matcher: ^0.12.3 meta: ^1.0.4 test_api: ^0.2.1 diff --git a/pkgs/mockito/test/all.dart b/pkgs/mockito/test/all.dart index ef120e6e7..0aa563518 100644 --- a/pkgs/mockito/test/all.dart +++ b/pkgs/mockito/test/all.dart @@ -15,6 +15,7 @@ // This file explicitly does _not_ end in `_test.dart`, so that it is not picked // up by `pub run test`. It is here for coveralls. +import 'builder_test.dart' as builder_test; import 'capture_test.dart' as capture_test; import 'invocation_matcher_test.dart' as invocation_matcher_test; import 'mockito_test.dart' as mockito_test; @@ -22,6 +23,7 @@ import 'until_called_test.dart' as until_called_test; import 'verify_test.dart' as verify_test; void main() { + builder_test.main(); capture_test.main(); invocation_matcher_test.main(); mockito_test.main(); diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart new file mode 100644 index 000000000..c2a422421 --- /dev/null +++ b/pkgs/mockito/test/builder_test.dart @@ -0,0 +1,266 @@ +// Copyright 2019 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:mockito/src/builder.dart'; +import 'package:test/test.dart'; + +Builder buildMocks(BuilderOptions options) => MockBuilder(); + +const annotationsAsset = { + 'mockito|lib/annotations.dart': ''' +class GenerateMocks { + final List classes; + + const GenerateMocks(this.classes); +} +''' +}; + +const simpleTestAsset = { + 'foo|test/foo_test.dart': ''' +import 'package:foo/foo.dart'; +import 'package:mockito/annotations.dart'; +@GenerateMocks([Foo]) +void main() {} +''' +}; + +void main() { + test( + 'generates mock for an imported class but does not override private ' + 'or static methods or methods w/ zero parameters', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + int a() => 7; + int _b(int x) => 8; + static int c(int y) => 9; + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends Mock implements Foo {} + '''), + }, + ); + }); + + test( + 'generates mock for an imported class but does not override private ' + 'or static fields', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + int _a; + static int b; + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends Mock implements Foo {} + '''), + }, + ); + }); + + test( + 'generates mock for an imported class but does not override any ' + 'extension methods', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + extension X on Foo { + dynamic x(int m, String n) => n + 1; + } + class Foo { + dynamic a(int m, String n) => n + 1; + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends Mock implements Foo { + dynamic a(int m, String n) => + super.noSuchMethod(Invocation.method(#a, [m, n])); + } + '''), + }, + ); + }); + + test('generates a mock class and overrides methods parameters', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + dynamic a(int m, String n) => n + 1; + dynamic b(List list) => list.length; + void c(String one, [String two, String three = ""]) => print('$one$two$three'); + void d(String one, {String two, String three = ""}) => print('$one$two$three'); + Future e(String s) async => print(s); + // TODO(srawlins): Figure out async*; doesn't work yet. `isGenerator` + // does not appear to be working. + // Stream f(String s) async* => print(s); + // Iterable g(String s) sync* => print(s); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends Mock implements Foo { + dynamic a(int m, String n) => + super.noSuchMethod(Invocation.method(#a, [m, n])); + dynamic b(List list) => + super.noSuchMethod(Invocation.method(#b, [list])); + void c(String one, [String two, String three = ""]) => + super.noSuchMethod(Invocation.method(#c, [one, two, three])); + void d(String one, {String two, String three = ""}) => super + .noSuchMethod(Invocation.method(#d, [one], {#two: two, #three: three})); + Future e(String s) async => + super.noSuchMethod(Invocation.method(#e, [s])); + } + '''), + }, + ); + }); + + test('generates multiple mock classes', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + dynamic a(int m, String n) => n + 1; + } + class Bar { + dynamic b(List list) => list.length; + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo, Bar]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends Mock implements Foo { + dynamic a(int m, String n) => + super.noSuchMethod(Invocation.method(#a, [m, n])); + } + + /// A class which mocks [Bar]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockBar extends Mock implements Bar { + dynamic b(List list) => + super.noSuchMethod(Invocation.method(#b, [list])); + } + '''), + }, + ); + }); + + test('generates a mock class and overrides getters and setters', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + int get a => n + 1; + int _b; + set b(int value) => _b = value; + } + '''), + }, + outputs: { + // TODO(srawlins): The getter will appear when it has a non-nullable + // return type. + 'foo|test/foo_test.mocks.dart': dedent(r''' + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends Mock implements Foo { + set b(int value) => super.noSuchMethod(Invocation.setter(#b, [value])); + } + '''), + }, + ); + }); + test('throws given bad input', () async { + expect( + () async => await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + // missing foo.dart import. + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void main() {} + '''), + }, + ), + throwsStateError); + }); +} + +/// Dedent [input], so that each line is shifted to the left, so that the first +/// line is at the 0 column. +String dedent(String input) { + final indentMatch = RegExp(r'^(\s*)').firstMatch(input); + final indent = ''.padRight(indentMatch.group(1).length); + return input.splitMapJoin('\n', + onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), '')); +} diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 7c597ba38..09da04522 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -22,7 +22,7 @@ class _RealClass { String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; set setter(String arg) { throw StateError('I must be mocked'); } diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index ab9a4e33e..95dbcb492 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -24,18 +24,18 @@ import 'package:test/test.dart'; class _RealClass { _RealClass innerObj; - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - Future methodReturningFuture() => Future.value('Real'); - Stream methodReturningStream() => Stream.fromIterable(['Real']); - String get getter => 'Real'; + String methodWithoutArgs() => "Real"; + String methodWithNormalArgs(int x) => "Real"; + String methodWithListArgs(List x) => "Real"; + String methodWithPositionalArgs(int x, [int y]) => "Real"; + String methodWithNamedArgs(int x, {int y}) => "Real"; + String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; + String methodWithObjArgs(_RealClass x) => "Real"; + Future methodReturningFuture() => Future.value("Real"); + Stream methodReturningStream() => Stream.fromIterable(["Real"]); + String get getter => "Real"; set setter(String arg) { - throw StateError('I must be mocked'); + throw StateError("I must be mocked"); } } @@ -54,23 +54,23 @@ class MockFoo extends AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, void Function() expectedToFail) { +void expectFail(String expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); - fail('It was expected to fail!'); + fail("It was expected to fail!"); } catch (e) { if (!(e is TestFailure)) { rethrow; } else { if (expectedMessage != e.message) { - throw TestFailure('Failed, but with wrong message: ${e.message}'); + throw TestFailure("Failed, but with wrong message: ${e.message}"); } } } } -String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' - 'please instead use `verifyNever(...);`.)'; +String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " + "please instead use `verifyNever(...);`.)"; void main() { _MockedClass mock; @@ -85,67 +85,67 @@ void main() { resetMockitoState(); }); - group('when()', () { - test('should mock method with argument matcher', () { + group("when()", () { + test("should mock method with argument matcher", () { when(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))) - .thenReturn('A lot!'); + .thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals('A lot!')); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); - test('should mock method with any argument matcher', () { - when(mock.methodWithNormalArgs(typed(any))).thenReturn('A lot!'); - expect(mock.methodWithNormalArgs(100), equals('A lot!')); - expect(mock.methodWithNormalArgs(101), equals('A lot!')); + test("should mock method with any argument matcher", () { + when(mock.methodWithNormalArgs(typed(any))).thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); - test('should mock method with any list argument matcher', () { - when(mock.methodWithListArgs(typed(any))).thenReturn('A lot!'); - expect(mock.methodWithListArgs([42]), equals('A lot!')); - expect(mock.methodWithListArgs([43]), equals('A lot!')); + test("should mock method with any list argument matcher", () { + when(mock.methodWithListArgs(typed(any))).thenReturn("A lot!"); + expect(mock.methodWithListArgs([42]), equals("A lot!")); + expect(mock.methodWithListArgs([43]), equals("A lot!")); }); - test('should mock method with mix of argument matchers and real things', + test("should mock method with mix of argument matchers and real things", () { when(mock.methodWithPositionalArgs(typed(argThat(greaterThan(100))), 17)) - .thenReturn('A lot with 17'); + .thenReturn("A lot with 17"); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals('A lot with 17')); + expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); }); //no need to mock setter, except if we will have spies later... - test('should mock method with thrown result', () { + test("should mock method with thrown result", () { when(mock.methodWithNormalArgs(typed(any))).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); - test('should mock method with calculated result', () { + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(typed(any))).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals('43')); - expect(mock.methodWithNormalArgs(42), equals('42')); + expect(mock.methodWithNormalArgs(43), equals("43")); + expect(mock.methodWithNormalArgs(42), equals("42")); }); - test('should mock method with calculated result', () { + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(typed(argThat(equals(43))))) - .thenReturn('43'); + .thenReturn("43"); when(mock.methodWithNormalArgs(typed(argThat(equals(42))))) - .thenReturn('42'); - expect(mock.methodWithNormalArgs(43), equals('43')); + .thenReturn("42"); + expect(mock.methodWithNormalArgs(43), equals("43")); }); - test('should mock hashCode', () { + test("should mock hashCode", () { named(mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); - test('should have toString as name when it is not mocked', () { - named(mock, name: 'Cat'); - expect(mock.toString(), equals('Cat')); + test("should have toString as name when it is not mocked", () { + named(mock, name: "Cat"); + expect(mock.toString(), equals("Cat")); }); - test('should mock equals between mocks when givenHashCode is equals', () { + test("should mock equals between mocks when givenHashCode is equals", () { var anotherMock = named(_MockedClass(), hashCode: 42); named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart index f4e6801ed..92ed48759 100644 --- a/pkgs/mockito/test/deprecated_apis/until_called_test.dart +++ b/pkgs/mockito/test/deprecated_apis/until_called_test.dart @@ -86,7 +86,8 @@ void main() { }); group('untilCalled', () { - var streamController = StreamController.broadcast(); + StreamController streamController = + StreamController.broadcast(); group('on methods already called', () { test('waits for method with normal args', () async { diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index 74cfdcba8..a8c870c17 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -57,7 +57,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, void Function() expectedToFail) { +void expectFail(String expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); fail('It was expected to fail!'); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index dc5bb5252..12eae076b 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -66,9 +66,9 @@ void main() { shouldFail( call1, isInvocation(call3), - 'Expected: lie() ' + "Expected: lie() " "Actual: " - 'Which: Does not match lie()', + "Which: Does not match lie()", ); }); @@ -102,9 +102,9 @@ void main() { call1, isInvocation(call3), // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - RegExp('Expected: set value=? ' + RegExp("Expected: set value=? " "Actual: " - 'Which: Does not match set value=? '), + "Which: Does not match set value=? "), ); }); }); @@ -118,7 +118,7 @@ void main() { shouldFail( call, invokes(#say, positionalArguments: [isNull]), - 'Expected: say(null) ' + "Expected: say(null) " "Actual: " "Which: Does not match say('Hello')", ); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index e31cde2b8..1b801509c 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -21,16 +21,16 @@ import 'utils.dart'; class _RealClass { _RealClass innerObj; - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - Future methodReturningFuture() => Future.value('Real'); - Stream methodReturningStream() => Stream.fromIterable(['Real']); - String get getter => 'Real'; + String methodWithoutArgs() => "Real"; + String methodWithNormalArgs(int x) => "Real"; + String methodWithListArgs(List x) => "Real"; + String methodWithPositionalArgs(int x, [int y]) => "Real"; + String methodWithNamedArgs(int x, {int y}) => "Real"; + String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; + String methodWithObjArgs(_RealClass x) => "Real"; + Future methodReturningFuture() => Future.value("Real"); + Stream methodReturningStream() => Stream.fromIterable(["Real"]); + String get getter => "Real"; } abstract class _Foo { @@ -43,30 +43,30 @@ abstract class _AbstractFoo implements _Foo { String baz(); - String quux() => 'Real'; + String quux() => "Real"; } class _MockFoo extends _AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, void Function() expectedToFail) { +void expectFail(String expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); - fail('It was expected to fail!'); + fail("It was expected to fail!"); } catch (e) { if (!(e is TestFailure)) { rethrow; } else { if (expectedMessage != e.message) { - throw TestFailure('Failed, but with wrong message: ${e.message}'); + throw TestFailure("Failed, but with wrong message: ${e.message}"); } } } } -String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' - 'please instead use `verifyNever(...);`.)'; +String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " + "please instead use `verifyNever(...);`.)"; void main() { _MockedClass mock; @@ -83,229 +83,229 @@ void main() { resetMockitoState(); }); - group('mixin support', () { - test('should work', () { + group("mixin support", () { + test("should work", () { var foo = _MockFoo(); when(foo.baz()).thenReturn('baz'); expect(foo.bar(), 'baz'); }); }); - group('when()', () { - test('should mock method without args', () { - when(mock.methodWithoutArgs()).thenReturn('A'); - expect(mock.methodWithoutArgs(), equals('A')); + group("when()", () { + test("should mock method without args", () { + when(mock.methodWithoutArgs()).thenReturn("A"); + expect(mock.methodWithoutArgs(), equals("A")); }); - test('should mock method with normal args', () { - when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); + test("should mock method with normal args", () { + when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); expect(mock.methodWithNormalArgs(43), isNull); - expect(mock.methodWithNormalArgs(42), equals('Ultimate Answer')); + expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); }); - test('should mock method with mock args', () { + test("should mock method with mock args", () { var m1 = _MockedClass(); - when(mock.methodWithObjArgs(m1)).thenReturn('Ultimate Answer'); + when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); expect(mock.methodWithObjArgs(_MockedClass()), isNull); - expect(mock.methodWithObjArgs(m1), equals('Ultimate Answer')); + expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); }); - test('should mock method with positional args', () { - when(mock.methodWithPositionalArgs(42, 17)).thenReturn('Answer and...'); + test("should mock method with positional args", () { + when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); expect(mock.methodWithPositionalArgs(42), isNull); expect(mock.methodWithPositionalArgs(42, 18), isNull); - expect(mock.methodWithPositionalArgs(42, 17), equals('Answer and...')); + expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); }); - test('should mock method with named args', () { - when(mock.methodWithNamedArgs(42, y: 17)).thenReturn('Why answer?'); + test("should mock method with named args", () { + when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); expect(mock.methodWithNamedArgs(42), isNull); expect(mock.methodWithNamedArgs(42, y: 18), isNull); - expect(mock.methodWithNamedArgs(42, y: 17), equals('Why answer?')); + expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); }); - test('should mock method with List args', () { - when(mock.methodWithListArgs([42])).thenReturn('Ultimate answer'); + test("should mock method with List args", () { + when(mock.methodWithListArgs([42])).thenReturn("Ultimate answer"); expect(mock.methodWithListArgs([43]), isNull); - expect(mock.methodWithListArgs([42]), equals('Ultimate answer')); + expect(mock.methodWithListArgs([42]), equals("Ultimate answer")); }); - test('should mock method with argument matcher', () { + test("should mock method with argument matcher", () { when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) - .thenReturn('A lot!'); + .thenReturn("A lot!"); expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals('A lot!')); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); - test('should mock method with any argument matcher', () { - when(mock.methodWithNormalArgs(any)).thenReturn('A lot!'); - expect(mock.methodWithNormalArgs(100), equals('A lot!')); - expect(mock.methodWithNormalArgs(101), equals('A lot!')); + test("should mock method with any argument matcher", () { + when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); + expect(mock.methodWithNormalArgs(100), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals("A lot!")); }); - test('should mock method with any list argument matcher', () { - when(mock.methodWithListArgs(any)).thenReturn('A lot!'); - expect(mock.methodWithListArgs([42]), equals('A lot!')); - expect(mock.methodWithListArgs([43]), equals('A lot!')); + test("should mock method with any list argument matcher", () { + when(mock.methodWithListArgs(any)).thenReturn("A lot!"); + expect(mock.methodWithListArgs([42]), equals("A lot!")); + expect(mock.methodWithListArgs([43]), equals("A lot!")); }); - test('should mock method with multiple named args and matchers', () { + test("should mock method with multiple named args and matchers", () { when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'))) - .thenReturn('x y'); + .thenReturn("x y"); when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) - .thenReturn('x z'); + .thenReturn("x z"); if (isNsmForwarding) { - expect(mock.methodWithTwoNamedArgs(42), 'x z'); + expect(mock.methodWithTwoNamedArgs(42), "x z"); } else { expect(mock.methodWithTwoNamedArgs(42), isNull); } - expect(mock.methodWithTwoNamedArgs(42, y: 18), equals('x y')); - expect(mock.methodWithTwoNamedArgs(42, z: 17), equals('x z')); + expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); + expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'), z: anyNamed('z'))) - .thenReturn('x y z'); - expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals('x y z')); + .thenReturn("x y z"); + expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); }); - test('should mock method with mix of argument matchers and real things', + test("should mock method with mix of argument matchers and real things", () { when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) - .thenReturn('A lot with 17'); + .thenReturn("A lot with 17"); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals('A lot with 17')); + expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); }); - test('should mock getter', () { - when(mock.getter).thenReturn('A'); - expect(mock.getter, equals('A')); + test("should mock getter", () { + when(mock.getter).thenReturn("A"); + expect(mock.getter, equals("A")); }); - test('should have hashCode when it is not mocked', () { + test("should have hashCode when it is not mocked", () { expect(mock.hashCode, isNotNull); }); - test('should have default toString when it is not mocked', () { + test("should have default toString when it is not mocked", () { expect(mock.toString(), equals('_MockedClass')); }); - test('should use identical equality between it is not mocked', () { + test("should use identical equality between it is not mocked", () { var anotherMock = _MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); - test('should mock method with thrown result', () { + test("should mock method with thrown result", () { when(mock.methodWithNormalArgs(any)).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); - test('should mock method with calculated result', () { + test("should mock method with calculated result", () { when(mock.methodWithNormalArgs(any)).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals('43')); - expect(mock.methodWithNormalArgs(42), equals('42')); + expect(mock.methodWithNormalArgs(43), equals("43")); + expect(mock.methodWithNormalArgs(42), equals("42")); }); - test('should return mock to make simple oneline mocks', () { + test("should return mock to make simple oneline mocks", () { _RealClass mockWithSetup = _MockedClass(); - when(mockWithSetup.methodWithoutArgs()).thenReturn('oneline'); - expect(mockWithSetup.methodWithoutArgs(), equals('oneline')); + when(mockWithSetup.methodWithoutArgs()).thenReturn("oneline"); + expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); }); - test('should use latest matching when definition', () { - when(mock.methodWithoutArgs()).thenReturn('A'); - when(mock.methodWithoutArgs()).thenReturn('B'); - expect(mock.methodWithoutArgs(), equals('B')); + test("should use latest matching when definition", () { + when(mock.methodWithoutArgs()).thenReturn("A"); + when(mock.methodWithoutArgs()).thenReturn("B"); + expect(mock.methodWithoutArgs(), equals("B")); }); - test('should mock method with calculated result', () { - when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn('43'); - when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn('42'); - expect(mock.methodWithNormalArgs(43), equals('43')); + test("should mock method with calculated result", () { + when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); + when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); + expect(mock.methodWithNormalArgs(43), equals("43")); }); // Error path tests. - test('should throw if `when` is called while stubbing', () { + test("should throw if `when` is called while stubbing", () { expect(() { var responseHelper = () { var mock2 = _MockedClass(); - when(mock2.getter).thenReturn('A'); + when(mock2.getter).thenReturn("A"); return mock2; }; when(mock.innerObj).thenReturn(responseHelper()); }, throwsStateError); }); - test('thenReturn throws if provided Future', () { + test("thenReturn throws if provided Future", () { expect( () => when(mock.methodReturningFuture()) - .thenReturn(Future.value('stub')), + .thenReturn(Future.value("stub")), throwsArgumentError); }); - test('thenReturn throws if provided Stream', () { + test("thenReturn throws if provided Stream", () { expect( () => when(mock.methodReturningStream()) - .thenReturn(Stream.fromIterable(['stub'])), + .thenReturn(Stream.fromIterable(["stub"])), throwsArgumentError); }); - test('thenAnswer supports stubbing method returning a Future', () async { + test("thenAnswer supports stubbing method returning a Future", () async { when(mock.methodReturningFuture()) - .thenAnswer((_) => Future.value('stub')); + .thenAnswer((_) => Future.value("stub")); - expect(await mock.methodReturningFuture(), 'stub'); + expect(await mock.methodReturningFuture(), "stub"); }); - test('thenAnswer supports stubbing method returning a Stream', () async { + test("thenAnswer supports stubbing method returning a Stream", () async { when(mock.methodReturningStream()) - .thenAnswer((_) => Stream.fromIterable(['stub'])); + .thenAnswer((_) => Stream.fromIterable(["stub"])); - expect(await mock.methodReturningStream().toList(), ['stub']); + expect(await mock.methodReturningStream().toList(), ["stub"]); }); - test('should throw if named matcher is passed as the wrong name', () { + test("should throw if named matcher is passed as the wrong name", () { expect(() { - when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed('z'))) - .thenReturn('99'); + when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed("z"))) + .thenReturn("99"); }, throwsArgumentError); }); - test('should throw if attempting to stub a real method', () { + test("should throw if attempting to stub a real method", () { var foo = _MockFoo(); expect(() { - when(foo.quux()).thenReturn('Stub'); + when(foo.quux()).thenReturn("Stub"); }, throwsStateError); }); }); - group('throwOnMissingStub', () { - test('should throw when a mock was called without a matching stub', () { + group("throwOnMissingStub", () { + test("should throw when a mock was called without a matching stub", () { throwOnMissingStub(mock); - when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); + when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); expect( () => (mock).methodWithoutArgs(), throwsNoSuchMethodError, ); }); - test('should not throw when a mock was called with a matching stub', () { + test("should not throw when a mock was called with a matching stub", () { throwOnMissingStub(mock); - when(mock.methodWithoutArgs()).thenReturn('A'); + when(mock.methodWithoutArgs()).thenReturn("A"); expect(() => mock.methodWithoutArgs(), returnsNormally); }); }); test( - 'reports an error when using an argument matcher outside of stubbing or ' - 'verification', () { + "reports an error when using an argument matcher outside of stubbing or " + "verification", () { expect(() => mock.methodWithNormalArgs(any), throwsArgumentError); }); test( - 'reports an error when using an argument matcher in a position other ' - 'than an argument for the stubbed method', () { + "reports an error when using an argument matcher in a position other " + "than an argument for the stubbed method", () { expect(() => when(mock.methodWithListArgs(List.filled(7, any))), throwsArgumentError); }); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index 4a0b03fd9..634c59c0f 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -81,7 +81,8 @@ void main() { }); group('untilCalled', () { - var streamController = StreamController.broadcast(); + StreamController streamController = + StreamController.broadcast(); group('on methods already called', () { test('waits for method without args', () async { diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 7622a2b0a..0c41c2941 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -54,7 +54,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -void expectFail(Pattern expectedMessage, void Function() expectedToFail) { +void expectFail(Pattern expectedMessage, dynamic expectedToFail()) { try { expectedToFail(); fail('It was expected to fail!'); From 7a29854d3aaa4f6396e7d7e5c422ebc52548ceea Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 12 Nov 2019 14:33:50 -0800 Subject: [PATCH 158/607] Address nbosch@'s comments from cl/279997785 PiperOrigin-RevId: 280056796 --- pkgs/mockito/lib/src/builder.dart | 58 ++++++++++++++----- pkgs/mockito/test/builder_test.dart | 90 +++++++++++++++++++++++------ 2 files changed, 114 insertions(+), 34 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 16bd59f6f..55d7972ee 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -25,7 +25,7 @@ import 'package:dart_style/dart_style.dart'; /// example: /// /// ```dart -/// @GenerateMocks[Foo] +/// @GenerateMocks([Foo]) /// void main() {} /// ``` /// @@ -52,17 +52,40 @@ class MockBuilder implements Builder { final generateMocksValue = annotation.computeConstantValue(); // TODO(srawlins): handle `generateMocksValue == null`? final classesToMock = generateMocksValue.getField('classes'); - if (classesToMock.isNull) - // TODO(srawlins): Log severe instead? How? How to test? - throw StateError( - 'Unresolved "classes" argument for GenerateMocks has errors: ' - '"${generateMocksValue}'); + if (classesToMock.isNull) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument has unknown types'); + } for (final classToMock in classesToMock.toListValue()) { final dartTypeToMock = classToMock.toTypeValue(); // TODO(srawlins): Import the library which declares [dartTypeToMock]. // TODO(srawlins): Import all supporting libraries, used in type // signatures. - _buildCodeForClass(dartTypeToMock, mockClasses); + if (dartTypeToMock == null) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-type: $classToMock'); + } + + final elementToMock = dartTypeToMock.element; + if (elementToMock is ClassElement) { + if (elementToMock.isEnum) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes an enum: ' + '${elementToMock.displayName}'); + } + // TODO(srawlins): Catch when someone tries to generate mocks for an + // un-subtypable class, like bool, String, FutureOr, etc. + mockClasses.add(_buildCodeForClass(dartTypeToMock, elementToMock)); + } else if (elementToMock is GenericFunctionTypeElement && + elementToMock.enclosingElement is FunctionTypeAliasElement) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a typedef: ' + '${elementToMock.enclosingElement.displayName}'); + } else { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-class: ' + '${elementToMock.displayName}'); + } } } @@ -78,15 +101,10 @@ class MockBuilder implements Builder { await buildStep.writeAsString(mockLibrary, mockLibraryContent); } - void _buildCodeForClass(final DartType dartType, List mockClasses) { - final elementToMock = dartType.element; - // TODO(srawlins): Log/throw here. - if (elementToMock is! ClassElement) return; - final classToMock = elementToMock as ClassElement; + Class _buildCodeForClass(DartType dartType, ClassElement classToMock) { final className = dartType.displayName; - // TODO(srawlins): Add a dartdoc to the Mock class. - final mockClass = Class((cBuilder) { + return Class((cBuilder) { cBuilder ..name = 'Mock$className' ..extend = refer('Mock') @@ -114,8 +132,6 @@ class MockBuilder implements Builder { Method((mBuilder) => _buildOverridingMethod(mBuilder, method))); } }); - - mockClasses.add(mockClass); } /// Build a method which overrides [method], with all non-nullable @@ -226,3 +242,13 @@ class MockBuilder implements Builder { '.dart': ['.mocks.dart'] }; } + +/// An exception which is thrown when Mockito encounters an invalid annotation. +class InvalidMockitoAnnotationException implements Exception { + final String message; + + InvalidMockitoAnnotationException(this.message); + + @override + String toString() => 'Invalid @GenerateMocks annotation: $message'; +} diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index c2a422421..5c2489881 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -14,6 +14,7 @@ import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; +import 'package:meta/meta.dart'; import 'package:mockito/src/builder.dart'; import 'package:test/test.dart'; @@ -235,25 +236,78 @@ void main() { }, ); }); - test('throws given bad input', () async { - expect( - () async => await testBuilder( - buildMocks(BuilderOptions({})), - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo {} - '''), - 'foo|test/foo_test.dart': dedent(''' - // missing foo.dart import. - import 'package:mockito/annotations.dart'; - @GenerateMocks([Foo]) - void main() {} - '''), - }, - ), - throwsStateError); + + test('throws when GenerateMocks references an unresolved type', () async { + expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + // missing foo.dart import. + import 'package:mockito/annotations.dart'; + @GenerateMocks([List, Foo]) + void main() {} + '''), + }, + message: 'The "classes" argument has unknown types', + ); + }); + + test('throws when GenerateMocks references a non-type', () async { + expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + // missing foo.dart import. + import 'package:mockito/annotations.dart'; + @GenerateMocks([7]) + void main() {} + '''), + }, + message: 'The "classes" argument includes a non-type: int (7)', + ); }); + + test('throws when GenerateMocks references a typedef', () async { + expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + typedef Foo = void Function(); + '''), + }, + message: 'The "classes" argument includes a typedef: Foo', + ); + }); + + test('throws when GenerateMocks references an enum', () async { + expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + enum Foo {} + '''), + }, + message: 'The "classes" argument includes an enum: Foo', + ); + }); +} + +/// Expect that [testBuilder], given [assets], throws an +/// [InvalidMockitoAnnotationException] with a message containing [message]. +void expectBuilderThrows( + {@required Map assets, @required String message}) { + expect( + () async => await testBuilder(buildMocks(BuilderOptions({})), assets), + throwsA(TypeMatcher() + .having((e) => e.message, 'message', contains(message)))); } /// Dedent [input], so that each line is shifted to the left, so that the first From ca8e76caefc884541fede045d1dfe4227f72ed71 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 18 Nov 2019 06:37:13 -0800 Subject: [PATCH 159/607] Refactor builder and add dummy return value feature: * Instead of concatenating a pile of classes, use a Library; this lets us start to import things properly. * Refactor some code out of loops so it's more readable. * Depending on return type of original methods, can generate dummy return values. * Test manually-written `super.noSuchMethod` calls which pass a dummy return value. I don't know of any other code which passes two arguments to noSuchMethod, so I want this tested in all Dart environments. PiperOrigin-RevId: 281056988 --- pkgs/mockito/CHANGELOG.md | 9 ++ pkgs/mockito/lib/src/builder.dart | 190 ++++++++++++++++------- pkgs/mockito/lib/src/mock.dart | 15 +- pkgs/mockito/test/builder_test.dart | 130 ++++++++++++++-- pkgs/mockito/test/mockito_test.dart | 3 - pkgs/mockito/test/nnbd_support_test.dart | 125 +++++++++++++++ 6 files changed, 399 insertions(+), 73 deletions(-) create mode 100644 pkgs/mockito/test/nnbd_support_test.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a7b21e70b..930be48b6 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,12 @@ +## 4.1.2 + +* Introduce experimental code-generated mocks. This is primarily to support + the new "Non-nullable by default" (NNBD) type system coming soon to Dart. +* Add an optional second parameter to `Mock.noSuchMethod`. This may break + clients who use the Mock class in unconventional ways, such as overriding + `noSuchMethod` on a class which extends Mock. To fix, or prepare such code, + add a second parameter to such overriding `noSuchMethod` declaration. + ## 4.1.1 * Mark the unexported and accidentally public `setDefaultResponse` as diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 55d7972ee..40652bc7f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; @@ -38,67 +39,75 @@ class MockBuilder implements Builder { final entryLib = await buildStep.inputLibrary; final resolver = buildStep.resolver; - final mockLibrary = buildStep.inputId.changeExtension('.mocks.dart'); - - final mockClasses = []; - - for (final element in entryLib.topLevelElements) { - final annotation = element.metadata.firstWhere( - (annotation) => - annotation.element is ConstructorElement && - annotation.element.enclosingElement.name == 'GenerateMocks', - orElse: () => null); - if (annotation == null) continue; - final generateMocksValue = annotation.computeConstantValue(); - // TODO(srawlins): handle `generateMocksValue == null`? - final classesToMock = generateMocksValue.getField('classes'); - if (classesToMock.isNull) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument has unknown types'); - } - for (final classToMock in classesToMock.toListValue()) { - final dartTypeToMock = classToMock.toTypeValue(); - // TODO(srawlins): Import the library which declares [dartTypeToMock]. - // TODO(srawlins): Import all supporting libraries, used in type - // signatures. - if (dartTypeToMock == null) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-type: $classToMock'); - } + final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); - final elementToMock = dartTypeToMock.element; - if (elementToMock is ClassElement) { - if (elementToMock.isEnum) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes an enum: ' - '${elementToMock.displayName}'); - } - // TODO(srawlins): Catch when someone tries to generate mocks for an - // un-subtypable class, like bool, String, FutureOr, etc. - mockClasses.add(_buildCodeForClass(dartTypeToMock, elementToMock)); - } else if (elementToMock is GenericFunctionTypeElement && - elementToMock.enclosingElement is FunctionTypeAliasElement) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a typedef: ' - '${elementToMock.enclosingElement.displayName}'); - } else { + final mockLibrary = Library((lBuilder) { + for (final element in entryLib.topLevelElements) { + final annotation = element.metadata.firstWhere( + (annotation) => + annotation.element is ConstructorElement && + annotation.element.enclosingElement.name == 'GenerateMocks', + orElse: () => null); + if (annotation == null) continue; + final generateMocksValue = annotation.computeConstantValue(); + // TODO(srawlins): handle `generateMocksValue == null`? + final classesToMock = generateMocksValue.getField('classes'); + if (classesToMock.isNull) { throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-class: ' - '${elementToMock.displayName}'); + 'The "classes" argument has unknown types'); } + + _buildMockClasses(classesToMock.toListValue(), lBuilder); } - } + }); - if (mockClasses.isEmpty) { + if (mockLibrary.body.isEmpty) { // Nothing to mock here! return; } - final emitter = DartEmitter(); - final mockLibraryContent = DartFormatter() - .format(mockClasses.map((c) => c.accept(emitter)).join('\n')); + final emitter = DartEmitter.scoped(); + final mockLibraryContent = + DartFormatter().format(mockLibrary.accept(emitter).toString()); - await buildStep.writeAsString(mockLibrary, mockLibraryContent); + await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); + } + + /// Build mock classes for [classesToMock], a list of classes obtained from a + /// `@GenerateMocks` annotation. + void _buildMockClasses( + List classesToMock, LibraryBuilder lBuilder) { + for (final classToMock in classesToMock) { + final dartTypeToMock = classToMock.toTypeValue(); + // TODO(srawlins): Import the library which declares [dartTypeToMock]. + // TODO(srawlins): Import all supporting libraries, used in type + // signatures. + if (dartTypeToMock == null) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-type: $classToMock'); + } + + final elementToMock = dartTypeToMock.element; + if (elementToMock is ClassElement) { + if (elementToMock.isEnum) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes an enum: ' + '${elementToMock.displayName}'); + } + // TODO(srawlins): Catch when someone tries to generate mocks for an + // un-subtypable class, like bool, String, FutureOr, etc. + lBuilder.body.add(_buildCodeForClass(dartTypeToMock, elementToMock)); + } else if (elementToMock is GenericFunctionTypeElement && + elementToMock.enclosingElement is FunctionTypeAliasElement) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a typedef: ' + '${elementToMock.enclosingElement.displayName}'); + } else { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-class: ' + '${elementToMock.displayName}'); + } + } } Class _buildCodeForClass(DartType dartType, ClassElement classToMock) { @@ -107,7 +116,8 @@ class MockBuilder implements Builder { return Class((cBuilder) { cBuilder ..name = 'Mock$className' - ..extend = refer('Mock') + ..extend = refer('Mock', 'package:mockito/mockito.dart') + // TODO(srawlins): Add URI of dartType. ..implements.add(refer(className)) ..docs.add('/// A class which mocks [$className].') ..docs.add('///') @@ -125,15 +135,40 @@ class MockBuilder implements Builder { } } for (final method in classToMock.methods) { - if (method.parameters.isEmpty || method.isPrivate || method.isStatic) { + if (method.isPrivate || method.isStatic) { continue; } - cBuilder.methods.add( - Method((mBuilder) => _buildOverridingMethod(mBuilder, method))); + if (_returnTypeIsNonNullable(method) || + _hasNonNullableParameter(method)) { + cBuilder.methods.add( + Method((mBuilder) => _buildOverridingMethod(mBuilder, method))); + } } }); } + // TODO(srawlins): Update this logic to correctly handle non-nullable return + // types. Right now this logic does not seem to be available on DartType. + bool _returnTypeIsNonNullable(MethodElement method) { + var type = method.returnType; + if (type.isDynamic || type.isVoid) return false; + if (method.isAsynchronous && type.isDartAsyncFuture || + type.isDartAsyncFutureOr) { + var typeArgument = (type as InterfaceType).typeArguments.first; + if (typeArgument.isDynamic || typeArgument.isVoid) { + // An asynchronous method which returns `Future`, for example, + // does not need a dummy return value. + return false; + } + } + return true; + } + + // TODO(srawlins): Update this logic to correctly handle non-nullable return + // types. Right now this logic does not seem to be available on DartType. + bool _hasNonNullableParameter(MethodElement method) => + method.parameters.isNotEmpty; + /// Build a method which overrides [method], with all non-nullable /// parameter types widened to be nullable. /// @@ -150,6 +185,10 @@ class MockBuilder implements Builder { ..name = method.displayName ..returns = refer(method.returnType.displayName); + if (method.typeParameters != null && method.typeParameters.isNotEmpty) { + builder.types.addAll(method.typeParameters.map((p) => refer(p.name))); + } + if (method.isAsynchronous) { builder.modifier = method.isGenerator ? MethodModifier.asyncStar : MethodModifier.async; @@ -180,23 +219,60 @@ class MockBuilder implements Builder { // type checks. // TODO(srawlins): Handle getter invocations with `Invocation.getter`, // and operators??? - // TODO(srawlins): Handle generic methods with `Invocation.genericMethod`. final invocation = refer('Invocation').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs), ]); + final noSuchMethodArgs = [invocation]; + if (_returnTypeIsNonNullable(method)) { + final dummyReturnValue = _dummyValue(method.returnType); + noSuchMethodArgs.add(dummyReturnValue); + } final returnNoSuchMethod = - refer('super').property('noSuchMethod').call([invocation]); + refer('super').property('noSuchMethod').call(noSuchMethodArgs); builder.body = returnNoSuchMethod.code; } + Expression _dummyValue(DartType type) { + if (type.isDartCoreBool) { + return literalFalse; + } else if (type.isDartCoreDouble) { + return literalNum(0.0); + } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { + var typeArgument = (type as InterfaceType).typeArguments.first; + return refer('Future') + .property('value') + .call([_dummyValue(typeArgument)]); + } else if (type.isDartCoreInt) { + return literalNum(0); + } else if (type.isDartCoreList) { + return literalList([]); + } else if (type.isDartCoreMap) { + return literalMap({}); + } else if (type.isDartCoreNum) { + return literalNum(0); + } else if (type.isDartCoreSet) { + // This is perhaps a dangerous hack. The code, `{}`, is parsed as a Set + // literal if it is used in a context which explicitly expects a Set. + return literalMap({}); + } else if (type.isDartCoreString) { + return literalString(''); + } else { + // TODO(srawlins): Returning null for now, but really this should only + // ever get to a state where we have to make a Fake class which implements + // the type, and return a no-op constructor call to that Fake class here. + return literalNull; + } + } + /// Returns a [Parameter] which matches [parameter]. Parameter _matchingParameter(ParameterElement parameter) => Parameter((pBuilder) { pBuilder ..name = parameter.displayName + // TODO(srawlins): Add URI of `parameter.type`. ..type = refer(parameter.type.displayName); if (parameter.isNamed) pBuilder.named = true; if (parameter.defaultValueCode != null) { diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 320586e1a..4ec119c52 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -107,20 +107,27 @@ class Mock { @override @visibleForTesting - dynamic noSuchMethod(Invocation invocation) { + + /// Handles method stubbing, method call verification, and real method calls. + /// + /// If passed, [returnValue] will be returned during method stubbing and + /// method call verification. This is useful in cases where the method + /// invocation which led to `noSuchMethod` being called has a non-nullable + /// return type. + dynamic noSuchMethod(Invocation invocation, [Object /*?*/ returnValue]) { // noSuchMethod is that 'magic' that allows us to ignore implementing fields // and methods and instead define them later at compile-time per instance. // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH invocation = _useMatchedInvocationIfSet(invocation); if (_whenInProgress) { _whenCall = _WhenCall(this, invocation); - return null; + return returnValue; } else if (_verificationInProgress) { _verifyCalls.add(_VerifyCall(this, invocation)); - return null; + return returnValue; } else if (_untilCalledInProgress) { _untilCall = _UntilCall(this, invocation); - return null; + return returnValue; } else { _realCalls.add(RealCall(this, invocation)); _invocationStreamController.add(invocation); diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 5c2489881..cc7db0428 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -50,7 +50,7 @@ void main() { ...simpleTestAsset, 'foo|lib/foo.dart': dedent(r''' class Foo { - int a() => 7; + dynamic a() => 7; int _b(int x) => 8; static int c(int y) => 9; } @@ -58,10 +58,12 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends Mock implements Foo {} + class MockFoo extends _i1.Mock implements Foo {} '''), }, ); @@ -84,10 +86,12 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends Mock implements Foo {} + class MockFoo extends _i1.Mock implements Foo {} '''), }, ); @@ -112,10 +116,12 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends Mock implements Foo { + class MockFoo extends _i1.Mock implements Foo { dynamic a(int m, String n) => super.noSuchMethod(Invocation.method(#a, [m, n])); } @@ -146,10 +152,12 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends Mock implements Foo { + class MockFoo extends _i1.Mock implements Foo { dynamic a(int m, String n) => super.noSuchMethod(Invocation.method(#a, [m, n])); dynamic b(List list) => @@ -188,10 +196,12 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends Mock implements Foo { + class MockFoo extends _i1.Mock implements Foo { dynamic a(int m, String n) => super.noSuchMethod(Invocation.method(#a, [m, n])); } @@ -199,7 +209,7 @@ void main() { /// A class which mocks [Bar]. /// /// See the documentation for Mockito's code generation for more information. - class MockBar extends Mock implements Bar { + class MockBar extends _i1.Mock implements Bar { dynamic b(List list) => super.noSuchMethod(Invocation.method(#b, [list])); } @@ -208,7 +218,39 @@ void main() { ); }); - test('generates a mock class and overrides getters and setters', () async { + test('overrides generic methods', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + dynamic f(int a) {} + // Bounded type parameters blocked by + // https://github.com/dart-lang/code_builder/issues/251. + // dynamic g(int a) {} + } + '''), + }, + outputs: { + // TODO(srawlins): The getter will appear when it has a non-nullable + // return type. + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements Foo { + dynamic f(int a) => super.noSuchMethod(Invocation.method(#f, [a])); + } + '''), + }, + ); + }); + + test('overrides getters and setters', () async { await testBuilder( buildMocks(BuilderOptions({})), { @@ -226,10 +268,12 @@ void main() { // TODO(srawlins): The getter will appear when it has a non-nullable // return type. 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends Mock implements Foo { + class MockFoo extends _i1.Mock implements Foo { set b(int value) => super.noSuchMethod(Invocation.setter(#b, [value])); } '''), @@ -237,6 +281,74 @@ void main() { ); }); + test('creates dummy non-null return values for known core classes', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + bool m1() => false; + double m2() => 3.14; + int m3() => 7; + String m4() => "Hello"; + List m5() => [Foo()]; + Set m6() => {Foo()}; + Map m7() => {7: Foo()}; + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements Foo { + bool m1() => super.noSuchMethod(Invocation.method(#m1, []), false); + double m2() => super.noSuchMethod(Invocation.method(#m2, []), 0.0); + int m3() => super.noSuchMethod(Invocation.method(#m3, []), 0); + String m4() => super.noSuchMethod(Invocation.method(#m4, []), ''); + List m5() => super.noSuchMethod(Invocation.method(#m5, []), []); + Set m6() => super.noSuchMethod(Invocation.method(#m6, []), {}); + Map m7() => super.noSuchMethod(Invocation.method(#m7, []), {}); + } + '''), + }, + ); + }); + + test('creates dummy non-null return values for Futures of known core classes', + () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + Future m1() async => false; + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements Foo { + Future m1() async => + super.noSuchMethod(Invocation.method(#m1, []), Future.value(false)); + } + '''), + }, + ); + }); + test('throws when GenerateMocks references an unresolved type', () async { expectBuilderThrows( assets: { diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 1b801509c..c1f65e221 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -65,9 +65,6 @@ void expectFail(String expectedMessage, dynamic expectedToFail()) { } } -String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " - "please instead use `verifyNever(...);`.)"; - void main() { _MockedClass mock; diff --git a/pkgs/mockito/test/nnbd_support_test.dart b/pkgs/mockito/test/nnbd_support_test.dart new file mode 100644 index 000000000..ed36543aa --- /dev/null +++ b/pkgs/mockito/test/nnbd_support_test.dart @@ -0,0 +1,125 @@ +// Copyright 2019 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +class Foo { + String /*?*/ returnsNullableString() => 'Hello'; + + // TODO(srawlins): When it becomes available, opt this test library into NNBD, + // and make this method really return a non-nullable String. + String /*!*/ returnsNonNullableString() => 'Hello'; +} + +class MockFoo extends Mock implements Foo { + String /*!*/ returnsNonNullableString() { + return super.noSuchMethod( + Invocation.method(#returnsNonNullableString, []), 'Dummy'); + } +} + +void main() { + MockFoo mock; + + setUp(() { + mock = MockFoo(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + 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); + }); + }); + + group('Using a method stub which passes a return argument to nSM,', () { + test('nSM returns the dummy value during method stubbing', () { + // Trigger method stubbing. + final whenCall = when; + final stubbedResponse = mock.returnsNonNullableString(); + + // Under the pre-NNBD type system, this expectation is not interesting. + // Under the NNBD type system, however, it is important that the second + // argument passed to `noSuchMethod` in `returnsNonNullableString` is + // returned by `noSuchMethod`, so that non-null values are can be returned + // when necessary. + expect(stubbedResponse, equals('Dummy')); + whenCall(stubbedResponse).thenReturn('A'); + }); + + test('nSM returns the dummy value during method call verification', () { + when(mock.returnsNonNullableString()).thenReturn('A'); + + // Make a real call. + final realResponse = mock.returnsNonNullableString(); + + // Under the pre-NNBD type system, this expectation is not interesting. + // Under the NNBD type system, however, it is important that the second + // argument passed to `noSuchMethod` in `returnsNonNullableString` is + // _not_ returned by `noSuchMethod`; the canned response, `'A'`, should be + // returned. + expect(realResponse, equals('A')); + + // Trigger method call verification. + final verifyCall = verify; + final verificationResponse = mock.returnsNonNullableString(); + expect(verificationResponse, equals('Dummy')); + verifyCall(verificationResponse); + }); + + test( + 'nSM returns the dummy value during method call verification, using ' + 'verifyNever', () { + // Trigger method call verification. + final verifyNeverCall = verifyNever; + final verificationResponse = mock.returnsNonNullableString(); + expect(verificationResponse, equals('Dummy')); + verifyNeverCall(verificationResponse); + }); + }); +} From 9d274dd36f91a79b75e8c307a7dcba1cf363e86c Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 18 Nov 2019 19:24:35 -0800 Subject: [PATCH 160/607] Mockito generated API: Import all referenced types. PiperOrigin-RevId: 281206303 --- pkgs/mockito/lib/src/builder.dart | 81 +++++++++++++-- pkgs/mockito/test/builder_test.dart | 153 +++++++++++++++++++++++++--- 2 files changed, 208 insertions(+), 26 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 40652bc7f..97d532bce 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -14,7 +14,7 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; @@ -110,15 +110,15 @@ class MockBuilder implements Builder { } } - Class _buildCodeForClass(DartType dartType, ClassElement classToMock) { + Class _buildCodeForClass( + analyzer.DartType dartType, ClassElement classToMock) { final className = dartType.displayName; return Class((cBuilder) { cBuilder ..name = 'Mock$className' ..extend = refer('Mock', 'package:mockito/mockito.dart') - // TODO(srawlins): Add URI of dartType. - ..implements.add(refer(className)) + ..implements.add(_typeReference(dartType)) ..docs.add('/// A class which mocks [$className].') ..docs.add('///') ..docs.add('/// See the documentation for Mockito\'s code generation ' @@ -154,7 +154,7 @@ class MockBuilder implements Builder { if (type.isDynamic || type.isVoid) return false; if (method.isAsynchronous && type.isDartAsyncFuture || type.isDartAsyncFutureOr) { - var typeArgument = (type as InterfaceType).typeArguments.first; + var typeArgument = (type as analyzer.InterfaceType).typeArguments.first; if (typeArgument.isDynamic || typeArgument.isVoid) { // An asynchronous method which returns `Future`, for example, // does not need a dummy return value. @@ -183,7 +183,7 @@ class MockBuilder implements Builder { // TODO(srawlins): abstract methods? builder ..name = method.displayName - ..returns = refer(method.returnType.displayName); + ..returns = _typeReference(method.returnType); if (method.typeParameters != null && method.typeParameters.isNotEmpty) { builder.types.addAll(method.typeParameters.map((p) => refer(p.name))); @@ -235,13 +235,13 @@ class MockBuilder implements Builder { builder.body = returnNoSuchMethod.code; } - Expression _dummyValue(DartType type) { + Expression _dummyValue(analyzer.DartType type) { if (type.isDartCoreBool) { return literalFalse; } else if (type.isDartCoreDouble) { return literalNum(0.0); } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { - var typeArgument = (type as InterfaceType).typeArguments.first; + var typeArgument = (type as analyzer.InterfaceType).typeArguments.first; return refer('Future') .property('value') .call([_dummyValue(typeArgument)]); @@ -272,8 +272,7 @@ class MockBuilder implements Builder { Parameter((pBuilder) { pBuilder ..name = parameter.displayName - // TODO(srawlins): Add URI of `parameter.type`. - ..type = refer(parameter.type.displayName); + ..type = _typeReference(parameter.type); if (parameter.isNamed) pBuilder.named = true; if (parameter.defaultValueCode != null) { pBuilder.defaultTo = Code(parameter.defaultValueCode); @@ -298,7 +297,7 @@ class MockBuilder implements Builder { if (parameter.isRequiredPositional) { builder.requiredParameters.add(Parameter((pBuilder) => pBuilder ..name = parameter.displayName - ..type = refer(parameter.type.displayName))); + ..type = _typeReference(parameter.type))); invocationPositionalArgs.add(refer(parameter.displayName)); } } @@ -313,6 +312,66 @@ class MockBuilder implements Builder { builder.body = returnNoSuchMethod.code; } + /// Create a reference for [type], properly referencing all attached types. + /// + /// This creates proper references for: + /// * [InterfaceType]s (classes, generic classes), + /// * FunctionType parameters (like `void callback(int i)`), + /// * type aliases (typedefs), both new- and old-style. + // TODO(srawlins): Contribute this back to a common location, like + // package:source_gen? + Reference _typeReference(analyzer.DartType type) { + if (type is analyzer.InterfaceType) { + return TypeReference((TypeReferenceBuilder trBuilder) { + trBuilder + ..symbol = type.name + ..url = _typeImport(type); + for (var typeArgument in type.typeArguments) { + trBuilder.types.add(_typeReference(typeArgument)); + } + }); + } else if (type is analyzer.FunctionType) { + GenericFunctionTypeElement element = type.element; + if (element == null) { + // [type] represents a FunctionTypedFormalParameter. + return FunctionType((b) { + b + ..returnType = _typeReference(type.returnType) + ..requiredParameters + .addAll(type.normalParameterTypes.map(_typeReference)) + ..optionalParameters + .addAll(type.optionalParameterTypes.map(_typeReference)); + for (var parameter in type.namedParameterTypes.entries) { + b.namedParameters[parameter.key] = _typeReference(parameter.value); + } + }); + } + return TypeReference((TypeReferenceBuilder trBuilder) { + var typedef = element.enclosingElement; + trBuilder + ..symbol = typedef.name + ..url = _typeImport(type); + for (var typeArgument in type.typeArguments) + trBuilder.types.add(_typeReference(typeArgument)); + }); + } else { + return refer(type.displayName, _typeImport(type)); + } + } + + /// Returns the import URL for [type]. + /// + /// For some types, like `dynamic`, this may return null. + String _typeImport(analyzer.DartType type) { + var library = type.element?.library; + + // For types like `dynamic`, return null; no import needed. + if (library == null) return null; + // TODO(srawlins): See what other code generators do here to guarantee sane + // URIs. + return library.source.uri.toString(); + } + @override final buildExtensions = const { '.dart': ['.mocks.dart'] diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index cc7db0428..32e93de6e 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -59,11 +59,12 @@ void main() { outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo {} + class MockFoo extends _i1.Mock implements _i2.Foo {} '''), }, ); @@ -87,11 +88,12 @@ void main() { outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo {} + class MockFoo extends _i1.Mock implements _i2.Foo {} '''), }, ); @@ -117,11 +119,12 @@ void main() { outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo { + class MockFoo extends _i1.Mock implements _i2.Foo { dynamic a(int m, String n) => super.noSuchMethod(Invocation.method(#a, [m, n])); } @@ -153,11 +156,13 @@ void main() { outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + import 'dart:async' as _i3; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo { + class MockFoo extends _i1.Mock implements _i2.Foo { dynamic a(int m, String n) => super.noSuchMethod(Invocation.method(#a, [m, n])); dynamic b(List list) => @@ -166,7 +171,7 @@ void main() { super.noSuchMethod(Invocation.method(#c, [one, two, three])); void d(String one, {String two, String three = ""}) => super .noSuchMethod(Invocation.method(#d, [one], {#two: two, #three: three})); - Future e(String s) async => + _i3.Future e(String s) async => super.noSuchMethod(Invocation.method(#e, [s])); } '''), @@ -197,11 +202,12 @@ void main() { outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo { + class MockFoo extends _i1.Mock implements _i2.Foo { dynamic a(int m, String n) => super.noSuchMethod(Invocation.method(#a, [m, n])); } @@ -209,7 +215,7 @@ void main() { /// A class which mocks [Bar]. /// /// See the documentation for Mockito's code generation for more information. - class MockBar extends _i1.Mock implements Bar { + class MockBar extends _i1.Mock implements _i2.Bar { dynamic b(List list) => super.noSuchMethod(Invocation.method(#b, [list])); } @@ -218,6 +224,118 @@ void main() { ); }); + test('imports libraries for external class types', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + import 'dart:async'; + class Foo { + dynamic f(List list) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + dynamic f(List<_i2.Foo> list) => + super.noSuchMethod(Invocation.method(#f, [list])); + } + '''), + }, + ); + }); + + test('imports libraries for type aliases with external types', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + import 'dart:async'; + typedef Callback = void Function(); + typedef void Callback2(); + typedef Future Callback3(); + class Foo { + dynamic f(Callback c) {} + dynamic g(Callback2 c) {} + dynamic h(Callback3 c) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + dynamic f(_i2.Callback c) => super.noSuchMethod(Invocation.method(#f, [c])); + dynamic g(_i2.Callback2 c) => super.noSuchMethod(Invocation.method(#g, [c])); + dynamic h(_i2.Callback3<_i2.Foo> c) => + super.noSuchMethod(Invocation.method(#h, [c])); + } + '''), + }, + ); + }); + + test('imports libraries for function types with external types', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + import 'dart:async'; + class Foo { + dynamic f(Foo c()) {} + dynamic g(void c(Foo f)) {} + dynamic h(void c(Foo f, [Foo g])) {} + dynamic i(void c(Foo f, {Foo g})) {} + dynamic j(Foo Function() c) {} + dynamic k(void Function(Foo f) c) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + dynamic f(_i2.Foo Function() c) => + super.noSuchMethod(Invocation.method(#f, [c])); + dynamic g(void Function(_i2.Foo) c) => + super.noSuchMethod(Invocation.method(#g, [c])); + dynamic h(void Function(_i2.Foo, [_i2.Foo]) c) => + super.noSuchMethod(Invocation.method(#h, [c])); + dynamic i(void Function(_i2.Foo, {_i2.Foo g}) c) => + super.noSuchMethod(Invocation.method(#i, [c])); + dynamic j(_i2.Foo Function() c) => + super.noSuchMethod(Invocation.method(#j, [c])); + dynamic k(void Function(_i2.Foo) c) => + super.noSuchMethod(Invocation.method(#k, [c])); + } + '''), + }, + ); + }); + test('overrides generic methods', () async { await testBuilder( buildMocks(BuilderOptions({})), @@ -238,11 +356,12 @@ void main() { // return type. 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo { + class MockFoo extends _i1.Mock implements _i2.Foo { dynamic f(int a) => super.noSuchMethod(Invocation.method(#f, [a])); } '''), @@ -269,11 +388,12 @@ void main() { // return type. 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo { + class MockFoo extends _i1.Mock implements _i2.Foo { set b(int value) => super.noSuchMethod(Invocation.setter(#b, [value])); } '''), @@ -302,18 +422,19 @@ void main() { outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo { + class MockFoo extends _i1.Mock implements _i2.Foo { bool m1() => super.noSuchMethod(Invocation.method(#m1, []), false); double m2() => super.noSuchMethod(Invocation.method(#m2, []), 0.0); int m3() => super.noSuchMethod(Invocation.method(#m3, []), 0); String m4() => super.noSuchMethod(Invocation.method(#m4, []), ''); - List m5() => super.noSuchMethod(Invocation.method(#m5, []), []); - Set m6() => super.noSuchMethod(Invocation.method(#m6, []), {}); - Map m7() => super.noSuchMethod(Invocation.method(#m7, []), {}); + List<_i2.Foo> m5() => super.noSuchMethod(Invocation.method(#m5, []), []); + Set<_i2.Foo> m6() => super.noSuchMethod(Invocation.method(#m6, []), {}); + Map m7() => super.noSuchMethod(Invocation.method(#m7, []), {}); } '''), }, @@ -336,12 +457,14 @@ void main() { outputs: { 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + import 'dart:async' as _i3; /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements Foo { - Future m1() async => + class MockFoo extends _i1.Mock implements _i2.Foo { + _i3.Future m1() async => super.noSuchMethod(Invocation.method(#m1, []), Future.value(false)); } '''), From de3fd6ad2069bba03dba6dd792893bab48a0676c Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 19 Nov 2019 13:35:16 -0800 Subject: [PATCH 161/607] Mockito generated API: Add proper bounds on mock classes and methods PiperOrigin-RevId: 281367705 --- pkgs/mockito/lib/src/builder.dart | 35 +++++++++--- pkgs/mockito/test/builder_test.dart | 82 +++++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 97d532bce..b74768e0e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -79,9 +79,6 @@ class MockBuilder implements Builder { List classesToMock, LibraryBuilder lBuilder) { for (final classToMock in classesToMock) { final dartTypeToMock = classToMock.toTypeValue(); - // TODO(srawlins): Import the library which declares [dartTypeToMock]. - // TODO(srawlins): Import all supporting libraries, used in type - // signatures. if (dartTypeToMock == null) { throw InvalidMockitoAnnotationException( 'The "classes" argument includes a non-type: $classToMock'); @@ -118,11 +115,26 @@ class MockBuilder implements Builder { cBuilder ..name = 'Mock$className' ..extend = refer('Mock', 'package:mockito/mockito.dart') - ..implements.add(_typeReference(dartType)) ..docs.add('/// A class which mocks [$className].') ..docs.add('///') ..docs.add('/// See the documentation for Mockito\'s code generation ' 'for more information.'); + // For each type parameter on [classToMock], the Mock class needs a type + // parameter with same type variables, and a mirrored type argument for + // the "implements" clause. + var typeArguments = []; + if (classToMock.typeParameters != null) { + for (var typeParameter in classToMock.typeParameters) { + cBuilder.types.add(_typeParameterReference(typeParameter)); + typeArguments.add(refer(typeParameter.name)); + } + } + cBuilder.implements.add(TypeReference((b) { + b + ..symbol = dartType.name + ..url = _typeImport(dartType) + ..types.addAll(typeArguments); + })); for (final field in classToMock.fields) { if (field.isPrivate || field.isStatic) { continue; @@ -185,8 +197,8 @@ class MockBuilder implements Builder { ..name = method.displayName ..returns = _typeReference(method.returnType); - if (method.typeParameters != null && method.typeParameters.isNotEmpty) { - builder.types.addAll(method.typeParameters.map((p) => refer(p.name))); + if (method.typeParameters != null) { + builder.types.addAll(method.typeParameters.map(_typeParameterReference)); } if (method.isAsynchronous) { @@ -312,6 +324,17 @@ class MockBuilder implements Builder { builder.body = returnNoSuchMethod.code; } + /// Create a reference for [typeParameter], properly referencing all types + /// in bounds. + TypeReference _typeParameterReference(TypeParameterElement typeParameter) { + return TypeReference((b) { + b.symbol = typeParameter.name; + if (typeParameter.bound != null) { + b.bound = _typeReference(typeParameter.bound); + } + }); + } + /// Create a reference for [type], properly referencing all attached types. /// /// This creates proper references for: diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 32e93de6e..4d2d00a9f 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -224,6 +224,82 @@ void main() { ); }); + test('generates generic mock classes', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + dynamic a(int m) => m + 1; + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + dynamic a(int m) => super.noSuchMethod(Invocation.method(#a, [m])); + } + '''), + }, + ); + }); + + test('generates generic mock classes with type bounds', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + dynamic a(int m) => m + 1; + } + class Bar { + dynamic b(int m) => m + 1; + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo, Bar]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + dynamic a(int m) => super.noSuchMethod(Invocation.method(#a, [m])); + } + + /// A class which mocks [Bar]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockBar extends _i1.Mock implements _i2.Bar { + dynamic b(int m) => super.noSuchMethod(Invocation.method(#b, [m])); + } + '''), + }, + ); + }); + test('imports libraries for external class types', () async { await testBuilder( buildMocks(BuilderOptions({})), @@ -345,9 +421,7 @@ void main() { 'foo|lib/foo.dart': dedent(r''' class Foo { dynamic f(int a) {} - // Bounded type parameters blocked by - // https://github.com/dart-lang/code_builder/issues/251. - // dynamic g(int a) {} + dynamic g(int a) {} } '''), }, @@ -363,6 +437,8 @@ void main() { /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { dynamic f(int a) => super.noSuchMethod(Invocation.method(#f, [a])); + dynamic g(int a) => + super.noSuchMethod(Invocation.method(#g, [a])); } '''), }, From 5f5e4cfa7617d7edb25ddc2c498e50bf6cae26f5 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 20 Nov 2019 13:57:44 -0800 Subject: [PATCH 162/607] Mockito generated API: Create fake classes for unknown dummy return types. This supports unknown classes, enums, and function types. PiperOrigin-RevId: 281594923 --- pkgs/mockito/lib/src/builder.dart | 181 ++++++++++++++++++++-------- pkgs/mockito/test/builder_test.dart | 139 +++++++++++++++++++++ 2 files changed, 272 insertions(+), 48 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b74768e0e..4f04ad622 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -41,24 +41,29 @@ class MockBuilder implements Builder { final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); - final mockLibrary = Library((lBuilder) { - for (final element in entryLib.topLevelElements) { - final annotation = element.metadata.firstWhere( - (annotation) => - annotation.element is ConstructorElement && - annotation.element.enclosingElement.name == 'GenerateMocks', - orElse: () => null); - if (annotation == null) continue; - final generateMocksValue = annotation.computeConstantValue(); - // TODO(srawlins): handle `generateMocksValue == null`? - final classesToMock = generateMocksValue.getField('classes'); - if (classesToMock.isNull) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument has unknown types'); - } - - _buildMockClasses(classesToMock.toListValue(), lBuilder); + final classesToMock = []; + + for (final element in entryLib.topLevelElements) { + final annotation = element.metadata.firstWhere( + (annotation) => + annotation.element is ConstructorElement && + annotation.element.enclosingElement.name == 'GenerateMocks', + orElse: () => null); + if (annotation == null) continue; + final generateMocksValue = annotation.computeConstantValue(); + // TODO(srawlins): handle `generateMocksValue == null`? + final classesField = generateMocksValue.getField('classes'); + if (classesField.isNull) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument has unknown types'); } + classesToMock.addAll(classesField.toListValue()); + } + + final mockLibrary = Library((b) { + var mockLibraryInfo = _MockLibraryInfo(classesToMock); + b.body.addAll(mockLibraryInfo.fakeClasses); + b.body.addAll(mockLibraryInfo.mockClasses); }); if (mockLibrary.body.isEmpty) { @@ -73,10 +78,29 @@ class MockBuilder implements Builder { await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); } + @override + final buildExtensions = const { + '.dart': ['.mocks.dart'] + }; +} + +class _MockLibraryInfo { + /// Mock classes to be added to the generated library. + final mockClasses = []; + + /// Fake classes to be added to the library. + /// + /// A fake class is only generated when it is needed for non-nullable return + /// values. + final fakeClasses = []; + + /// [ClassElement]s which are used in non-nullable return types, for which + /// fake classes are added to the generated library. + final fakedClassElements = []; + /// Build mock classes for [classesToMock], a list of classes obtained from a /// `@GenerateMocks` annotation. - void _buildMockClasses( - List classesToMock, LibraryBuilder lBuilder) { + _MockLibraryInfo(List classesToMock) { for (final classToMock in classesToMock) { final dartTypeToMock = classToMock.toTypeValue(); if (dartTypeToMock == null) { @@ -93,7 +117,7 @@ class MockBuilder implements Builder { } // TODO(srawlins): Catch when someone tries to generate mocks for an // un-subtypable class, like bool, String, FutureOr, etc. - lBuilder.body.add(_buildCodeForClass(dartTypeToMock, elementToMock)); + mockClasses.add(_buildMockClass(dartTypeToMock, elementToMock)); } else if (elementToMock is GenericFunctionTypeElement && elementToMock.enclosingElement is FunctionTypeAliasElement) { throw InvalidMockitoAnnotationException( @@ -107,8 +131,7 @@ class MockBuilder implements Builder { } } - Class _buildCodeForClass( - analyzer.DartType dartType, ClassElement classToMock) { + Class _buildMockClass(analyzer.DartType dartType, ClassElement classToMock) { final className = dartType.displayName; return Class((cBuilder) { @@ -272,24 +295,92 @@ class MockBuilder implements Builder { } else if (type.isDartCoreString) { return literalString(''); } else { - // TODO(srawlins): Returning null for now, but really this should only - // ever get to a state where we have to make a Fake class which implements - // the type, and return a no-op constructor call to that Fake class here. - return literalNull; + // This class is unknown; we must likely generate a fake class, and return + // an instance here. + return _dummyValueImplementing(type); } } - /// Returns a [Parameter] which matches [parameter]. - Parameter _matchingParameter(ParameterElement parameter) => - Parameter((pBuilder) { - pBuilder - ..name = parameter.displayName - ..type = _typeReference(parameter.type); - if (parameter.isNamed) pBuilder.named = true; - if (parameter.defaultValueCode != null) { - pBuilder.defaultTo = Code(parameter.defaultValueCode); + Expression _dummyValueImplementing(analyzer.DartType dartType) { + // For each type parameter on [classToMock], the Mock class needs a type + // parameter with same type variables, and a mirrored type argument for + // the "implements" clause. + var typeArguments = []; + var elementToFake = dartType.element; + if (elementToFake is ClassElement) { + if (elementToFake.isEnum) { + return _typeReference(dartType).property( + elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); + } else { + var fakeName = '_Fake${dartType.name}'; + // Only make one fake class for each class that needs to be faked. + if (!fakedClassElements.contains(elementToFake)) { + fakeClasses.add(Class((cBuilder) { + cBuilder + ..name = fakeName + ..extend = refer('Fake', 'package:mockito/mockito.dart'); + if (elementToFake.typeParameters != null) { + for (var typeParameter in elementToFake.typeParameters) { + cBuilder.types.add(_typeParameterReference(typeParameter)); + typeArguments.add(refer(typeParameter.name)); + } + } + cBuilder.implements.add(TypeReference((b) { + b + ..symbol = dartType.name + ..url = _typeImport(dartType) + ..types.addAll(typeArguments); + })); + })); + fakedClassElements.add(elementToFake); } - }); + return refer(fakeName).newInstance([]); + } + } else if (dartType is analyzer.FunctionType) { + return Method((b) { + // The positional parameters in a FunctionType have no names. This + // counter lets us create unique dummy names. + var counter = 0; + for (final parameter in dartType.parameters) { + if (parameter.isRequiredPositional) { + b.requiredParameters + .add(_matchingParameter(parameter, defaultName: '__p$counter')); + counter++; + } else if (parameter.isOptionalPositional) { + b.optionalParameters + .add(_matchingParameter(parameter, defaultName: '__p$counter')); + counter++; + } else if (parameter.isNamed) { + b.optionalParameters.add(_matchingParameter(parameter)); + } + } + if (dartType.returnType.isVoid) { + b.body = Code(''); + } else { + b.body = _dummyValue(dartType.returnType).code; + } + }).closure; + } + + // We shouldn't get here. + return literalNull; + } + + /// Returns a [Parameter] which matches [parameter]. + Parameter _matchingParameter(ParameterElement parameter, + {String defaultName}) { + String name = + parameter.name?.isEmpty ?? false ? defaultName : parameter.name; + return Parameter((pBuilder) { + pBuilder + ..name = name + ..type = _typeReference(parameter.type); + if (parameter.isNamed) pBuilder.named = true; + if (parameter.defaultValueCode != null) { + pBuilder.defaultTo = Code(parameter.defaultValueCode); + } + }); + } /// Build a setter which overrides [setter], widening the single parameter /// type to be nullable if it is non-nullable. @@ -340,18 +431,17 @@ class MockBuilder implements Builder { /// This creates proper references for: /// * [InterfaceType]s (classes, generic classes), /// * FunctionType parameters (like `void callback(int i)`), - /// * type aliases (typedefs), both new- and old-style. + /// * type aliases (typedefs), both new- and old-style, + /// * enums. // TODO(srawlins): Contribute this back to a common location, like // package:source_gen? Reference _typeReference(analyzer.DartType type) { if (type is analyzer.InterfaceType) { - return TypeReference((TypeReferenceBuilder trBuilder) { - trBuilder + return TypeReference((TypeReferenceBuilder b) { + b ..symbol = type.name - ..url = _typeImport(type); - for (var typeArgument in type.typeArguments) { - trBuilder.types.add(_typeReference(typeArgument)); - } + ..url = _typeImport(type) + ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { GenericFunctionTypeElement element = type.element; @@ -394,11 +484,6 @@ class MockBuilder implements Builder { // URIs. return library.source.uri.toString(); } - - @override - final buildExtensions = const { - '.dart': ['.mocks.dart'] - }; } /// An exception which is thrown when Mockito encounters an invalid annotation. diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 4d2d00a9f..336ebd70f 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -548,6 +548,145 @@ void main() { ); }); + test('creates dummy non-null return values for unknown classes', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + Bar m1() => Bar('name'); + } + class Bar { + final String name; + Bar(this.name); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + class _FakeBar extends _i1.Fake implements _i2.Bar {} + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); + } + '''), + }, + ); + }); + + test('deduplicates fake classes', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + Bar m1() => Bar('name1'); + Bar m2() => Bar('name2'); + } + class Bar { + final String name; + Bar(this.name); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + class _FakeBar extends _i1.Fake implements _i2.Bar {} + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); + _i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar()); + } + '''), + }, + ); + }); + + test('creates dummy non-null return values for enums', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + Bar m1() => Bar('name'); + } + enum Bar { + one, + two, + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _i2.Bar.one); + } + '''), + }, + ); + }); + + test('creates dummy non-null return values for functions', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void Function(int, [String]) m1() => (int i, [String s]) {}; + void Function(Foo, {bool b}) m2() => (Foo f, {bool b}) {}; + Foo Function() m3() => () => Foo(); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + class _FakeFoo extends _i1.Fake implements _i2.Foo {} + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + void Function(int, [String]) m1() => super + .noSuchMethod(Invocation.method(#m1, []), (int __p0, [String __p1]) {}); + void Function(_i2.Foo, {bool b}) m2() => super + .noSuchMethod(Invocation.method(#m2, []), (_i2.Foo __p0, {bool b}) {}); + _i2.Foo Function() m3() => + super.noSuchMethod(Invocation.method(#m3, []), () => _FakeFoo()); + } + '''), + }, + ); + }); + test('throws when GenerateMocks references an unresolved type', () async { expectBuilderThrows( assets: { From 27e6fb3d7002c129219a595e5491d7d18e59d1c1 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 3 Dec 2019 10:45:11 -0800 Subject: [PATCH 163/607] Mockito code generation: Support operators. Also add tests for operators, and abstract methods. PiperOrigin-RevId: 283576341 --- pkgs/mockito/lib/src/builder.dart | 5 ++- pkgs/mockito/test/builder_test.dart | 62 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 4f04ad622..6b8e1e003 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -215,9 +215,10 @@ class _MockLibraryInfo { // yet. void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { // TODO(srawlins): generator methods like async*, sync*. - // TODO(srawlins): abstract methods? + var name = method.displayName; + if (method.isOperator) name = 'operator$name'; builder - ..name = method.displayName + ..name = name ..returns = _typeReference(method.returnType); if (method.typeParameters != null) { diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 336ebd70f..5eb446a40 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -412,6 +412,35 @@ void main() { ); }); + test('overrides absrtact methods', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + dynamic f(int a); + dynamic _g(int a); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + dynamic f(int a) => super.noSuchMethod(Invocation.method(#f, [a])); + } + '''), + }, + ); + }); + test('overrides generic methods', () async { await testBuilder( buildMocks(BuilderOptions({})), @@ -477,6 +506,39 @@ void main() { ); }); + test('overrides operators', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + int _b; + int operator +(Foo other) => _b + other._b; + bool operator ==(Object other) => other is Foo && _b == other._b; + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + int operator +(_i2.Foo other) => + super.noSuchMethod(Invocation.method(#+, [other]), 0); + bool operator ==(Object other) => + super.noSuchMethod(Invocation.method(#==, [other]), false); + } + '''), + }, + ); + }); + test('creates dummy non-null return values for known core classes', () async { await testBuilder( buildMocks(BuilderOptions({})), From 992306d3f27f502ebb556227c441c0dd784349c7 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 17 Dec 2019 11:31:38 -0800 Subject: [PATCH 164/607] Fix newly enforced package:pedantic lints - omit_local_variable_types - prefer_conditional_assignment - prefer_if_null_operators - prefer_single_quotes - use_function_type_syntax_for_parameters Also ignore a deprecated import. Change a check against null to the string "null" to use string interpolation which already handles `null`. Remove some inferrable argument types on function literals. PiperOrigin-RevId: 286022532 --- pkgs/mockito/example/example.dart | 96 ++++---- pkgs/mockito/example/iss/iss.dart | 8 +- pkgs/mockito/example/iss/iss_test.dart | 20 +- pkgs/mockito/lib/src/mock.dart | 67 +++--- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/capture_test.dart | 2 +- .../test/deprecated_apis/mockito_test.dart | 88 ++++---- .../deprecated_apis/until_called_test.dart | 3 +- .../test/deprecated_apis/verify_test.dart | 2 +- .../mockito/test/invocation_matcher_test.dart | 10 +- pkgs/mockito/test/mockito_test.dart | 208 +++++++++--------- pkgs/mockito/test/until_called_test.dart | 3 +- pkgs/mockito/test/verify_test.dart | 2 +- 13 files changed, 253 insertions(+), 258 deletions(-) diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 9742c3a94..3a708b561 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -5,9 +5,9 @@ import 'package:test/test.dart'; // Real class class Cat { - String sound() => "Meow"; + String sound() => 'Meow'; bool eatFood(String food, {bool hungry}) => true; - Future chew() async => print("Chewing..."); + Future chew() async => print('Chewing...'); int walk(List places) => 7; void sleep() {} void hunt(String place, String prey) {} @@ -42,20 +42,20 @@ void main() { verify(cat.sound()); }); - test("How about some stubbing?", () { + test('How about some stubbing?', () { // Unstubbed methods return null. expect(cat.sound(), null); // Stub a method before interacting with it. - when(cat.sound()).thenReturn("Purr"); - expect(cat.sound(), "Purr"); + when(cat.sound()).thenReturn('Purr'); + expect(cat.sound(), 'Purr'); // You can call it again. - expect(cat.sound(), "Purr"); + expect(cat.sound(), 'Purr'); // Let's change the stub. - when(cat.sound()).thenReturn("Meow"); - expect(cat.sound(), "Meow"); + when(cat.sound()).thenReturn('Meow'); + expect(cat.sound(), 'Meow'); // You can stub getters. when(cat.lives).thenReturn(9); @@ -66,48 +66,48 @@ void main() { expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time. - var responses = ["Purr", "Meow"]; + var responses = ['Purr', 'Meow']; when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); - expect(cat.sound(), "Purr"); - expect(cat.sound(), "Meow"); + expect(cat.sound(), 'Purr'); + expect(cat.sound(), 'Meow'); }); - test("Argument matchers", () { + test('Argument matchers', () { // You can use plain arguments themselves - when(cat.eatFood("fish")).thenReturn(true); + when(cat.eatFood('fish')).thenReturn(true); // ... including collections - when(cat.walk(["roof", "tree"])).thenReturn(2); + when(cat.walk(['roof', 'tree'])).thenReturn(2); // ... or matchers - when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); + when(cat.eatFood(argThat(startsWith('dry')))).thenReturn(false); // ... or mix aguments with matchers - when(cat.eatFood(argThat(startsWith("dry")), hungry: true)) + when(cat.eatFood(argThat(startsWith('dry')), hungry: true)) .thenReturn(true); - expect(cat.eatFood("fish"), isTrue); - expect(cat.walk(["roof", "tree"]), equals(2)); - expect(cat.eatFood("dry food"), isFalse); - expect(cat.eatFood("dry food", hungry: true), isTrue); + expect(cat.eatFood('fish'), isTrue); + expect(cat.walk(['roof', 'tree']), equals(2)); + expect(cat.eatFood('dry food'), isFalse); + expect(cat.eatFood('dry food', hungry: true), isTrue); // You can also verify using an argument matcher. - verify(cat.eatFood("fish")); - verify(cat.walk(["roof", "tree"])); - verify(cat.eatFood(argThat(contains("food")))); + verify(cat.eatFood('fish')); + verify(cat.walk(['roof', 'tree'])); + verify(cat.eatFood(argThat(contains('food')))); // You can verify setters. cat.lives = 9; verify(cat.lives = 9); - cat.hunt("backyard", null); - verify(cat.hunt("backyard", null)); // OK: no arg matchers. + cat.hunt('backyard', null); + verify(cat.hunt('backyard', null)); // OK: no arg matchers. - cat.hunt("backyard", null); - verify(cat.hunt(argThat(contains("yard")), + cat.hunt('backyard', null); + verify(cat.hunt(argThat(contains('yard')), argThat(isNull))); // OK: null is wrapped in an arg matcher. }); - test("Named arguments", () { + test('Named arguments', () { // GOOD: argument matchers include their names. when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(true); when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))) @@ -117,7 +117,7 @@ void main() { .thenReturn(true); }); - test("Verifying exact number of invocations / at least x / never", () { + test('Verifying exact number of invocations / at least x / never', () { cat.sound(); cat.sound(); // Exact number of invocations @@ -133,41 +133,41 @@ void main() { verifyNever(cat.eatFood(any)); }); - test("Verification in order", () { - cat.eatFood("Milk"); + test('Verification in order', () { + cat.eatFood('Milk'); cat.sound(); - cat.eatFood("Fish"); - verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood("Fish")]); + cat.eatFood('Fish'); + verifyInOrder([cat.eatFood('Milk'), cat.sound(), cat.eatFood('Fish')]); }); - test("Making sure interaction(s) never happened on mock", () { + test('Making sure interaction(s) never happened on mock', () { verifyZeroInteractions(cat); }); - test("Finding redundant invocations", () { + test('Finding redundant invocations', () { cat.sound(); verify(cat.sound()); verifyNoMoreInteractions(cat); }); - test("Capturing arguments for further assertions", () { + test('Capturing arguments for further assertions', () { // Simple capture: - cat.eatFood("Fish"); - expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); + cat.eatFood('Fish'); + expect(verify(cat.eatFood(captureAny)).captured.single, 'Fish'); // Capture multiple calls: - cat.eatFood("Milk"); - cat.eatFood("Fish"); - expect(verify(cat.eatFood(captureAny)).captured, ["Milk", "Fish"]); + cat.eatFood('Milk'); + cat.eatFood('Fish'); + expect(verify(cat.eatFood(captureAny)).captured, ['Milk', 'Fish']); // Conditional capture: - cat.eatFood("Milk"); - cat.eatFood("Fish"); + cat.eatFood('Milk'); + cat.eatFood('Fish'); expect( - verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); + verify(cat.eatFood(captureThat(startsWith('F')))).captured, ['Fish']); }); - test("Waiting for an interaction", () async { + test('Waiting for an interaction', () async { Future chewHelper(Cat cat) { return cat.chew(); } @@ -177,15 +177,15 @@ void main() { await untilCalled(cat.chew()); // This completes when cat.chew() is called. // Waiting for a call that has already happened. - cat.eatFood("Fish"); + cat.eatFood('Fish'); await untilCalled(cat.eatFood(any)); // This completes immediately. }); - test("Fake class", () { + test('Fake class', () { // Create a new fake Cat at runtime. var cat = FakeCat(); - cat.eatFood("Milk"); // Prints 'Fake eat Milk'. + cat.eatFood('Milk'); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); }); } diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index e932fd9e0..eef8df43d 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -31,9 +31,7 @@ class IssLocator { /// Returns the current GPS position in [latitude, longitude] format. Future update() async { - if (_ongoingRequest == null) { - _ongoingRequest = _doUpdate(); - } + _ongoingRequest ??= _doUpdate(); await _ongoingRequest; _ongoingRequest = null; } @@ -41,7 +39,7 @@ class IssLocator { Future _doUpdate() async { // Returns the point on the earth directly under the space station // at this moment. - Response rs = await client.get('http://api.open-notify.org/iss-now.json'); + var rs = await client.get('http://api.open-notify.org/iss-now.json'); var data = jsonDecode(rs.body); var latitude = double.parse(data['iss_position']['latitude'] as String); var longitude = double.parse(data['iss_position']['longitude'] as String); @@ -60,7 +58,7 @@ class IssSpotter { // The ISS is defined to be visible if the distance from the observer to // the point on the earth directly under the space station is less than 80km. bool get isVisible { - double distance = sphericalDistanceKm(locator.currentPosition, observer); + var distance = sphericalDistanceKm(locator.currentPosition, observer); return distance < 80.0; } } diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index 3aa2a6247..dc4f3cd86 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -27,18 +27,18 @@ void main() { // verify the calculated distance between them. group('Spherical distance', () { test('London - Paris', () { - Point london = Point(51.5073, -0.1277); - Point paris = Point(48.8566, 2.3522); - double d = sphericalDistanceKm(london, paris); + var london = Point(51.5073, -0.1277); + var paris = Point(48.8566, 2.3522); + var d = sphericalDistanceKm(london, paris); // London should be approximately 343.5km // (+/- 0.1km) from Paris. expect(d, closeTo(343.5, 0.1)); }); test('San Francisco - Mountain View', () { - Point sf = Point(37.783333, -122.416667); - Point mtv = Point(37.389444, -122.081944); - double d = sphericalDistanceKm(sf, mtv); + var sf = Point(37.783333, -122.416667); + var mtv = Point(37.389444, -122.081944); + var d = sphericalDistanceKm(sf, mtv); // San Francisco should be approximately 52.8km // (+/- 0.1km) from Mountain View. expect(d, closeTo(52.8, 0.1)); @@ -52,8 +52,8 @@ void main() { // second predefined location. This test runs asynchronously. group('ISS spotter', () { test('ISS visible', () async { - Point sf = Point(37.783333, -122.416667); - Point mtv = Point(37.389444, -122.081944); + var sf = Point(37.783333, -122.416667); + var mtv = Point(37.389444, -122.081944); IssLocator locator = MockIssLocator(); // Mountain View should be visible from San Francisco. when(locator.currentPosition).thenReturn(sf); @@ -63,8 +63,8 @@ void main() { }); test('ISS not visible', () async { - Point london = Point(51.5073, -0.1277); - Point mtv = Point(37.389444, -122.081944); + var london = Point(51.5073, -0.1277); + var mtv = Point(37.389444, -122.081944); IssLocator locator = MockIssLocator(); // London should not be visible from Mountain View. when(locator.currentPosition).thenReturn(london); diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 4ec119c52..fb68a167a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,6 +17,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; +// ignore: deprecated_member_use import 'package:test_api/test_api.dart'; // TODO(srawlins): Remove this when we no longer need to check for an // incompatiblity between test_api and test. @@ -37,7 +38,8 @@ final Map _storedNamedArgs = {}; @Deprecated( 'This function is not a supported function, and may be deleted as early as ' 'Mockito 5.0.0') -void setDefaultResponse(Mock mock, CallPair defaultResponse()) { +void setDefaultResponse( + Mock mock, CallPair Function() defaultResponse) { mock._defaultResponse = defaultResponse; } @@ -105,15 +107,14 @@ class Mock { _responses.add(cannedResponse); } - @override - @visibleForTesting - /// Handles method stubbing, method call verification, and real method calls. /// /// If passed, [returnValue] will be returned during method stubbing and /// method call verification. This is useful in cases where the method /// invocation which led to `noSuchMethod` being called has a non-nullable /// return type. + @override + @visibleForTesting dynamic noSuchMethod(Invocation invocation, [Object /*?*/ returnValue]) { // noSuchMethod is that 'magic' that allows us to ignore implementing fields // and methods and instead define them later at compile-time per instance. @@ -143,7 +144,7 @@ class Mock { const Object().noSuchMethod(invocation); @override - int get hashCode => _givenHashCode == null ? 0 : _givenHashCode; + int get hashCode => _givenHashCode ?? 0; @override bool operator ==(other) => (_givenHashCode != null && other is Mock) @@ -151,7 +152,7 @@ class Mock { : identical(this, other); @override - String toString() => _givenName != null ? _givenName : runtimeType.toString(); + String toString() => _givenName ?? runtimeType.toString(); String _realCallsToString() { var stringRepresentations = _realCalls.map((call) => call.toString()); @@ -245,7 +246,7 @@ class _InvocationForMatchedArguments extends Invocation { factory _InvocationForMatchedArguments(Invocation invocation) { if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) { throw StateError( - "_InvocationForMatchedArguments called when no ArgMatchers have been saved."); + '_InvocationForMatchedArguments called when no ArgMatchers have been saved.'); } // Handle named arguments first, so that we can provide useful errors for @@ -296,7 +297,7 @@ class _InvocationForMatchedArguments extends Invocation { // Iterate through the stored named args, validate them, and add them to // the return map. _storedNamedArgs.forEach((name, arg) { - Symbol nameSymbol = Symbol(name); + var nameSymbol = Symbol(name); if (!invocation.namedArguments.containsKey(nameSymbol)) { // Clear things out for the next call. _storedArgs.clear(); @@ -351,8 +352,8 @@ class _InvocationForMatchedArguments extends Invocation { 'needs to specify the name of the argument it is being used in. For ' 'example: `when(obj.fn(x: anyNamed("x")))`).'); } - int storedIndex = 0; - int positionalIndex = 0; + var storedIndex = 0; + var positionalIndex = 0; while (storedIndex < _storedArgs.length && positionalIndex < invocation.positionalArguments.length) { var arg = _storedArgs[storedIndex]; @@ -463,7 +464,7 @@ class InvocationMatcher { } void _captureArguments(Invocation invocation) { - int index = 0; + var index = 0; for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; if (roleArg is ArgMatcher && roleArg._capture) { @@ -491,7 +492,7 @@ class InvocationMatcher { roleInvocation.namedArguments.length) { return false; } - int index = 0; + var index = 0; for (var roleArg in roleInvocation.positionalArguments) { var actArg = invocation.positionalArguments[index]; if (!isMatchingArg(roleArg, actArg)) { @@ -548,8 +549,7 @@ class RealCall { @override String toString() { var argString = ''; - var args = invocation.positionalArguments - .map((v) => v == null ? "null" : v.toString()); + var args = invocation.positionalArguments.map((v) => '$v'); if (args.any((arg) => arg.contains('\n'))) { // As one or more arg contains newlines, put each on its own line, and // indent each, for better readability. @@ -656,18 +656,18 @@ class _VerifyCall { if (!never && matchingInvocations.isEmpty) { var message; if (mock._realCalls.isEmpty) { - message = "No matching calls (actually, no calls at all)."; + message = 'No matching calls (actually, no calls at all).'; } else { var otherCalls = mock._realCallsToString(); - message = "No matching calls. All calls: $otherCalls"; + message = 'No matching calls. All calls: $otherCalls'; } - fail("$message\n" - "(If you called `verify(...).called(0);`, please instead use " - "`verifyNever(...);`.)"); + fail('$message\n' + '(If you called `verify(...).called(0);`, please instead use ' + '`verifyNever(...);`.)'); } if (never && matchingInvocations.isNotEmpty) { var calls = mock._realCallsToString(); - fail("Unexpected calls. All calls: $calls"); + fail('Unexpected calls. All calls: $calls'); } matchingInvocations.forEach((inv) { inv.verified = true; @@ -880,7 +880,7 @@ class VerificationResult { _checkTestApiMismatch(); } expect(callCount, wrapMatcher(matcher), - reason: "Unexpected number of calls"); + reason: 'Unexpected number of calls'); } } @@ -946,12 +946,12 @@ Verification _makeVerify(bool never) { return (T mock) { _verificationInProgress = false; if (_verifyCalls.length == 1) { - _VerifyCall verifyCall = _verifyCalls.removeLast(); + var verifyCall = _verifyCalls.removeLast(); var result = VerificationResult._(verifyCall.matchingInvocations.length); verifyCall._checkWith(never); return result; } else { - fail("Used on a non-mockito object"); + fail('Used on a non-mockito object'); } }; } @@ -977,23 +977,22 @@ _InOrderVerification get verifyInOrder { _verificationInProgress = true; return (List _) { _verificationInProgress = false; - DateTime dt = DateTime.fromMillisecondsSinceEpoch(0); + var dt = DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); _verifyCalls.clear(); - List matchedCalls = []; - for (_VerifyCall verifyCall in tmpVerifyCalls) { - RealCall matched = verifyCall._findAfter(dt); + var matchedCalls = []; + for (var verifyCall in tmpVerifyCalls) { + var matched = verifyCall._findAfter(dt); if (matched != null) { matchedCalls.add(matched); dt = matched.timeStamp; } else { - Set mocks = - tmpVerifyCalls.map((_VerifyCall vc) => vc.mock).toSet(); - List allInvocations = + var mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); + var allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); - String otherCalls = ""; + var otherCalls = ''; if (allInvocations.isNotEmpty) { otherCalls = " All calls: ${allInvocations.join(", ")}"; } @@ -1018,7 +1017,7 @@ void verifyNoMoreInteractions(var mock) { if (mock is Mock) { var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); if (unverified.isNotEmpty) { - fail("No more calls expected, but following found: " + unverified.join()); + fail('No more calls expected, but following found: ' + unverified.join()); } } else { _throwMockArgumentError('verifyNoMoreInteractions', mock); @@ -1028,7 +1027,7 @@ void verifyNoMoreInteractions(var mock) { void verifyZeroInteractions(var mock) { if (mock is Mock) { if (mock._realCalls.isNotEmpty) { - fail("No interaction expected, but following found: " + + fail('No interaction expected, but following found: ' + mock._realCalls.join()); } } else { @@ -1091,7 +1090,7 @@ InvocationLoader get untilCalled { /// Print all collected invocations of any mock methods of [mocks]. void logInvocations(List mocks) { - List allInvocations = + var allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); allInvocations.forEach((inv) { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 521dd803f..6f39aa45e 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.1 +version: 4.1.2-dev authors: - Dmitriy Fibulwinter diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 09da04522..7c597ba38 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -22,7 +22,7 @@ class _RealClass { String methodWithNormalArgs(int x) => 'Real'; String methodWithListArgs(List x) => 'Real'; String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; set setter(String arg) { throw StateError('I must be mocked'); } diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index 95dbcb492..ab9a4e33e 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -24,18 +24,18 @@ import 'package:test/test.dart'; class _RealClass { _RealClass innerObj; - String methodWithoutArgs() => "Real"; - String methodWithNormalArgs(int x) => "Real"; - String methodWithListArgs(List x) => "Real"; - String methodWithPositionalArgs(int x, [int y]) => "Real"; - String methodWithNamedArgs(int x, {int y}) => "Real"; - String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; - String methodWithObjArgs(_RealClass x) => "Real"; - Future methodReturningFuture() => Future.value("Real"); - Stream methodReturningStream() => Stream.fromIterable(["Real"]); - String get getter => "Real"; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; + Future methodReturningFuture() => Future.value('Real'); + Stream methodReturningStream() => Stream.fromIterable(['Real']); + String get getter => 'Real'; set setter(String arg) { - throw StateError("I must be mocked"); + throw StateError('I must be mocked'); } } @@ -54,23 +54,23 @@ class MockFoo extends AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, dynamic expectedToFail()) { +void expectFail(String expectedMessage, void Function() expectedToFail) { try { expectedToFail(); - fail("It was expected to fail!"); + fail('It was expected to fail!'); } catch (e) { if (!(e is TestFailure)) { rethrow; } else { if (expectedMessage != e.message) { - throw TestFailure("Failed, but with wrong message: ${e.message}"); + throw TestFailure('Failed, but with wrong message: ${e.message}'); } } } } -String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, " - "please instead use `verifyNever(...);`.)"; +String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' + 'please instead use `verifyNever(...);`.)'; void main() { _MockedClass mock; @@ -85,67 +85,67 @@ void main() { resetMockitoState(); }); - group("when()", () { - test("should mock method with argument matcher", () { + group('when()', () { + test('should mock method with argument matcher', () { when(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))) - .thenReturn("A lot!"); + .thenReturn('A lot!'); expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any argument matcher", () { - when(mock.methodWithNormalArgs(typed(any))).thenReturn("A lot!"); - expect(mock.methodWithNormalArgs(100), equals("A lot!")); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + test('should mock method with any argument matcher', () { + when(mock.methodWithNormalArgs(typed(any))).thenReturn('A lot!'); + expect(mock.methodWithNormalArgs(100), equals('A lot!')); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any list argument matcher", () { - when(mock.methodWithListArgs(typed(any))).thenReturn("A lot!"); - expect(mock.methodWithListArgs([42]), equals("A lot!")); - expect(mock.methodWithListArgs([43]), equals("A lot!")); + test('should mock method with any list argument matcher', () { + when(mock.methodWithListArgs(typed(any))).thenReturn('A lot!'); + expect(mock.methodWithListArgs([42]), equals('A lot!')); + expect(mock.methodWithListArgs([43]), equals('A lot!')); }); - test("should mock method with mix of argument matchers and real things", + test('should mock method with mix of argument matchers and real things', () { when(mock.methodWithPositionalArgs(typed(argThat(greaterThan(100))), 17)) - .thenReturn("A lot with 17"); + .thenReturn('A lot with 17'); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); + expect(mock.methodWithPositionalArgs(101, 17), equals('A lot with 17')); }); //no need to mock setter, except if we will have spies later... - test("should mock method with thrown result", () { + test('should mock method with thrown result', () { when(mock.methodWithNormalArgs(typed(any))).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); - test("should mock method with calculated result", () { + test('should mock method with calculated result', () { when(mock.methodWithNormalArgs(typed(any))).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals("43")); - expect(mock.methodWithNormalArgs(42), equals("42")); + expect(mock.methodWithNormalArgs(43), equals('43')); + expect(mock.methodWithNormalArgs(42), equals('42')); }); - test("should mock method with calculated result", () { + test('should mock method with calculated result', () { when(mock.methodWithNormalArgs(typed(argThat(equals(43))))) - .thenReturn("43"); + .thenReturn('43'); when(mock.methodWithNormalArgs(typed(argThat(equals(42))))) - .thenReturn("42"); - expect(mock.methodWithNormalArgs(43), equals("43")); + .thenReturn('42'); + expect(mock.methodWithNormalArgs(43), equals('43')); }); - test("should mock hashCode", () { + test('should mock hashCode', () { named(mock, hashCode: 42); expect(mock.hashCode, equals(42)); }); - test("should have toString as name when it is not mocked", () { - named(mock, name: "Cat"); - expect(mock.toString(), equals("Cat")); + test('should have toString as name when it is not mocked', () { + named(mock, name: 'Cat'); + expect(mock.toString(), equals('Cat')); }); - test("should mock equals between mocks when givenHashCode is equals", () { + test('should mock equals between mocks when givenHashCode is equals', () { var anotherMock = named(_MockedClass(), hashCode: 42); named(mock, hashCode: 42); expect(mock == anotherMock, isTrue); diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart index 92ed48759..f4e6801ed 100644 --- a/pkgs/mockito/test/deprecated_apis/until_called_test.dart +++ b/pkgs/mockito/test/deprecated_apis/until_called_test.dart @@ -86,8 +86,7 @@ void main() { }); group('untilCalled', () { - StreamController streamController = - StreamController.broadcast(); + var streamController = StreamController.broadcast(); group('on methods already called', () { test('waits for method with normal args', () async { diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index a8c870c17..74cfdcba8 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -57,7 +57,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, dynamic expectedToFail()) { +void expectFail(String expectedMessage, void Function() expectedToFail) { try { expectedToFail(); fail('It was expected to fail!'); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 12eae076b..dc5bb5252 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -66,9 +66,9 @@ void main() { shouldFail( call1, isInvocation(call3), - "Expected: lie() " + 'Expected: lie() ' "Actual: " - "Which: Does not match lie()", + 'Which: Does not match lie()', ); }); @@ -102,9 +102,9 @@ void main() { call1, isInvocation(call3), // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - RegExp("Expected: set value=? " + RegExp('Expected: set value=? ' "Actual: " - "Which: Does not match set value=? "), + 'Which: Does not match set value=? '), ); }); }); @@ -118,7 +118,7 @@ void main() { shouldFail( call, invokes(#say, positionalArguments: [isNull]), - "Expected: say(null) " + 'Expected: say(null) ' "Actual: " "Which: Does not match say('Hello')", ); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index c1f65e221..0b4927aa9 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -21,16 +21,16 @@ import 'utils.dart'; class _RealClass { _RealClass innerObj; - String methodWithoutArgs() => "Real"; - String methodWithNormalArgs(int x) => "Real"; - String methodWithListArgs(List x) => "Real"; - String methodWithPositionalArgs(int x, [int y]) => "Real"; - String methodWithNamedArgs(int x, {int y}) => "Real"; - String methodWithTwoNamedArgs(int x, {int y, int z}) => "Real"; - String methodWithObjArgs(_RealClass x) => "Real"; - Future methodReturningFuture() => Future.value("Real"); - Stream methodReturningStream() => Stream.fromIterable(["Real"]); - String get getter => "Real"; + String methodWithoutArgs() => 'Real'; + String methodWithNormalArgs(int x) => 'Real'; + String methodWithListArgs(List x) => 'Real'; + String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNamedArgs(int x, {int y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithObjArgs(_RealClass x) => 'Real'; + Future methodReturningFuture() => Future.value('Real'); + Stream methodReturningStream() => Stream.fromIterable(['Real']); + String get getter => 'Real'; } abstract class _Foo { @@ -43,23 +43,23 @@ abstract class _AbstractFoo implements _Foo { String baz(); - String quux() => "Real"; + String quux() => 'Real'; } class _MockFoo extends _AbstractFoo with Mock {} class _MockedClass extends Mock implements _RealClass {} -void expectFail(String expectedMessage, dynamic expectedToFail()) { +void expectFail(String expectedMessage, void Function() expectedToFail) { try { expectedToFail(); - fail("It was expected to fail!"); + fail('It was expected to fail!'); } catch (e) { if (!(e is TestFailure)) { rethrow; } else { if (expectedMessage != e.message) { - throw TestFailure("Failed, but with wrong message: ${e.message}"); + throw TestFailure('Failed, but with wrong message: ${e.message}'); } } } @@ -80,229 +80,229 @@ void main() { resetMockitoState(); }); - group("mixin support", () { - test("should work", () { + group('mixin support', () { + test('should work', () { var foo = _MockFoo(); when(foo.baz()).thenReturn('baz'); expect(foo.bar(), 'baz'); }); }); - group("when()", () { - test("should mock method without args", () { - when(mock.methodWithoutArgs()).thenReturn("A"); - expect(mock.methodWithoutArgs(), equals("A")); + group('when()', () { + test('should mock method without args', () { + when(mock.methodWithoutArgs()).thenReturn('A'); + expect(mock.methodWithoutArgs(), equals('A')); }); - test("should mock method with normal args", () { - when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); + test('should mock method with normal args', () { + when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); expect(mock.methodWithNormalArgs(43), isNull); - expect(mock.methodWithNormalArgs(42), equals("Ultimate Answer")); + expect(mock.methodWithNormalArgs(42), equals('Ultimate Answer')); }); - test("should mock method with mock args", () { + test('should mock method with mock args', () { var m1 = _MockedClass(); - when(mock.methodWithObjArgs(m1)).thenReturn("Ultimate Answer"); + when(mock.methodWithObjArgs(m1)).thenReturn('Ultimate Answer'); expect(mock.methodWithObjArgs(_MockedClass()), isNull); - expect(mock.methodWithObjArgs(m1), equals("Ultimate Answer")); + expect(mock.methodWithObjArgs(m1), equals('Ultimate Answer')); }); - test("should mock method with positional args", () { - when(mock.methodWithPositionalArgs(42, 17)).thenReturn("Answer and..."); + test('should mock method with positional args', () { + when(mock.methodWithPositionalArgs(42, 17)).thenReturn('Answer and...'); expect(mock.methodWithPositionalArgs(42), isNull); expect(mock.methodWithPositionalArgs(42, 18), isNull); - expect(mock.methodWithPositionalArgs(42, 17), equals("Answer and...")); + expect(mock.methodWithPositionalArgs(42, 17), equals('Answer and...')); }); - test("should mock method with named args", () { - when(mock.methodWithNamedArgs(42, y: 17)).thenReturn("Why answer?"); + test('should mock method with named args', () { + when(mock.methodWithNamedArgs(42, y: 17)).thenReturn('Why answer?'); expect(mock.methodWithNamedArgs(42), isNull); expect(mock.methodWithNamedArgs(42, y: 18), isNull); - expect(mock.methodWithNamedArgs(42, y: 17), equals("Why answer?")); + expect(mock.methodWithNamedArgs(42, y: 17), equals('Why answer?')); }); - test("should mock method with List args", () { - when(mock.methodWithListArgs([42])).thenReturn("Ultimate answer"); + test('should mock method with List args', () { + when(mock.methodWithListArgs([42])).thenReturn('Ultimate answer'); expect(mock.methodWithListArgs([43]), isNull); - expect(mock.methodWithListArgs([42]), equals("Ultimate answer")); + expect(mock.methodWithListArgs([42]), equals('Ultimate answer')); }); - test("should mock method with argument matcher", () { + test('should mock method with argument matcher', () { when(mock.methodWithNormalArgs(argThat(greaterThan(100)))) - .thenReturn("A lot!"); + .thenReturn('A lot!'); expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any argument matcher", () { - when(mock.methodWithNormalArgs(any)).thenReturn("A lot!"); - expect(mock.methodWithNormalArgs(100), equals("A lot!")); - expect(mock.methodWithNormalArgs(101), equals("A lot!")); + test('should mock method with any argument matcher', () { + when(mock.methodWithNormalArgs(any)).thenReturn('A lot!'); + expect(mock.methodWithNormalArgs(100), equals('A lot!')); + expect(mock.methodWithNormalArgs(101), equals('A lot!')); }); - test("should mock method with any list argument matcher", () { - when(mock.methodWithListArgs(any)).thenReturn("A lot!"); - expect(mock.methodWithListArgs([42]), equals("A lot!")); - expect(mock.methodWithListArgs([43]), equals("A lot!")); + test('should mock method with any list argument matcher', () { + when(mock.methodWithListArgs(any)).thenReturn('A lot!'); + expect(mock.methodWithListArgs([42]), equals('A lot!')); + expect(mock.methodWithListArgs([43]), equals('A lot!')); }); - test("should mock method with multiple named args and matchers", () { + test('should mock method with multiple named args and matchers', () { when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'))) - .thenReturn("x y"); + .thenReturn('x y'); when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) - .thenReturn("x z"); + .thenReturn('x z'); if (isNsmForwarding) { - expect(mock.methodWithTwoNamedArgs(42), "x z"); + expect(mock.methodWithTwoNamedArgs(42), 'x z'); } else { expect(mock.methodWithTwoNamedArgs(42), isNull); } - expect(mock.methodWithTwoNamedArgs(42, y: 18), equals("x y")); - expect(mock.methodWithTwoNamedArgs(42, z: 17), equals("x z")); + expect(mock.methodWithTwoNamedArgs(42, y: 18), equals('x y')); + expect(mock.methodWithTwoNamedArgs(42, z: 17), equals('x z')); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); when(mock.methodWithTwoNamedArgs(any, y: anyNamed('y'), z: anyNamed('z'))) - .thenReturn("x y z"); - expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals("x y z")); + .thenReturn('x y z'); + expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), equals('x y z')); }); - test("should mock method with mix of argument matchers and real things", + test('should mock method with mix of argument matchers and real things', () { when(mock.methodWithPositionalArgs(argThat(greaterThan(100)), 17)) - .thenReturn("A lot with 17"); + .thenReturn('A lot with 17'); expect(mock.methodWithPositionalArgs(100, 17), isNull); expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals("A lot with 17")); + expect(mock.methodWithPositionalArgs(101, 17), equals('A lot with 17')); }); - test("should mock getter", () { - when(mock.getter).thenReturn("A"); - expect(mock.getter, equals("A")); + test('should mock getter', () { + when(mock.getter).thenReturn('A'); + expect(mock.getter, equals('A')); }); - test("should have hashCode when it is not mocked", () { + test('should have hashCode when it is not mocked', () { expect(mock.hashCode, isNotNull); }); - test("should have default toString when it is not mocked", () { + test('should have default toString when it is not mocked', () { expect(mock.toString(), equals('_MockedClass')); }); - test("should use identical equality between it is not mocked", () { + test('should use identical equality between it is not mocked', () { var anotherMock = _MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); - test("should mock method with thrown result", () { + test('should mock method with thrown result', () { when(mock.methodWithNormalArgs(any)).thenThrow(StateError('Boo')); expect(() => mock.methodWithNormalArgs(42), throwsStateError); }); - test("should mock method with calculated result", () { + test('should mock method with calculated result', () { when(mock.methodWithNormalArgs(any)).thenAnswer( (Invocation inv) => inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals("43")); - expect(mock.methodWithNormalArgs(42), equals("42")); + expect(mock.methodWithNormalArgs(43), equals('43')); + expect(mock.methodWithNormalArgs(42), equals('42')); }); - test("should return mock to make simple oneline mocks", () { + test('should return mock to make simple oneline mocks', () { _RealClass mockWithSetup = _MockedClass(); - when(mockWithSetup.methodWithoutArgs()).thenReturn("oneline"); - expect(mockWithSetup.methodWithoutArgs(), equals("oneline")); + when(mockWithSetup.methodWithoutArgs()).thenReturn('oneline'); + expect(mockWithSetup.methodWithoutArgs(), equals('oneline')); }); - test("should use latest matching when definition", () { - when(mock.methodWithoutArgs()).thenReturn("A"); - when(mock.methodWithoutArgs()).thenReturn("B"); - expect(mock.methodWithoutArgs(), equals("B")); + test('should use latest matching when definition', () { + when(mock.methodWithoutArgs()).thenReturn('A'); + when(mock.methodWithoutArgs()).thenReturn('B'); + expect(mock.methodWithoutArgs(), equals('B')); }); - test("should mock method with calculated result", () { - when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn("43"); - when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn("42"); - expect(mock.methodWithNormalArgs(43), equals("43")); + test('should mock method with calculated result', () { + when(mock.methodWithNormalArgs(argThat(equals(43)))).thenReturn('43'); + when(mock.methodWithNormalArgs(argThat(equals(42)))).thenReturn('42'); + expect(mock.methodWithNormalArgs(43), equals('43')); }); // Error path tests. - test("should throw if `when` is called while stubbing", () { + test('should throw if `when` is called while stubbing', () { expect(() { var responseHelper = () { var mock2 = _MockedClass(); - when(mock2.getter).thenReturn("A"); + when(mock2.getter).thenReturn('A'); return mock2; }; when(mock.innerObj).thenReturn(responseHelper()); }, throwsStateError); }); - test("thenReturn throws if provided Future", () { + test('thenReturn throws if provided Future', () { expect( () => when(mock.methodReturningFuture()) - .thenReturn(Future.value("stub")), + .thenReturn(Future.value('stub')), throwsArgumentError); }); - test("thenReturn throws if provided Stream", () { + test('thenReturn throws if provided Stream', () { expect( () => when(mock.methodReturningStream()) - .thenReturn(Stream.fromIterable(["stub"])), + .thenReturn(Stream.fromIterable(['stub'])), throwsArgumentError); }); - test("thenAnswer supports stubbing method returning a Future", () async { + test('thenAnswer supports stubbing method returning a Future', () async { when(mock.methodReturningFuture()) - .thenAnswer((_) => Future.value("stub")); + .thenAnswer((_) => Future.value('stub')); - expect(await mock.methodReturningFuture(), "stub"); + expect(await mock.methodReturningFuture(), 'stub'); }); - test("thenAnswer supports stubbing method returning a Stream", () async { + test('thenAnswer supports stubbing method returning a Stream', () async { when(mock.methodReturningStream()) - .thenAnswer((_) => Stream.fromIterable(["stub"])); + .thenAnswer((_) => Stream.fromIterable(['stub'])); - expect(await mock.methodReturningStream().toList(), ["stub"]); + expect(await mock.methodReturningStream().toList(), ['stub']); }); - test("should throw if named matcher is passed as the wrong name", () { + test('should throw if named matcher is passed as the wrong name', () { expect(() { - when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed("z"))) - .thenReturn("99"); + when(mock.methodWithNamedArgs(argThat(equals(42)), y: anyNamed('z'))) + .thenReturn('99'); }, throwsArgumentError); }); - test("should throw if attempting to stub a real method", () { + test('should throw if attempting to stub a real method', () { var foo = _MockFoo(); expect(() { - when(foo.quux()).thenReturn("Stub"); + when(foo.quux()).thenReturn('Stub'); }, throwsStateError); }); }); - group("throwOnMissingStub", () { - test("should throw when a mock was called without a matching stub", () { + group('throwOnMissingStub', () { + test('should throw when a mock was called without a matching stub', () { throwOnMissingStub(mock); - when(mock.methodWithNormalArgs(42)).thenReturn("Ultimate Answer"); + when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); expect( () => (mock).methodWithoutArgs(), throwsNoSuchMethodError, ); }); - test("should not throw when a mock was called with a matching stub", () { + test('should not throw when a mock was called with a matching stub', () { throwOnMissingStub(mock); - when(mock.methodWithoutArgs()).thenReturn("A"); + when(mock.methodWithoutArgs()).thenReturn('A'); expect(() => mock.methodWithoutArgs(), returnsNormally); }); }); test( - "reports an error when using an argument matcher outside of stubbing or " - "verification", () { + 'reports an error when using an argument matcher outside of stubbing or ' + 'verification', () { expect(() => mock.methodWithNormalArgs(any), throwsArgumentError); }); test( - "reports an error when using an argument matcher in a position other " - "than an argument for the stubbed method", () { + 'reports an error when using an argument matcher in a position other ' + 'than an argument for the stubbed method', () { expect(() => when(mock.methodWithListArgs(List.filled(7, any))), throwsArgumentError); }); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index 634c59c0f..4a0b03fd9 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -81,8 +81,7 @@ void main() { }); group('untilCalled', () { - StreamController streamController = - StreamController.broadcast(); + var streamController = StreamController.broadcast(); group('on methods already called', () { test('waits for method without args', () async { diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 0c41c2941..7622a2b0a 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -54,7 +54,7 @@ class LongToString { class _MockedClass extends Mock implements _RealClass {} -void expectFail(Pattern expectedMessage, dynamic expectedToFail()) { +void expectFail(Pattern expectedMessage, void Function() expectedToFail) { try { expectedToFail(); fail('It was expected to fail!'); From b13e1971fd8e03f49bb2bbbaa5e41107d731feed Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 22 Jan 2020 14:52:22 -0800 Subject: [PATCH 165/607] Update README.md example to use Invocation PiperOrigin-RevId: 291036628 --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 5b70df8ab..09b6878d0 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -65,7 +65,7 @@ expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time. var responses = ["Purr", "Meow"]; -when(cat.sound()).thenAnswer(() => responses.removeAt(0)); +when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); expect(cat.sound(), "Purr"); expect(cat.sound(), "Meow"); ``` From b62d47b563231b76e160398e82713d5f00d07352 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 31 Jan 2020 11:31:17 -0800 Subject: [PATCH 166/607] Bump Mockito's dependency on analyzer in order to see isDartCoreList etc. These were introduced in analyzer 0.37.1. PiperOrigin-RevId: 292583748 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 930be48b6..cfe10bc7d 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.1.2 +## 4.1.2-dev * Introduce experimental code-generated mocks. This is primarily to support the new "Non-nullable by default" (NNBD) type system coming soon to Dart. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 6f39aa45e..18e9163b8 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -12,7 +12,7 @@ environment: sdk: '>=2.3.0 <3.0.0' dependencies: - analyzer: ^0.36.0 + analyzer: ^0.37.1 build: ^1.1.3 code_builder: ^3.2.0 collection: ^1.1.0 From 131aad52bcdec4df5cccf06355a4b17f6dbb2486 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 31 Jan 2020 14:11:14 -0800 Subject: [PATCH 167/607] Flip mockito to rely on internal static analysis rules (more-or-less the pedantic lints), so that code which lands internally will pass Travis static analysis. PiperOrigin-RevId: 292614095 --- pkgs/mockito/analysis_options.yaml | 16 ---------------- pkgs/mockito/lib/src/builder.dart | 6 +++--- pkgs/mockito/lib/src/mock.dart | 4 ++++ pkgs/mockito/test/nnbd_support_test.dart | 1 + 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 969b27133..928ef9760 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,20 +1,4 @@ include: package:pedantic/analysis_options.yaml -analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: false - errors: - # These are for 2.5.0-dev.2.0 and after - implicit_dynamic_function: ignore - implicit_dynamic_list_literal: ignore - implicit_dynamic_map_literal: ignore - implicit_dynamic_parameter: ignore - implicit_dynamic_variable: ignore - # These are pre-2.5.0-dev.2.0 - strong_mode_implicit_dynamic_list_literal: ignore - strong_mode_implicit_dynamic_map_literal: ignore - strong_mode_implicit_dynamic_parameter: ignore - strong_mode_implicit_dynamic_variable: ignore linter: rules: # Errors diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 6b8e1e003..2d38275a2 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -370,8 +370,7 @@ class _MockLibraryInfo { /// Returns a [Parameter] which matches [parameter]. Parameter _matchingParameter(ParameterElement parameter, {String defaultName}) { - String name = - parameter.name?.isEmpty ?? false ? defaultName : parameter.name; + var name = parameter.name?.isEmpty ?? false ? defaultName : parameter.name; return Parameter((pBuilder) { pBuilder ..name = name @@ -465,8 +464,9 @@ class _MockLibraryInfo { trBuilder ..symbol = typedef.name ..url = _typeImport(type); - for (var typeArgument in type.typeArguments) + for (var typeArgument in type.typeArguments) { trBuilder.types.add(_typeReference(typeArgument)); + } }); } else { return refer(type.displayName, _typeImport(type)); diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index fb68a167a..6552a1d10 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -806,11 +806,15 @@ class VerificationResult { /// /// Named arguments are listed in the order they are captured in, not the /// order in which they were passed. + // TODO(https://github.com/dart-lang/linter/issues/1992): Remove ignore + // comments below when google3 has linter with this bug fixed. + // ignore: unnecessary_getters_setters List get captured => _captured; @Deprecated( 'captured should be considered final - assigning this field may be ' 'removed as early as Mockito 5.0.0') + // ignore: unnecessary_getters_setters set captured(List captured) => _captured = captured; /// The number of calls matched in this verification. diff --git a/pkgs/mockito/test/nnbd_support_test.dart b/pkgs/mockito/test/nnbd_support_test.dart index ed36543aa..91f263990 100644 --- a/pkgs/mockito/test/nnbd_support_test.dart +++ b/pkgs/mockito/test/nnbd_support_test.dart @@ -24,6 +24,7 @@ class Foo { } class MockFoo extends Mock implements Foo { + @override String /*!*/ returnsNonNullableString() { return super.noSuchMethod( Invocation.method(#returnsNonNullableString, []), 'Dummy'); From b3d3e0436cfe6f49c557b5f1c157273ddf0adbe2 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 3 Feb 2020 14:34:03 -0800 Subject: [PATCH 168/607] Bump mockito's deps on analyzer and dart_style: * dart_style only gained support for extensions in 1.3.0. * analyzer's APIs improved in 0.38.0 allowing "throws when GenerateMocks references a typedef" test to pass (detecting `elementToMock is GenericFunctionTypeElement`). PiperOrigin-RevId: 293006909 --- pkgs/mockito/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 18e9163b8..e76d7aae4 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -12,11 +12,11 @@ environment: sdk: '>=2.3.0 <3.0.0' dependencies: - analyzer: ^0.37.1 + analyzer: ^0.38.0 build: ^1.1.3 code_builder: ^3.2.0 collection: ^1.1.0 - dart_style: ^1.2.5 + dart_style: ^1.3.0 matcher: ^0.12.3 meta: ^1.0.4 test_api: ^0.2.1 From e36a6b64abc8ad62b97e0d3a678b7286431bcd00 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 3 Feb 2020 15:42:17 -0800 Subject: [PATCH 169/607] Move buildMocks from bin/ to lib/src/mock.dart, for use by build.yaml. Also fix some code nits (These things were caught by Travis; need to fix for export to GitHub.) PiperOrigin-RevId: 293021863 --- pkgs/mockito/bin/codegen.dart | 5 ++--- pkgs/mockito/lib/src/builder.dart | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/bin/codegen.dart b/pkgs/mockito/bin/codegen.dart index f3ec2b59d..e216c351e 100644 --- a/pkgs/mockito/bin/codegen.dart +++ b/pkgs/mockito/bin/codegen.dart @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:mockito/src/builder.dart'; - import 'package:build/build.dart'; +import 'package:mockito/src/builder.dart' as b; -Builder buildMocks(BuilderOptions options) => MockBuilder(); +Builder buildMocks(BuilderOptions options) => b.buildMocks(options); diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 2d38275a2..0a16320bd 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -37,10 +37,7 @@ class MockBuilder implements Builder { @override Future build(BuildStep buildStep) async { final entryLib = await buildStep.inputLibrary; - final resolver = buildStep.resolver; - final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); - final classesToMock = []; for (final element in entryLib.topLevelElements) { @@ -429,7 +426,7 @@ class _MockLibraryInfo { /// Create a reference for [type], properly referencing all attached types. /// /// This creates proper references for: - /// * [InterfaceType]s (classes, generic classes), + /// * InterfaceTypes (classes, generic classes), /// * FunctionType parameters (like `void callback(int i)`), /// * type aliases (typedefs), both new- and old-style, /// * enums. @@ -496,3 +493,6 @@ class InvalidMockitoAnnotationException implements Exception { @override String toString() => 'Invalid @GenerateMocks annotation: $message'; } + +/// A [MockBuilder] instance for use by `build.yaml`. +Builder buildMocks(BuilderOptions options) => MockBuilder(); From 2e02465d24e85e4cb71b180a27e41359a1cfc65a Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 3 Feb 2020 17:55:02 -0800 Subject: [PATCH 170/607] Run builder_test only on the VM. Necessary for Travis; no effect in google3. PiperOrigin-RevId: 293048256 --- pkgs/mockito/test/builder_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 5eb446a40..5bbce3aad 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +@TestOn('vm') import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:meta/meta.dart'; From 8eee658d2cfd9932c914a68c1fe8aedf737c74d9 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 11 Mar 2020 18:58:59 -0700 Subject: [PATCH 171/607] Update travis to run 2.4.0 and dev Also bump the minimum Dart SDK --- pkgs/mockito/.travis.yml | 60 +++++++++++++++++++++++++++++---------- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 12aac8822..26f0d103f 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -1,46 +1,76 @@ language: dart -# Gives more resources on Travis (8GB Ram, 2 CPUs). -# Do not remove without verifying w/ Travis. -sudo: required -addons: - chrome: stable - -dart: - - dev - # Build stages: https://docs.travis-ci.com/user/build-stages/. stages: - - presubmit - - build - - testing +- presubmit +- build +- testing # 1. Run dartfmt, dartanalyzer, pub run test (VM). # 2. Then run a build. # 3. Then run tests compiled via dartdevc and dart2js. jobs: - include: + include: + - stage: presubmit + name: "2.4.0 analyzer" + script: ./tool/travis.sh dartanalyzer + dart: 2.4.0 + - stage: presubmit + name: "2.4.0 vm test" + script: ./tool/travis.sh vm_test + dart: 2.4.0 + - stage: build + name: "2.4.0 DDC build" + script: ./tool/travis.sh dartdevc_build + dart: 2.4.0 + - stage: testing + name: "2.4.0 DDC test" + script: ./tool/travis.sh dartdevc_test + dart: 2.4.0 + - stage: testing + name: "2.4.0 dart2js test" + script: ./tool/travis.sh dart2js_test + dart: 2.4.0 + - stage: testing + name: "2.4.0 code coverage" + script: ./tool/travis.sh coverage + dart: 2.4.0 + - stage: presubmit + name: "dev dartfmt" script: ./tool/travis.sh dartfmt + dart: dev - stage: presubmit + name: "dev analyzer" script: ./tool/travis.sh dartanalyzer + dart: dev - stage: presubmit + name: "dev vm test" script: ./tool/travis.sh vm_test + dart: dev - stage: build + name: "dev DDC build" script: ./tool/travis.sh dartdevc_build + dart: dev - stage: testing + name: "dev DDC test" script: ./tool/travis.sh dartdevc_test + dart: dev - stage: testing + name: "dev dart2js test" script: ./tool/travis.sh dart2js_test + dart: dev - stage: testing + name: "dev code coverage" script: ./tool/travis.sh coverage + dart: dev # Only building master means that we don't run two builds for each pull request. branches: - only: [master] + only: [master] # Incremental pub cache and builds. cache: - directories: + directories: - $HOME/.pub-cache - .dart_tool diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index cfe10bc7d..9d485e1bc 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,6 +6,7 @@ clients who use the Mock class in unconventional ways, such as overriding `noSuchMethod` on a class which extends Mock. To fix, or prepare such code, add a second parameter to such overriding `noSuchMethod` declaration. +* Increase minimum Dart SDK to `2.4.0`. ## 4.1.1 diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index e76d7aae4..6edb4825e 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -9,7 +9,7 @@ description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.3.0 <3.0.0' + sdk: '>=2.4.0 <3.0.0' dependencies: analyzer: ^0.38.0 From 1c4fb3be2d561114e9cff23f3e122e4ad4c001b0 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 13 Mar 2020 12:19:25 -0700 Subject: [PATCH 172/607] Remove authors from pubspec --- pkgs/mockito/pubspec.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 6edb4825e..f89993c01 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,10 +1,6 @@ name: mockito version: 4.1.2-dev -authors: - - Dmitriy Fibulwinter - - Dart Team - description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito From 9e8fb8b4c26502741def11d51d790ef70d01638d Mon Sep 17 00:00:00 2001 From: rkj Date: Tue, 3 Mar 2020 14:01:09 -0500 Subject: [PATCH 173/607] Only print non-verified calls when calling verifyNever. I have a tests that calls the mock > 50times and it's super hard to read through all the calls to find the one that triggered failure. With this I just see the unexpected calls. #dart #testing #mockito PiperOrigin-RevId: 298644968 --- pkgs/mockito/CHANGELOG.md | 1 - pkgs/mockito/lib/src/mock.dart | 12 ++++++++---- pkgs/mockito/pubspec.yaml | 6 +++++- pkgs/mockito/test/verify_test.dart | 6 +++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9d485e1bc..cfe10bc7d 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,7 +6,6 @@ clients who use the Mock class in unconventional ways, such as overriding `noSuchMethod` on a class which extends Mock. To fix, or prepare such code, add a second parameter to such overriding `noSuchMethod` declaration. -* Increase minimum Dart SDK to `2.4.0`. ## 4.1.1 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 6552a1d10..5d5afc4e3 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -154,8 +154,9 @@ class Mock { @override String toString() => _givenName ?? runtimeType.toString(); - String _realCallsToString() { - var stringRepresentations = _realCalls.map((call) => call.toString()); + String _realCallsToString([Iterable realCalls]) { + var stringRepresentations = + (realCalls ?? _realCalls).map((call) => call.toString()); if (stringRepresentations.any((s) => s.contains('\n'))) { // As each call contains newlines, put each on its own line, for better // readability. @@ -165,6 +166,9 @@ class Mock { return stringRepresentations.join(', '); } } + + String _unverifiedCallsToString() => + _realCallsToString(_realCalls.where((call) => !call.verified)); } /// Extend or mixin this class to mark the implementation as a [Fake]. @@ -666,8 +670,8 @@ class _VerifyCall { '`verifyNever(...);`.)'); } if (never && matchingInvocations.isNotEmpty) { - var calls = mock._realCallsToString(); - fail('Unexpected calls. All calls: $calls'); + var calls = mock._unverifiedCallsToString(); + fail('Unexpected calls: $calls'); } matchingInvocations.forEach((inv) { inv.verified = true; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index f89993c01..e76d7aae4 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,11 +1,15 @@ name: mockito version: 4.1.2-dev +authors: + - Dmitriy Fibulwinter + - Dart Team + description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.4.0 <3.0.0' + sdk: '>=2.3.0 <3.0.0' dependencies: analyzer: ^0.38.0 diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 7622a2b0a..c66d3253a 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -362,9 +362,13 @@ void main() { }); test('one fails', () { + // Add one verified method that should not appear in message. + mock.methodWithNormalArgs(1); + verify(mock.methodWithNormalArgs(1)).called(1); + mock.methodWithoutArgs(); expectFail( - 'Unexpected calls. All calls: _MockedClass.methodWithoutArgs()', + 'Unexpected calls: _MockedClass.methodWithoutArgs()', () { verifyNever(mock.methodWithoutArgs()); }); From b631138e1c2016f576b46e2f22ca521ddeba00d0 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 18 Mar 2020 13:56:56 -0400 Subject: [PATCH 174/607] Lints - pull https://github.com/dart-lang/mockito/pull/243 by @kevmoo into google3 PiperOrigin-RevId: 301623462 --- pkgs/mockito/analysis_options.yaml | 20 ++++---------------- pkgs/mockito/lib/src/builder.dart | 2 +- pkgs/mockito/test/nnbd_support_test.dart | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 928ef9760..2cdc67be2 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,31 +1,19 @@ include: package:pedantic/analysis_options.yaml +analyzer: + strong-mode: + implicit-casts: false + linter: rules: # Errors - - avoid_empty_else - comment_references - control_flow_in_finally - empty_statements - hash_and_equals - test_types_in_equals - throw_in_finally - - unrelated_type_equality_checks - - valid_regexps # Style - - annotate_overrides - - avoid_init_to_null - - avoid_return_types_on_setters - await_only_futures - camel_case_types - - empty_catches - - empty_constructor_bodies - - library_names - - library_prefixes - non_constant_identifier_names - - prefer_generic_function_type_aliases - - prefer_is_not_empty - - slash_for_doc_comments - - type_init_formals - - unnecessary_const - - unnecessary_new diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0a16320bd..23edeb100 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -441,7 +441,7 @@ class _MockLibraryInfo { ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { - GenericFunctionTypeElement element = type.element; + var element = type.element; if (element == null) { // [type] represents a FunctionTypedFormalParameter. return FunctionType((b) { diff --git a/pkgs/mockito/test/nnbd_support_test.dart b/pkgs/mockito/test/nnbd_support_test.dart index 91f263990..faf089872 100644 --- a/pkgs/mockito/test/nnbd_support_test.dart +++ b/pkgs/mockito/test/nnbd_support_test.dart @@ -27,7 +27,7 @@ class MockFoo extends Mock implements Foo { @override String /*!*/ returnsNonNullableString() { return super.noSuchMethod( - Invocation.method(#returnsNonNullableString, []), 'Dummy'); + Invocation.method(#returnsNonNullableString, []), 'Dummy') as String; } } From 81fa068200b103746707728c7799024474ab532d Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 21 Apr 2020 20:30:27 -0400 Subject: [PATCH 175/607] Improve documentation with an FAQ. Addresses comments in https://github.com/dart-lang/mockito/issues/245, https://github.com/dart-lang/mockito/issues/247, and https://github.com/dart-lang/mockito/issues/239 PiperOrigin-RevId: 307714765 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/README.md | 69 ++-------------------------------- pkgs/mockito/lib/src/mock.dart | 4 ++ pkgs/mockito/pubspec.yaml | 6 +-- 4 files changed, 10 insertions(+), 70 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index cfe10bc7d..9d485e1bc 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,6 +6,7 @@ clients who use the Mock class in unconventional ways, such as overriding `noSuchMethod` on a class which extends Mock. To fix, or prepare such code, add a second parameter to such overriding `noSuchMethod` declaration. +* Increase minimum Dart SDK to `2.4.0`. ## 4.1.1 diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 09b6878d0..61a09dd40 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -243,7 +243,7 @@ verifyNoMoreInteractions(cat); ```dart // Simple capture cat.eatFood("Fish"); -expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); +expect(verify(cat.eatFood(captureAny)).captured.single, ["Fish"]); // Capture multiple calls cat.eatFood("Milk"); @@ -349,70 +349,9 @@ Mockito. A mix of test defined stubbed responses and mock defined overrides will lead to confusion. It is OK to define _static_ utilities on a class which `extends Mock` if it helps with code structure. +## Frequently asked questions -## How it works - -The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to -catch all method invocations, and returns the value that you have configured -beforehand with `when()` calls. - -The implementation of `when()` is a bit more tricky. Take this example: - -```dart -// Unstubbed methods return null: -expect(cat.sound(), nullValue); - -// Stubbing - before execution: -when(cat.sound()).thenReturn("Purr"); -``` - -Since `cat.sound()` returns `null`, how can the `when()` call configure it? - -It works, because `when` is not a function, but a top level getter that -_returns_ a function. Before returning the function, it sets a flag -(`_whenInProgress`), so that all `Mock` objects know to return a "matcher" -(internally `_WhenCall`) instead of the expected value. As soon as the function -has been invoked `_whenInProgress` is set back to `false` and Mock objects -behave as normal. - -Argument matchers work by storing the wrapped arguments, one after another, -until the `when` (or `verify`) call gathers everything that has been stored, -and creates an InvocationMatcher with the arguments. This is a simple process -for positional arguments: the order in which the arguments has been stored -should be preserved for matching an invocation. Named arguments are trickier: -their evaluation order is not specified, so if Mockito blindly stored them in -the order of their evaluation, it wouldn't be able to match up each argument -matcher with the correct name. This is why each named argument matcher must -repeat its own name. `foo: anyNamed('foo')` tells Mockito to store an argument -matcher for an invocation under the name 'foo'. - -> **Be careful** never to write `when;` (without the function call) anywhere. -> This would set `_whenInProgress` to `true`, and the next mock invocation will -> return an unexpected value. - -The same goes for "chaining" mock objects in a test call. This will fail: - -```dart -var mockUtils = MockUtils(); -var mockStringUtils = MockStringUtils(); - -// Setting up mockUtils.stringUtils to return a mock StringUtils implementation -when(mockUtils.stringUtils).thenReturn(mockStringUtils); - -// Some tests - -// FAILS! -verify(mockUtils.stringUtils.uppercase()).called(1); -// Instead use this: -verify(mockStringUtils.uppercase()).called(1); -``` - -This fails, because `verify` sets an internal flag, so mock objects don't -return their mocked values anymore but their matchers. So -`mockUtils.stringUtils` will *not* return the mocked `stringUtils` object you -put inside. - -You can look at the `when` and `Mock.noSuchMethod` implementations to see how -it's done. It's very straightforward. +Read more information about this package in the +[FAQ](https://github.com/dart-lang/mockito/blob/master/FAQ.md). **NOTE:** This is not an official Google product diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 5d5afc4e3..241ad2e9a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -930,6 +930,10 @@ Verification get verifyNever => _makeVerify(true); /// verify(cat.eatFood("fish")).called(greaterThan(3)); /// ``` /// +/// Note: When mockito verifies a method call, said call is then excluded from +/// further verifications. A single method call cannot be verified from multiple +/// calls to `verify`, or `verifyInOrder`. See more details in the FAQ. +/// /// Note: because of an unintended limitation, `verify(...).called(0);` will /// not work as expected. Please use `verifyNever(...);` instead. /// diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index e76d7aae4..f89993c01 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,15 +1,11 @@ name: mockito version: 4.1.2-dev -authors: - - Dmitriy Fibulwinter - - Dart Team - description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.3.0 <3.0.0' + sdk: '>=2.4.0 <3.0.0' dependencies: analyzer: ^0.38.0 From 04657b964d3b50d93ef9b9c751853202d2a8ebcd Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 27 Apr 2020 12:06:06 -0700 Subject: [PATCH 176/607] Format verify_test.dart --- pkgs/mockito/test/verify_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index c66d3253a..62814ed27 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -367,9 +367,7 @@ void main() { verify(mock.methodWithNormalArgs(1)).called(1); mock.methodWithoutArgs(); - expectFail( - 'Unexpected calls: _MockedClass.methodWithoutArgs()', - () { + expectFail('Unexpected calls: _MockedClass.methodWithoutArgs()', () { verifyNever(mock.methodWithoutArgs()); }); }); From a7250ef1269b3c1a4456a41ab441b7766f699ce1 Mon Sep 17 00:00:00 2001 From: Allen Thomas Varghese Date: Wed, 13 May 2020 01:07:19 +0100 Subject: [PATCH 177/607] Minor tweaks (dart-lang/mockito#254) Minor tweaks --- pkgs/mockito/.gitignore | 2 ++ pkgs/mockito/README.md | 13 ++++++------- pkgs/mockito/analysis_options.yaml | 22 +++++++++++----------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index d87ef84cb..9f6d19583 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -5,3 +5,5 @@ .packages .pub pubspec.lock + +coverage/ diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 61a09dd40..fb8587b86 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,4 +1,4 @@ -Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). +# Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito) [![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dartlang.org/packages/mockito) [![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) @@ -72,10 +72,10 @@ expect(cat.sound(), "Meow"); By default, for all methods that return a value, `mock` returns `null`. Stubbing can be overridden: for example common stubbing can go to fixture setup -but the test methods can override it. Please note that overridding stubbing is -a potential code smell that points out too much stubbing. Once stubbed, the +but the test methods can override it. Please note that overridding stubbing is +a potential code smell that points out too much stubbing. Once stubbed, the method will always return stubbed value regardless of how many times it is -called. Last stubbing is more important, when you stubbed the same method with +called. Last stubbing is more important, when you stubbed the same method with the same arguments many times. In other words: the order of stubbing matters, but it is meaningful rarely, e.g. when stubbing exactly the same method calls or sometimes when argument matchers are used, etc. @@ -94,7 +94,7 @@ example: Instead, use `thenAnswer` to stub methods that return a `Future` or `Stream`. -``` +```dart // BAD when(mock.methodThatReturnsAFuture()) .thenReturn(Future.value('Stub')); @@ -112,14 +112,13 @@ when(mock.methodThatReturnsAStream()) If, for some reason, you desire the behavior of `thenReturn`, you can return a pre-defined instance. -``` +```dart // Use the above method unless you're sure you want to create the Future ahead // of time. final future = Future.value('Stub'); when(mock.methodThatReturnsAFuture()).thenAnswer((_) => future); ``` - ## Argument matchers Mockito provides the concept of the "argument matcher" (using the class diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 2cdc67be2..5a16769b2 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -5,15 +5,15 @@ analyzer: linter: rules: - # Errors - - comment_references - - control_flow_in_finally - - empty_statements - - hash_and_equals - - test_types_in_equals - - throw_in_finally + # Errors + - comment_references + - control_flow_in_finally + - empty_statements + - hash_and_equals + - test_types_in_equals + - throw_in_finally - # Style - - await_only_futures - - camel_case_types - - non_constant_identifier_names + # Style + - await_only_futures + - camel_case_types + - non_constant_identifier_names From 69e253003358b6ff0d6796c8aeedfcd37b5d0125 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 27 Apr 2020 13:19:17 -0400 Subject: [PATCH 178/607] Import two commits from GitHub: 1c4fb3be2d561114e9cff23f3e122e4ad4c001b0 Remove authors from pubspec 8eee658d2cfd9932c914a68c1fe8aedf737c74d9 Update travis to run 2.4.0 and dev PiperOrigin-RevId: 308646263 --- pkgs/mockito/CHANGELOG.md | 1 - pkgs/mockito/FAQ.md | 169 +++++++++++++++++++++++++++++ pkgs/mockito/README.md | 13 ++- pkgs/mockito/analysis_options.yaml | 22 ++-- pkgs/mockito/test/verify_test.dart | 4 +- 5 files changed, 190 insertions(+), 19 deletions(-) create mode 100644 pkgs/mockito/FAQ.md diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9d485e1bc..cfe10bc7d 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,7 +6,6 @@ clients who use the Mock class in unconventional ways, such as overriding `noSuchMethod` on a class which extends Mock. To fix, or prepare such code, add a second parameter to such overriding `noSuchMethod` declaration. -* Increase minimum Dart SDK to `2.4.0`. ## 4.1.1 diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md new file mode 100644 index 000000000..4a361b78b --- /dev/null +++ b/pkgs/mockito/FAQ.md @@ -0,0 +1,169 @@ +# Frequently asked questions + +#### How do I mock a static method, constructor, or top-level function? + +Mockito provides its stubbing and verification features by overriding class +instance methods. Since there is no mechanism for overriding static methods, +constructors, or top-level functions, mockito cannot mock them. They are what +they are. + +One idea to consider is "Do I need to use mocks to test this code?" For +example, the [test_descriptor package] allows testing file system concepts using +real files, and the [test_process package] supports testing subprocesses using +real subprocesses. `dart:io` also includes an [IOOVerides] class and a +[runWithIOOverrides] function that can be used to mock out the filesystem. + +[IOOverrides]: https://api.dart.dev/stable/2.7.2/dart-io/IOOverrides-class.html +[runWithIOOverrides]: https://api.dart.dev/stable/2.7.2/dart-io/IOOverrides/runWithIOOverrides.html + +If mocking is still desired, the underlying code may be refactored in order to +enable mocking. One way to get around un-mockable constructors is to change the +function in which the constructor is being called. Instead of constructing an +object, accept one. + +```dart +// BEFORE: +void f() { + var foo = Foo(); + // ... +} + +// AFTER +void f(Foo foo) { + // ... +} +``` + +In tests, you can declare a MockFoo class which implements Foo, and pass such +an object to `f`. + +You can also refactor code which makes much use of such constructors or static +methods to use a wrapper system. For example, instead of calling +`Directory.current` or `new File` throughout your code, use the +[file package]. You can start with a [FileSystem] object (a [LocalFileSystem] +for production and a [MemoryFileSystem] for tests), and use its wrapper methods +([`currentDirectory`] replaces `Directory.current`, [`file()`] replaces +`File()`). Another example of this pattern is the [io package] and its +[ProcessManager] class. + + +[test_descriptor package]: https://pub.dev/documentation/test_descriptor +[test_process package]: https://pub.dev/packages/test_process +[file package]: https://pub.dev/packages/file +[FileSystem]: https://pub.dev/documentation/file/latest/file/FileSystem-class.html +[LocalFileSystem]: https://pub.dev/documentation/file/latest/local/LocalFileSystem-class.html +[MemoryFileSystem]: https://pub.dev/documentation/file/latest/memory/MemoryFileSystem-class.html +[`currentDirectory`]: https://pub.dev/documentation/file/latest/file/FileSystem/currentDirectory.html +[`file()`]: https://pub.dev/documentation/file/latest/file/FileSystem/file.html +[io package]: https://pub.dev/packages/io +[ProcessManager]: https://pub.dev/documentation/io/latest/io/ProcessManager-class.html + +#### How do I mock an extension method? + +If there is no way to override some kind of function, then mockito cannot mock +it. See the above answer for further explanation, and alternatives. + +#### Why can a method call not be verified multiple times? + +When mockito verifies a method call (via [`verify`] or [`verifyInOrder`]), it +marks the call as "verified", which excludes the call from further +verifications. For example: + +```dart +cat.eatFood("fish"); +verify(cat.eatFood("fish")); // This call succeeds. +verify(cat.eatFood(any)); // This call fails. +``` + +In order to make multiple reasonings about a call, for example to assert on the +arguments, make one verification call, and save the captured arguments: + +```dart +cat.hunt("home", "birds"); +var captured = verify(cat.hunt(captureAny, captureAny)).captured.single; +expect(captured[0], equals("home")); +expect(captured[1], equals("birds")); +``` + +If you need to verify the number of types a method was called, _and_ capture the +arguments, save the verification object: + +```dart +cat.hunt("home", "birds"); +cat.hunt("home", "lizards"); +var verification = verify(cat.hunt("home", captureAny)); +verification.called(greaterThan(2)); +var firstCall = verification.captured[0]; +var secondCall = verification.captured[1]; + +expect(firstCall, equals(["birds"])); +expect(secondCall, equals(["lizards"])); +``` + +#### How does mockito work? + +The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to +catch all method invocations, and returns the value that you have configured +beforehand with `when()` calls. + +The implementation of `when()` is a bit more tricky. Take this example: + +```dart +// Unstubbed methods return null: +expect(cat.sound(), nullValue); + +// Stubbing - before execution: +when(cat.sound()).thenReturn("Purr"); +``` + +Since `cat.sound()` returns `null`, how can the `when()` call configure it? + +It works, because `when` is not a function, but a top level getter that +_returns_ a function. Before returning the function, it sets a flag +(`_whenInProgress`), so that all `Mock` objects know to return a "matcher" +(internally `_WhenCall`) instead of the expected value. As soon as the function +has been invoked `_whenInProgress` is set back to `false` and Mock objects +behave as normal. + +Argument matchers work by storing the wrapped arguments, one after another, +until the `when` (or `verify`) call gathers everything that has been stored, +and creates an InvocationMatcher with the arguments. This is a simple process +for positional arguments: the order in which the arguments has been stored +should be preserved for matching an invocation. Named arguments are trickier: +their evaluation order is not specified, so if Mockito blindly stored them in +the order of their evaluation, it wouldn't be able to match up each argument +matcher with the correct name. This is why each named argument matcher must +repeat its own name. `foo: anyNamed('foo')` tells Mockito to store an argument +matcher for an invocation under the name 'foo'. + +> **Be careful** never to write `when;` (without the function call) anywhere. +> This would set `_whenInProgress` to `true`, and the next mock invocation will +> return an unexpected value. + +The same goes for "chaining" mock objects in a test call. This will fail: + +```dart +var mockUtils = MockUtils(); +var mockStringUtils = MockStringUtils(); + +// Setting up mockUtils.stringUtils to return a mock StringUtils implementation +when(mockUtils.stringUtils).thenReturn(mockStringUtils); + +// Some tests + +// FAILS! +verify(mockUtils.stringUtils.uppercase()).called(1); +// Instead use this: +verify(mockStringUtils.uppercase()).called(1); +``` + +This fails, because `verify` sets an internal flag, so mock objects don't +return their mocked values anymore but their matchers. So +`mockUtils.stringUtils` will *not* return the mocked `stringUtils` object you +put inside. + +You can look at the `when` and `Mock.noSuchMethod` implementations to see how +it's done. It's very straightforward. + +[`verify`]: https://pub.dev/documentation/mockito/latest/mockito/verify.html +[`verifyInOrder`]: https://pub.dev/documentation/mockito/latest/mockito/verifyInOrder.html diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index fb8587b86..61a09dd40 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,4 +1,4 @@ -# Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito) +Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). [![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dartlang.org/packages/mockito) [![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) @@ -72,10 +72,10 @@ expect(cat.sound(), "Meow"); By default, for all methods that return a value, `mock` returns `null`. Stubbing can be overridden: for example common stubbing can go to fixture setup -but the test methods can override it. Please note that overridding stubbing is -a potential code smell that points out too much stubbing. Once stubbed, the +but the test methods can override it. Please note that overridding stubbing is +a potential code smell that points out too much stubbing. Once stubbed, the method will always return stubbed value regardless of how many times it is -called. Last stubbing is more important, when you stubbed the same method with +called. Last stubbing is more important, when you stubbed the same method with the same arguments many times. In other words: the order of stubbing matters, but it is meaningful rarely, e.g. when stubbing exactly the same method calls or sometimes when argument matchers are used, etc. @@ -94,7 +94,7 @@ example: Instead, use `thenAnswer` to stub methods that return a `Future` or `Stream`. -```dart +``` // BAD when(mock.methodThatReturnsAFuture()) .thenReturn(Future.value('Stub')); @@ -112,13 +112,14 @@ when(mock.methodThatReturnsAStream()) If, for some reason, you desire the behavior of `thenReturn`, you can return a pre-defined instance. -```dart +``` // Use the above method unless you're sure you want to create the Future ahead // of time. final future = Future.value('Stub'); when(mock.methodThatReturnsAFuture()).thenAnswer((_) => future); ``` + ## Argument matchers Mockito provides the concept of the "argument matcher" (using the class diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 5a16769b2..2cdc67be2 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -5,15 +5,15 @@ analyzer: linter: rules: - # Errors - - comment_references - - control_flow_in_finally - - empty_statements - - hash_and_equals - - test_types_in_equals - - throw_in_finally + # Errors + - comment_references + - control_flow_in_finally + - empty_statements + - hash_and_equals + - test_types_in_equals + - throw_in_finally - # Style - - await_only_futures - - camel_case_types - - non_constant_identifier_names + # Style + - await_only_futures + - camel_case_types + - non_constant_identifier_names diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 62814ed27..c66d3253a 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -367,7 +367,9 @@ void main() { verify(mock.methodWithNormalArgs(1)).called(1); mock.methodWithoutArgs(); - expectFail('Unexpected calls: _MockedClass.methodWithoutArgs()', () { + expectFail( + 'Unexpected calls: _MockedClass.methodWithoutArgs()', + () { verifyNever(mock.methodWithoutArgs()); }); }); From dc51919b188b93cdd09c6f42af2048b60117960d Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 27 Apr 2020 14:26:56 -0400 Subject: [PATCH 179/607] Missed CHANGELOG file in GitHub import: 8eee658d2cfd9932c914a68c1fe8aedf737c74d9 Update travis to run 2.4.0 and dev PiperOrigin-RevId: 308662083 --- pkgs/mockito/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index cfe10bc7d..9d485e1bc 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,6 +6,7 @@ clients who use the Mock class in unconventional ways, such as overriding `noSuchMethod` on a class which extends Mock. To fix, or prepare such code, add a second parameter to such overriding `noSuchMethod` declaration. +* Increase minimum Dart SDK to `2.4.0`. ## 4.1.1 From 4a2af3ae7af39724029fa3bdc4fa0e040b6a39c4 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 27 Apr 2020 15:04:23 -0400 Subject: [PATCH 180/607] Format verify_test.dart I'm not sure how this file has been checked in w/o formatting. PiperOrigin-RevId: 308670018 --- pkgs/mockito/test/verify_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index c66d3253a..62814ed27 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -367,9 +367,7 @@ void main() { verify(mock.methodWithNormalArgs(1)).called(1); mock.methodWithoutArgs(); - expectFail( - 'Unexpected calls: _MockedClass.methodWithoutArgs()', - () { + expectFail('Unexpected calls: _MockedClass.methodWithoutArgs()', () { verifyNever(mock.methodWithoutArgs()); }); }); From 944aef65ad87e412315206b17e864876710bf92f Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 28 Apr 2020 22:08:23 -0400 Subject: [PATCH 181/607] Link README text to API docs. Additionally, introduce API docs for thenReturn, thenAnswer, and thenThrow. Fixes https://github.com/dart-lang/mockito/issues/246 PiperOrigin-RevId: 308943156 --- pkgs/mockito/README.md | 77 +++++++++++++++++++++++++--------- pkgs/mockito/lib/src/mock.dart | 8 ++++ 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 61a09dd40..fe936d5de 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -26,6 +26,9 @@ class MockCat extends Mock implements Cat {} var cat = MockCat(); ``` +By declaring a class which extends Mockito's Mock class and implements the Cat +class under test, we have a class which supports stubbing and verifying. + ## Let's verify some behaviour! ```dart @@ -35,8 +38,9 @@ cat.sound(); verify(cat.sound()); ``` -Once created, mock will remember all interactions. Then you can selectively -verify whatever interaction you are interested in. +Once created, the mock instance will remember all interactions. Then you can +selectively [`verify`] (or [`verifyInOrder`], or [`verifyNever`]) the +interactions you are interested in. ## How about some stubbing? @@ -70,19 +74,16 @@ expect(cat.sound(), "Purr"); expect(cat.sound(), "Meow"); ``` -By default, for all methods that return a value, `mock` returns `null`. -Stubbing can be overridden: for example common stubbing can go to fixture setup -but the test methods can override it. Please note that overridding stubbing is -a potential code smell that points out too much stubbing. Once stubbed, the -method will always return stubbed value regardless of how many times it is -called. Last stubbing is more important, when you stubbed the same method with -the same arguments many times. In other words: the order of stubbing matters, -but it is meaningful rarely, e.g. when stubbing exactly the same method calls -or sometimes when argument matchers are used, etc. +By default, any instance method of the mock instance returns `null`. The +[`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a +stubbing mechanism to override this behavior. Once stubbed, the method will +always return stubbed value regardless of how many times it is called. If a +method invocation matches multiple stubs, the one which was declared last will +be used. ### A quick word on async stubbing -**Using `thenReturn` to return a `Future` or `Stream` will throw an +**Using [`thenReturn`] to return a `Future` or `Stream` will throw an `ArgumentError`.** This is because it can lead to unexpected behaviors. For example: @@ -154,10 +155,10 @@ cat.lives = 9; verify(cat.lives=9); ``` -If an argument other than an ArgMatcher (like `any`, `anyNamed()`, `argThat`, -`captureArg`, etc.) is passed to a mock method, then the `equals` matcher is -used for argument matching. If you need more strict matching consider use -`argThat(identical(arg))`. +If an argument other than an ArgMatcher (like [`any`], [`anyNamed`], +[`argThat`], [`captureThat`], etc.) is passed to a mock method, then the +[`equals`] matcher is used for argument matching. If you need more strict +matching consider use `argThat(identical(arg))`. However, note that `null` cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example: @@ -194,6 +195,8 @@ when(cat.eatFood(any, hungry: captureThat(isNotNull))).thenReturn(true); ## Verifying exact number of invocations / at least x / never +Use [`verify`] or [`verifyNever`]: + ```dart cat.sound(); cat.sound(); @@ -210,6 +213,8 @@ verifyNever(cat.eatFood(any)); ## Verification in order +Use [`verifyInOrder`]: + ```dart cat.eatFood("Milk"); cat.sound(); @@ -226,12 +231,16 @@ one-by-one but only those that you are interested in testing in order. ## Making sure interaction(s) never happened on mock +Use [`verifyZeroInteractions`]: + ```dart verifyZeroInteractions(cat); ``` ## Finding redundant invocations +Use [`verifyNoMoreInteractions`]: + ```dart cat.sound(); verify(cat.sound()); @@ -240,6 +249,8 @@ verifyNoMoreInteractions(cat); ## Capturing arguments for further assertions +Use the [`captureAny`], [`captureThat`], and [`captureNamed`] argument matchers: + ```dart // Simple capture cat.eatFood("Fish"); @@ -258,6 +269,8 @@ expect(verify(cat.eatFood(captureThat(startsWith("F")))).captured, ["Fish"]); ## Waiting for an interaction +Use [`untilCalled`]: + ```dart // Waiting for a call. cat.eatFood("Fish"); @@ -295,11 +308,10 @@ void main() { } ``` -[Fake]: https://pub.dev/documentation/mockito/latest/mockito/Fake-class.html -[UnimplementedError]: https://api.dartlang.org/stable/dart-core/UnimplementedError-class.html - ## Resetting mocks +Use [`reset`]: + ```dart // Clearing collected interactions: cat.eatFood("Fish"); @@ -317,6 +329,8 @@ expect(cat.eatFood("Fish"), false); ## Debugging +Use [`logInvocations`] and [`throwOnMissingStub`]: + ```dart // Print all collected invocations of any mock methods of a list of mock objects: logInvocations([catOne, catTwo]); @@ -329,7 +343,7 @@ throwOnMissingStub(cat); Testing with real objects is preferred over testing with mocks - if you can construct a real instance for your tests, you should! If there are no calls to -`verify` in your test, it is a strong signal that you may not need mocks at all, +[`verify`] in your test, it is a strong signal that you may not need mocks at all, though it's also OK to use a `Mock` like a stub. When it's not possible to use the real object, a tested implementation of a fake is the next best thing - it's more likely to behave similarly to the real class than responses stubbed out in @@ -355,3 +369,26 @@ Read more information about this package in the [FAQ](https://github.com/dart-lang/mockito/blob/master/FAQ.md). **NOTE:** This is not an official Google product + +[`verify`]: https://pub.dev/documentation/mockito/latest/mockito/verify.html +[`verifyInOrder`]: https://pub.dev/documentation/mockito/latest/mockito/verifyInOrder.html +[`verifyNever`]: https://pub.dev/documentation/mockito/latest/mockito/verifyNever.html +[`when`]: https://pub.dev/documentation/mockito/latest/mockito/when.html +[`thenReturn`]: https://pub.dev/documentation/mockito/latest/mockito/PostExpectation/thenReturn.html +[`thenAnswer`]: https://pub.dev/documentation/mockito/latest/mockito/PostExpectation/thenAnswer.html +[`thenThrow`]: https://pub.dev/documentation/mockito/latest/mockito/PostExpectation/thenThrow.html +[`any`]: https://pub.dev/documentation/mockito/latest/mockito/any.html +[`anyNamed`]: https://pub.dev/documentation/mockito/latest/mockito/anyNamed.html +[`argThat`]: https://pub.dev/documentation/mockito/latest/mockito/argThat.html +[`captureAny`]: https://pub.dev/documentation/mockito/latest/mockito/captureAny.html +[`captureThat`]: https://pub.dev/documentation/mockito/latest/mockito/captureThat.html +[`captureAnyNamed`]: https://pub.dev/documentation/mockito/latest/mockito/captureAnyNamed.html +[`equals`]: https://pub.dev/documentation/matcher/latest/matcher/equals.html +[`verifyZeroInteractions`]: https://pub.dev/documentation/mockito/latest/mockito/verifyZeroInteractions.html +[`verifyNoMoreInteractions`]: https://pub.dev/documentation/mockito/latest/mockito/verifyNoMoreInteractions.html +[`untilCalled`]: https://pub.dev/documentation/mockito/latest/mockito/untilCalled.html +[Fake]: https://pub.dev/documentation/mockito/latest/mockito/Fake-class.html +[UnimplementedError]: https://api.dartlang.org/stable/dart-core/UnimplementedError-class.html +[`reset`]: https://pub.dev/documentation/mockito/latest/mockito/reset.html +[`logInvocations]: https://pub.dev/documentation/mockito/latest/mockito/logInvocations.html +[`throwOnMissingStub`]: https://pub.dev/documentation/mockito/latest/mockito/throwOnMissingStub.html diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 241ad2e9a..c58476ef3 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -407,6 +407,10 @@ void clearInteractions(var mock) { } class PostExpectation { + /// Store a canned response for this method stub. + /// + /// Note: [expected] cannot be a Future or Stream, due to Zone considerations. + /// To return a Future or Stream from a method stub, use [thenAnswer]. void thenReturn(T expected) { if (expected is Future) { throw ArgumentError('`thenReturn` should not be used to return a Future. ' @@ -419,12 +423,16 @@ class PostExpectation { return _completeWhen((_) => expected); } + /// Store an exception to throw when this method stub is called. void thenThrow(throwable) { return _completeWhen((_) { throw throwable; }); } + /// Store a function which is called when this method stub is called. + /// + /// The function will be called, and the return value will be returned. void thenAnswer(Answering answer) { return _completeWhen(answer); } From b22de6ad7fd2ac4759f032cdd15251531e86626a Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 13 May 2020 18:33:06 -0400 Subject: [PATCH 182/607] Merge "Minor tweaks" PR dart-lang/mockito#254 from Github. PiperOrigin-RevId: 311417418 --- pkgs/mockito/README.md | 10 ++++++---- pkgs/mockito/analysis_options.yaml | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index fe936d5de..4dbe15119 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,3 +1,5 @@ +# mockito + Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). [![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dartlang.org/packages/mockito) @@ -76,8 +78,8 @@ expect(cat.sound(), "Meow"); By default, any instance method of the mock instance returns `null`. The [`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a -stubbing mechanism to override this behavior. Once stubbed, the method will -always return stubbed value regardless of how many times it is called. If a +stubbing mechanism to override this behavior. Once stubbed, the method will +always return stubbed value regardless of how many times it is called. If a method invocation matches multiple stubs, the one which was declared last will be used. @@ -95,7 +97,7 @@ example: Instead, use `thenAnswer` to stub methods that return a `Future` or `Stream`. -``` +```dart // BAD when(mock.methodThatReturnsAFuture()) .thenReturn(Future.value('Stub')); @@ -113,7 +115,7 @@ when(mock.methodThatReturnsAStream()) If, for some reason, you desire the behavior of `thenReturn`, you can return a pre-defined instance. -``` +```dart // Use the above method unless you're sure you want to create the Future ahead // of time. final future = Future.value('Stub'); diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 2cdc67be2..5a16769b2 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -5,15 +5,15 @@ analyzer: linter: rules: - # Errors - - comment_references - - control_flow_in_finally - - empty_statements - - hash_and_equals - - test_types_in_equals - - throw_in_finally + # Errors + - comment_references + - control_flow_in_finally + - empty_statements + - hash_and_equals + - test_types_in_equals + - throw_in_finally - # Style - - await_only_futures - - camel_case_types - - non_constant_identifier_names + # Style + - await_only_futures + - camel_case_types + - non_constant_identifier_names From 99bde4f03b1262b362c4a6444cc490abde8767ce Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Fri, 15 May 2020 09:38:25 +0200 Subject: [PATCH 183/607] Update README.md --- pkgs/mockito/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 4dbe15119..32904aa2f 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -370,8 +370,6 @@ lead to confusion. It is OK to define _static_ utilities on a class which Read more information about this package in the [FAQ](https://github.com/dart-lang/mockito/blob/master/FAQ.md). -**NOTE:** This is not an official Google product - [`verify`]: https://pub.dev/documentation/mockito/latest/mockito/verify.html [`verifyInOrder`]: https://pub.dev/documentation/mockito/latest/mockito/verifyInOrder.html [`verifyNever`]: https://pub.dev/documentation/mockito/latest/mockito/verifyNever.html From fc165cd31365118ae4ad09fbeab4739ddcd32868 Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Fri, 15 May 2020 09:39:30 +0200 Subject: [PATCH 184/607] Update CONTRIBUTING.md --- pkgs/mockito/CONTRIBUTING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/mockito/CONTRIBUTING.md b/pkgs/mockito/CONTRIBUTING.md index a984856bf..c363dda1b 100644 --- a/pkgs/mockito/CONTRIBUTING.md +++ b/pkgs/mockito/CONTRIBUTING.md @@ -27,5 +27,3 @@ use GitHub pull requests for this purpose. Contributions made by corporations are covered by a different agreement than the one above, the [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). - -**NOTE:** This is not an official Google product From d0a333321968b869d434244447316a88e8f3d743 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 16 May 2020 11:59:21 -0700 Subject: [PATCH 185/607] Update old links to point to dart.dev and pub.dev (dart-lang/mockito#244) --- pkgs/mockito/.gitignore | 2 +- pkgs/mockito/CHANGELOG.md | 10 +++++----- pkgs/mockito/README.md | 2 +- pkgs/mockito/example/iss/README.md | 2 +- pkgs/mockito/lib/src/mock.dart | 1 - 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index 9f6d19583..7621c584a 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -1,4 +1,4 @@ -# See https://www.dartlang.org/guides/libraries/private-files +# See https://dart.dev/guides/libraries/private-files # Files and directories created by pub .dart_tool diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9d485e1bc..5f40fbc8f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -24,8 +24,8 @@ ## 4.0.0 * Replace the dependency on the - _[test](https://pub.dartlang.org/packages/test)_ package with a dependency on - the new _[test_api](https://pub.dartlang.org/packages/test_api)_ package. + _[test](https://pub.dev/packages/test)_ package with a dependency on + the new _[test_api](https://pub.dev/packages/test_api)_ package. This dramatically reduces mockito's transitive dependencies. This bump can result in runtime errors when coupled with a version of the @@ -33,15 +33,15 @@ ## 3.0.2 -* Rollback the _[test_api](https://pub.dartlang.org/packages/test_api)_ part of +* Rollback the _[test_api](https://pub.dev/packages/test_api)_ part of the 3.0.1 release. This was breaking tests that use Flutter's current test tools, and will instead be released as part of Mockito 4.0.0. ## 3.0.1 * Replace the dependency on the - _[test](https://pub.dartlang.org/packages/test)_ package with a dependency on - the new _[test_api](https://pub.dartlang.org/packages/test_api)_ package. + _[test](https://pub.dev/packages/test)_ package with a dependency on + the new _[test_api](https://pub.dev/packages/test_api)_ package. This dramatically reduces mockito's transitive dependencies. * Internal improvements to tests and examples. diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 32904aa2f..665da49c6 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -2,7 +2,7 @@ Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). -[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dartlang.org/packages/mockito) +[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito) [![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) ## Let's create mocks diff --git a/pkgs/mockito/example/iss/README.md b/pkgs/mockito/example/iss/README.md index 18039bcf2..9e0acde4e 100644 --- a/pkgs/mockito/example/iss/README.md +++ b/pkgs/mockito/example/iss/README.md @@ -1,7 +1,7 @@ # International Space Station (ISS) library This library accesses the International Space Station's APIs -(using [package:http](https://pub.dartlang.org/packages/http)) +(using [package:http](https://pub.dev/packages/http)) to fetch the space station's current location. The unit tests for this library use package:mockito to generate diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index c58476ef3..9b2e3b1c7 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -118,7 +118,6 @@ class Mock { dynamic noSuchMethod(Invocation invocation, [Object /*?*/ returnValue]) { // noSuchMethod is that 'magic' that allows us to ignore implementing fields // and methods and instead define them later at compile-time per instance. - // See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH invocation = _useMatchedInvocationIfSet(invocation); if (_whenInProgress) { _whenCall = _WhenCall(this, invocation); From 34b9fdc5018911eb3776f2663adebf2476d754be Mon Sep 17 00:00:00 2001 From: Edwin Nyawoli Date: Tue, 2 Jun 2020 09:50:02 +0000 Subject: [PATCH 186/607] Add any matcher example in README.md The other matchers shown here are functions but `any` is not. This may cause users to assume all matchers are functions and try calling `any()`. --- pkgs/mockito/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 665da49c6..dcb356514 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -139,6 +139,7 @@ when(cat.walk(["roof","tree"])).thenReturn(2); // ... or matchers when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); +when(cat.eatFood(any)).thenReturn(false); // ... or mix aguments with matchers when(cat.eatFood(argThat(startsWith("dry")), hungry: true)).thenReturn(true); From e47dc0b8b986463d6578d5ac798e403f79a27131 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 7 Jul 2020 13:43:34 -0700 Subject: [PATCH 187/607] MockBuilder: don't write imports for type variables. --- pkgs/mockito/lib/src/builder.dart | 5 ++++- pkgs/mockito/test/builder_test.dart | 35 ++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 23edeb100..1aeb1ef89 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -472,8 +472,11 @@ class _MockLibraryInfo { /// Returns the import URL for [type]. /// - /// For some types, like `dynamic`, this may return null. + /// For some types, like `dynamic` and type variables, this may return null. String _typeImport(analyzer.DartType type) { + // For type variables, no import needed. + if (type is analyzer.TypeParameterType) return null; + var library = type.element?.library; // For types like `dynamic`, return null; no import needed. diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 5bbce3aad..f67137e95 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -301,6 +301,39 @@ void main() { ); }); + test('writes non-interface types w/o imports', () async { + await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + import 'dart:async'; + class Foo { + void f(dynamic a) {} + void g(T b) {} + void h(U c) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + void f(dynamic a) => super.noSuchMethod(Invocation.method(#f, [a])); + void g(T b) => super.noSuchMethod(Invocation.method(#g, [b])); + void h(U c) => super.noSuchMethod(Invocation.method(#h, [c])); + } + '''), + }, + ); + }); + test('imports libraries for external class types', () async { await testBuilder( buildMocks(BuilderOptions({})), @@ -413,7 +446,7 @@ void main() { ); }); - test('overrides absrtact methods', () async { + test('overrides abstract methods', () async { await testBuilder( buildMocks(BuilderOptions({})), { From 23e4c802482f686256a5f095483a6a4593ac0a73 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 3 Jun 2020 16:28:29 -0400 Subject: [PATCH 188/607] MockBuilder: integrate non-nullability of libraries into types and tests. The (non-)nullability of types is matched between source classes and mock classes. This should be true for any generated type annotation, including method parameters and method return types. A method is no longer stubbed if all of its parameters are nullable, or if the source library does not use the non-nullable type system. PiperOrigin-RevId: 314593229 --- pkgs/mockito/lib/src/builder.dart | 41 ++++- pkgs/mockito/test/builder_test.dart | 269 +++++++++++++++++++++++----- 2 files changed, 254 insertions(+), 56 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 1aeb1ef89..ff878f354 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -14,7 +14,9 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; +import 'package:analyzer/dart/element/type_system.dart'; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; @@ -37,6 +39,7 @@ class MockBuilder implements Builder { @override Future build(BuildStep buildStep) async { final entryLib = await buildStep.inputLibrary; + final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); final classesToMock = []; @@ -58,7 +61,9 @@ class MockBuilder implements Builder { } final mockLibrary = Library((b) { - var mockLibraryInfo = _MockLibraryInfo(classesToMock); + var mockLibraryInfo = _MockLibraryInfo(classesToMock, + sourceLibIsNonNullable: sourceLibIsNonNullable, + typeSystem: entryLib.typeSystem); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); @@ -68,7 +73,8 @@ class MockBuilder implements Builder { return; } - final emitter = DartEmitter.scoped(); + final emitter = + DartEmitter.scoped(useNullSafetySyntax: sourceLibIsNonNullable); final mockLibraryContent = DartFormatter().format(mockLibrary.accept(emitter).toString()); @@ -82,6 +88,11 @@ class MockBuilder implements Builder { } class _MockLibraryInfo { + final bool sourceLibIsNonNullable; + + /// The type system which applies to the source library. + final TypeSystem typeSystem; + /// Mock classes to be added to the generated library. final mockClasses = []; @@ -97,7 +108,8 @@ class _MockLibraryInfo { /// Build mock classes for [classesToMock], a list of classes obtained from a /// `@GenerateMocks` annotation. - _MockLibraryInfo(List classesToMock) { + _MockLibraryInfo(List classesToMock, + {this.sourceLibIsNonNullable, this.typeSystem}) { for (final classToMock in classesToMock) { final dartTypeToMock = classToMock.toTypeValue(); if (dartTypeToMock == null) { @@ -196,10 +208,20 @@ class _MockLibraryInfo { return true; } - // TODO(srawlins): Update this logic to correctly handle non-nullable return - // types. Right now this logic does not seem to be available on DartType. + // Returns whether [method] has at least one parameter whose type is + // potentially non-nullable. + // + // A parameter whose type uses a type variable may be non-nullable on certain + // instances. For example: + // + // class C { + // void m(T a) {} + // } + // final c1 = C(); // m's parameter's type is nullable. + // final c2 = C(); // m's parameter's type is non-nullable. bool _hasNonNullableParameter(MethodElement method) => - method.parameters.isNotEmpty; + sourceLibIsNonNullable && + method.parameters.any((p) => typeSystem.isPotentiallyNonNullable(p.type)); /// Build a method which overrides [method], with all non-nullable /// parameter types widened to be nullable. @@ -207,7 +229,9 @@ class _MockLibraryInfo { /// This new method just calls `super.noSuchMethod`, optionally passing a /// return value for methods with a non-nullable return type. // TODO(srawlins): This method does no widening yet. Widen parameters. Include - // tests for typedefs, old-style function parameters, and function types. + // tests for typedefs, old-style function parameters, function types, type + // variables, non-nullable type variables (bounded to Object, I think), + // dynamic. // TODO(srawlins): This method declares no specific non-null return values // yet. void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { @@ -437,6 +461,9 @@ class _MockLibraryInfo { return TypeReference((TypeReferenceBuilder b) { b ..symbol = type.name + // Using the `nullabilitySuffix` rather than `TypeSystem.isNullable` + // is more correct for types like `dynamic`. + ..isNullable = type.nullabilitySuffix == NullabilitySuffix.question ..url = _typeImport(type) ..types.addAll(type.typeArguments.map(_typeReference)); }); diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index f67137e95..a5d58947e 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -14,9 +14,11 @@ @TestOn('vm') import 'package:build/build.dart'; +import 'package:build/experiments.dart'; import 'package:build_test/build_test.dart'; import 'package:meta/meta.dart'; import 'package:mockito/src/builder.dart'; +import 'package:package_config/package_config.dart'; import 'package:test/test.dart'; Builder buildMocks(BuilderOptions options) => MockBuilder(); @@ -44,8 +46,7 @@ void main() { test( 'generates mock for an imported class but does not override private ' 'or static methods or methods w/ zero parameters', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -74,8 +75,7 @@ void main() { test( 'generates mock for an imported class but does not override private ' 'or static fields', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -103,8 +103,7 @@ void main() { test( 'generates mock for an imported class but does not override any ' 'extension methods', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -135,8 +134,7 @@ void main() { }); test('generates a mock class and overrides methods parameters', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -181,8 +179,7 @@ void main() { }); test('generates multiple mock classes', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -226,8 +223,7 @@ void main() { }); test('generates generic mock classes', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -259,8 +255,7 @@ void main() { }); test('generates generic mock classes with type bounds', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -302,17 +297,15 @@ void main() { }); test('writes non-interface types w/o imports', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, 'foo|lib/foo.dart': dedent(r''' - import 'dart:async'; class Foo { - void f(dynamic a) {} - void g(T b) {} - void h(U c) {} + void f(dynamic a, int b) {} + void g(T c) {} + void h(U d) {} } '''), }, @@ -325,9 +318,9 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - void f(dynamic a) => super.noSuchMethod(Invocation.method(#f, [a])); - void g(T b) => super.noSuchMethod(Invocation.method(#g, [b])); - void h(U c) => super.noSuchMethod(Invocation.method(#h, [c])); + void f(dynamic a, int b) => super.noSuchMethod(Invocation.method(#f, [a, b])); + void g(T c) => super.noSuchMethod(Invocation.method(#g, [c])); + void h(U d) => super.noSuchMethod(Invocation.method(#h, [d])); } '''), }, @@ -335,8 +328,7 @@ void main() { }); test('imports libraries for external class types', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -365,8 +357,7 @@ void main() { }); test('imports libraries for type aliases with external types', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -402,8 +393,7 @@ void main() { }); test('imports libraries for function types with external types', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -446,9 +436,86 @@ void main() { ); }); + test('correctly matches nullability of parameters', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + void f(int? a, int b); + void g(List a, List b); + void h(int? Function() a, int Function() b); + void i(void Function(int?) a, void Function(int) b); + void j(int? a(), int b()); + void k(void a(int? x), void b(int x)); + void l(T? a, T b); + } + '''), + }, + outputs: { + // TODO(srawlins): The type of l's first parameter should be `T?`. + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + void f(int? a, int b) => super.noSuchMethod(Invocation.method(#f, [a, b])); + void g(List a, List b) => + super.noSuchMethod(Invocation.method(#g, [a, b])); + void h(int? Function() a, int Function() b) => + super.noSuchMethod(Invocation.method(#h, [a, b])); + void i(void Function(int?) a, void Function(int) b) => + super.noSuchMethod(Invocation.method(#i, [a, b])); + void j(int? Function() a, int Function() b) => + super.noSuchMethod(Invocation.method(#j, [a, b])); + void k(void Function(int?) a, void Function(int) b) => + super.noSuchMethod(Invocation.method(#k, [a, b])); + void l(T a, T b) => super.noSuchMethod(Invocation.method(#l, [a, b])); + } + '''), + }, + ); + }); + + test('correctly matches nullability of return types', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + int f(); + int? g(); + List h(); + List i(); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + int f() => super.noSuchMethod(Invocation.method(#f, []), 0); + int? g() => super.noSuchMethod(Invocation.method(#g, []), 0); + List h() => super.noSuchMethod(Invocation.method(#h, []), []); + List i() => super.noSuchMethod(Invocation.method(#i, []), []); + } + '''), + }, + ); + }); + test('overrides abstract methods', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -475,9 +542,62 @@ void main() { ); }); + test('does not override methods with all nullable parameters', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void a(int? m) {} + void b(dynamic n) {} + void c(int Function()? o) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo {} + '''), + }, + ); + }); + + test('overrides methods with a potentially non-nullable parameter', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void a(T m) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + void a(T m) => super.noSuchMethod(Invocation.method(#a, [m])); + } + '''), + }, + ); + }); + test('overrides generic methods', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -509,8 +629,7 @@ void main() { }); test('overrides getters and setters', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -541,8 +660,7 @@ void main() { }); test('overrides operators', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -574,8 +692,7 @@ void main() { }); test('creates dummy non-null return values for known core classes', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -615,8 +732,7 @@ void main() { test('creates dummy non-null return values for Futures of known core classes', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -645,8 +761,7 @@ void main() { }); test('creates dummy non-null return values for unknown classes', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -679,8 +794,7 @@ void main() { }); test('deduplicates fake classes', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -715,8 +829,7 @@ void main() { }); test('creates dummy non-null return values for enums', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -747,8 +860,7 @@ void main() { }); test('creates dummy non-null return values for functions', () async { - await testBuilder( - buildMocks(BuilderOptions({})), + await _testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -844,6 +956,65 @@ void main() { message: 'The "classes" argument includes an enum: Foo', ); }); + + test('given a pre-non-nullable library, does not override any members', + () async { + await _testPreNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + dynamic f(int a) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo {} + '''), + }, + ); + }); +} + +/// Test [MockBuilder] in a package which has not opted into the non-nullable +/// type system. +/// +/// Whether the non-nullable experiment is enabled depends on the SDK executing +/// this test, but that does not affect the opt-in state of the package under +/// test. +Future _testPreNonNullable(Map sourceAssets, + {Map*/ dynamic> outputs}) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 7)) + ]); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + outputs: outputs, packageConfig: packageConfig); +} + +/// Test [MockBuilder] in a package which has opted into the non-nullable type +/// system, and with the non-nullable experiment enabled. +Future _testWithNonNullable(Map sourceAssets, + {Map*/ dynamic> outputs}) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 9)) + ]); + await withEnabledExperiments( + () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + outputs: outputs, packageConfig: packageConfig), + ['non-nullable'], + ); } /// Expect that [testBuilder], given [assets], throws an From 8bdf810ee5f79bba453dc2c630dcd92455b1bd4d Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 4 Jun 2020 16:25:23 -0400 Subject: [PATCH 189/607] MockBuilder: Fix a few nullability bugs. Methods should not be overridden unless they have a non-nullable return type, or at least one non-nullable parameter type. Previously, there was a complicated, pre-non-nullability logic to determine this for methods. This has been fixed to simply ask the type system. Type variables are now correctly suffixed with a `?` in order to match the source type annotation; the _typeReference logic now handles TypeParameterTypes. Stop using NullabilitySuffix; the TypeSystem is just a better way to handle question marks. dynamic can be handled separately. PiperOrigin-RevId: 314792190 --- pkgs/mockito/lib/src/builder.dart | 50 ++++++++++----------- pkgs/mockito/test/builder_test.dart | 68 ++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index ff878f354..99a2a3fe4 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -14,7 +14,6 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:analyzer/dart/element/type_system.dart'; import 'package:build/build.dart'; @@ -167,6 +166,12 @@ class _MockLibraryInfo { ..url = _typeImport(dartType) ..types.addAll(typeArguments); })); + + // Only override members of a class declared in a library which uses the + // non-nullable type system. + if (!sourceLibIsNonNullable) { + return; + } for (final field in classToMock.fields) { if (field.isPrivate || field.isStatic) { continue; @@ -191,22 +196,8 @@ class _MockLibraryInfo { }); } - // TODO(srawlins): Update this logic to correctly handle non-nullable return - // types. Right now this logic does not seem to be available on DartType. - bool _returnTypeIsNonNullable(MethodElement method) { - var type = method.returnType; - if (type.isDynamic || type.isVoid) return false; - if (method.isAsynchronous && type.isDartAsyncFuture || - type.isDartAsyncFutureOr) { - var typeArgument = (type as analyzer.InterfaceType).typeArguments.first; - if (typeArgument.isDynamic || typeArgument.isVoid) { - // An asynchronous method which returns `Future`, for example, - // does not need a dummy return value. - return false; - } - } - return true; - } + bool _returnTypeIsNonNullable(MethodElement method) => + typeSystem.isPotentiallyNonNullable(method.returnType); // Returns whether [method] has at least one parameter whose type is // potentially non-nullable. @@ -220,7 +211,6 @@ class _MockLibraryInfo { // final c1 = C(); // m's parameter's type is nullable. // final c2 = C(); // m's parameter's type is non-nullable. bool _hasNonNullableParameter(MethodElement method) => - sourceLibIsNonNullable && method.parameters.any((p) => typeSystem.isPotentiallyNonNullable(p.type)); /// Build a method which overrides [method], with all non-nullable @@ -274,8 +264,7 @@ class _MockLibraryInfo { // TODO(srawlins): Optionally pass a non-null return value to `noSuchMethod` // which `Mock.noSuchMethod` will simply return, in order to satisfy runtime // type checks. - // TODO(srawlins): Handle getter invocations with `Invocation.getter`, - // and operators??? + // TODO(srawlins): Handle getter invocations with `Invocation.getter`. final invocation = refer('Invocation').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), @@ -453,17 +442,16 @@ class _MockLibraryInfo { /// * InterfaceTypes (classes, generic classes), /// * FunctionType parameters (like `void callback(int i)`), /// * type aliases (typedefs), both new- and old-style, - /// * enums. + /// * enums, + /// * type variables. // TODO(srawlins): Contribute this back to a common location, like // package:source_gen? Reference _typeReference(analyzer.DartType type) { if (type is analyzer.InterfaceType) { - return TypeReference((TypeReferenceBuilder b) { + return TypeReference((b) { b ..symbol = type.name - // Using the `nullabilitySuffix` rather than `TypeSystem.isNullable` - // is more correct for types like `dynamic`. - ..isNullable = type.nullabilitySuffix == NullabilitySuffix.question + ..isNullable = typeSystem.isPotentiallyNullable(type) ..url = _typeImport(type) ..types.addAll(type.typeArguments.map(_typeReference)); }); @@ -483,15 +471,21 @@ class _MockLibraryInfo { } }); } - return TypeReference((TypeReferenceBuilder trBuilder) { + return TypeReference((b) { var typedef = element.enclosingElement; - trBuilder + b ..symbol = typedef.name ..url = _typeImport(type); for (var typeArgument in type.typeArguments) { - trBuilder.types.add(_typeReference(typeArgument)); + b.types.add(_typeReference(typeArgument)); } }); + } else if (type is analyzer.TypeParameterType) { + return TypeReference((b) { + b + ..symbol = type.name + ..isNullable = typeSystem.isNullable(type); + }); } else { return refer(type.displayName, _typeImport(type)); } diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index a5d58947e..aa79a522b 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -133,7 +133,7 @@ void main() { ); }); - test('generates a mock class and overrides methods parameters', () async { + test('generates a mock class and overrides method parameters', () async { await _testWithNonNullable( { ...annotationsAsset, @@ -171,7 +171,7 @@ void main() { void d(String one, {String two, String three = ""}) => super .noSuchMethod(Invocation.method(#d, [one], {#two: two, #three: three})); _i3.Future e(String s) async => - super.noSuchMethod(Invocation.method(#e, [s])); + super.noSuchMethod(Invocation.method(#e, [s]), Future.value(null)); } '''), }, @@ -450,11 +450,11 @@ void main() { void j(int? a(), int b()); void k(void a(int? x), void b(int x)); void l(T? a, T b); + void m(dynamic a, int b); } '''), }, outputs: { - // TODO(srawlins): The type of l's first parameter should be `T?`. 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; import 'package:foo/foo.dart' as _i2; @@ -474,7 +474,8 @@ void main() { super.noSuchMethod(Invocation.method(#j, [a, b])); void k(void Function(int?) a, void Function(int) b) => super.noSuchMethod(Invocation.method(#k, [a, b])); - void l(T a, T b) => super.noSuchMethod(Invocation.method(#l, [a, b])); + void l(T? a, T b) => super.noSuchMethod(Invocation.method(#l, [a, b])); + void m(dynamic a, int b) => super.noSuchMethod(Invocation.method(#m, [a, b])); } '''), }, @@ -488,10 +489,12 @@ void main() { ...simpleTestAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { - int f(); - int? g(); - List h(); - List i(); + int f(int a); + int? g(int a); + List h(int a); + List i(int a); + j(int a); + T? k(int a); } '''), }, @@ -504,10 +507,12 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - int f() => super.noSuchMethod(Invocation.method(#f, []), 0); - int? g() => super.noSuchMethod(Invocation.method(#g, []), 0); - List h() => super.noSuchMethod(Invocation.method(#h, []), []); - List i() => super.noSuchMethod(Invocation.method(#i, []), []); + int f(int a) => super.noSuchMethod(Invocation.method(#f, [a]), 0); + int? g(int a) => super.noSuchMethod(Invocation.method(#g, [a])); + List h(int a) => super.noSuchMethod(Invocation.method(#h, [a]), []); + List i(int a) => super.noSuchMethod(Invocation.method(#i, [a]), []); + dynamic j(int a) => super.noSuchMethod(Invocation.method(#j, [a])); + T? k(int a) => super.noSuchMethod(Invocation.method(#k, [a])); } '''), }, @@ -549,9 +554,40 @@ void main() { ...simpleTestAsset, 'foo|lib/foo.dart': dedent(r''' class Foo { - void a(int? m) {} - void b(dynamic n) {} - void c(int Function()? o) {} + void a(int? p) {} + void b(dynamic p) {} + void c(var p) {} + void d(final p) {} + void e(int Function()? p) {} + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo {} + '''), + }, + ); + }); + + test('does not override methods with a nullable return type', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + void a(); + b(); + dynamic c(); + void d(); + int? f(); } '''), }, @@ -965,7 +1001,7 @@ void main() { ...simpleTestAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { - dynamic f(int a) {} + int f(int a); } '''), }, From a12864aa3c873f53e5f9dd849c6e6348debe568e Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 5 Jun 2020 00:08:36 -0400 Subject: [PATCH 190/607] MockBuilder: Simplify test cases I recently discovered that testBuilder can accept Matchers for its outputs. This makes it possible to not pass in whole-library verifications. I've written a nice wrapper, so that we only verify the lines we care about. PiperOrigin-RevId: 314859743 --- pkgs/mockito/test/builder_test.dart | 390 +++++++++++++++------------- 1 file changed, 204 insertions(+), 186 deletions(-) diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index aa79a522b..37c6188df 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -727,207 +727,203 @@ void main() { ); }); - test('creates dummy non-null return values for known core classes', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - bool m1() => false; - double m2() => 3.14; - int m3() => 7; - String m4() => "Hello"; - List m5() => [Foo()]; - Set m6() => {Foo()}; - Map m7() => {7: Foo()}; - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('creates dummy non-null bool return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + bool m() => false; + } + '''), + _containsAllOf( + 'bool m() => super.noSuchMethod(Invocation.method(#m, []), false);'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - bool m1() => super.noSuchMethod(Invocation.method(#m1, []), false); - double m2() => super.noSuchMethod(Invocation.method(#m2, []), 0.0); - int m3() => super.noSuchMethod(Invocation.method(#m3, []), 0); - String m4() => super.noSuchMethod(Invocation.method(#m4, []), ''); - List<_i2.Foo> m5() => super.noSuchMethod(Invocation.method(#m5, []), []); - Set<_i2.Foo> m6() => super.noSuchMethod(Invocation.method(#m6, []), {}); - Map m7() => super.noSuchMethod(Invocation.method(#m7, []), {}); - } - '''), - }, + test('creates dummy non-null double return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + double m() => 3.14; + } + '''), + _containsAllOf( + 'double m() => super.noSuchMethod(Invocation.method(#m, []), 0.0);'), ); }); - test('creates dummy non-null return values for Futures of known core classes', - () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - Future m1() async => false; - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - import 'dart:async' as _i3; + test('creates dummy non-null int return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int m() => 7; + } + '''), + _containsAllOf( + 'int m() => super.noSuchMethod(Invocation.method(#m, []), 0);'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - _i3.Future m1() async => - super.noSuchMethod(Invocation.method(#m1, []), Future.value(false)); - } - '''), - }, + test('creates dummy non-null String return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + String m() => "Hello"; + } + '''), + _containsAllOf( + "String m() => super.noSuchMethod(Invocation.method(#m, []), '');"), ); }); - test('creates dummy non-null return values for unknown classes', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - Bar m1() => Bar('name'); - } - class Bar { - final String name; - Bar(this.name); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('creates dummy non-null List return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + List m() => [Foo()]; + } + '''), + _containsAllOf( + 'List<_i2.Foo> m() => super.noSuchMethod(Invocation.method(#m, []), []);'), + ); + }); - class _FakeBar extends _i1.Fake implements _i2.Bar {} + test('creates dummy non-null Set return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Set m() => {Foo()}; + } + '''), + _containsAllOf( + 'Set<_i2.Foo> m() => super.noSuchMethod(Invocation.method(#m, []), {});'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); - } - '''), - }, + test('creates dummy non-null Map return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Map m() => {7: Foo()}; + } + '''), + _containsAllOf( + 'Map m() => super.noSuchMethod(Invocation.method(#m, []), {});'), ); }); - test('deduplicates fake classes', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - Bar m1() => Bar('name1'); - Bar m2() => Bar('name2'); - } - class Bar { - final String name; - Bar(this.name); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('creates dummy non-null return values for Futures of known core classes', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Future m1() async => false; + } + '''), + _containsAllOf('_i3.Future m1() async =>', + 'super.noSuchMethod(Invocation.method(#m1, []), Future.value(false));'), + ); + }); - class _FakeBar extends _i1.Fake implements _i2.Bar {} + test('creates dummy non-null return values for unknown classes', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Bar m() => Bar('name'); + } + class Bar { + final String name; + Bar(this.name); + } + '''), + _containsAllOf( + '_i2.Bar m() => super.noSuchMethod(Invocation.method(#m, []), _FakeBar());'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); - _i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar()); - } - '''), - }, + test('deduplicates fake classes', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Bar m1() => Bar('name1'); + Bar m2() => Bar('name2'); + } + class Bar { + final String name; + Bar(this.name); + } + '''), + dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + class _FakeBar extends _i1.Fake implements _i2.Bar {} + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); + _i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar()); + } + '''), ); }); test('creates dummy non-null return values for enums', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - Bar m1() => Bar('name'); - } - enum Bar { - one, - two, - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _i2.Bar.one); - } - '''), - }, + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Bar m1() => Bar('name'); + } + enum Bar { + one, + two, + } + '''), + _containsAllOf( + '_i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _i2.Bar.one);'), ); }); - test('creates dummy non-null return values for functions', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - void Function(int, [String]) m1() => (int i, [String s]) {}; - void Function(Foo, {bool b}) m2() => (Foo f, {bool b}) {}; - Foo Function() m3() => () => Foo(); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test( + 'creates a dummy non-null return function-typed value, with optional ' + 'parameters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void Function(int, [String]) m() => (int i, [String s]) {}; + } + '''), + _containsAllOf('void Function(int, [String]) m() => super', + '.noSuchMethod(Invocation.method(#m, []), (int __p0, [String __p1]) {});'), + ); + }); - class _FakeFoo extends _i1.Fake implements _i2.Foo {} + test( + 'creates a dummy non-null return function-typed value, with named ' + 'parameters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void Function(Foo, {bool b}) m() => (Foo f, {bool b}) {}; + } + '''), + _containsAllOf('void Function(_i2.Foo, {bool b}) m() => super', + '.noSuchMethod(Invocation.method(#m, []), (_i2.Foo __p0, {bool b}) {});'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - void Function(int, [String]) m1() => super - .noSuchMethod(Invocation.method(#m1, []), (int __p0, [String __p1]) {}); - void Function(_i2.Foo, {bool b}) m2() => super - .noSuchMethod(Invocation.method(#m2, []), (_i2.Foo __p0, {bool b}) {}); - _i2.Foo Function() m3() => - super.noSuchMethod(Invocation.method(#m3, []), () => _FakeFoo()); - } - '''), - }, + test( + 'creates a dummy non-null return function-typed value, with non-core ' + 'return type', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Foo Function() m() => () => Foo(); + } + '''), + _containsAllOf('_i2.Foo Function() m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), () => _FakeFoo());'), ); }); @@ -1006,15 +1002,8 @@ void main() { '''), }, outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo {} - '''), + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), }, ); }); @@ -1053,6 +1042,35 @@ Future _testWithNonNullable(Map sourceAssets, ); } +/// Test [MockBuilder] on a single source file, in a package which has opted +/// into the non-nullable type system, and with the non-nullable experiment +/// enabled. +Future _expectSingleNonNullableOutput( + String sourceAssetText, + /*String|Matcher*/ dynamic output) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 9)) + ]); + + await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': sourceAssetText, + }, + outputs: {'foo|test/foo_test.mocks.dart': output}, + packageConfig: packageConfig), + ['non-nullable'], + ); +} + +TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( + b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); + /// Expect that [testBuilder], given [assets], throws an /// [InvalidMockitoAnnotationException] with a message containing [message]. void expectBuilderThrows( From d9f5b34e1dbe372a93961576afc7155605b74b3e Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 5 Jun 2020 19:14:38 -0400 Subject: [PATCH 191/607] MockBuilder: Override getters; correct bugs in overriding setters. Getter support is added here, and only public non-nullable instance getters are overridden. Setter support is improved; previously all public instance setters were overridden. This change ensures that only non-nullable setters are overridden. Small tests are added for fields, final fields, getters, and setters. PiperOrigin-RevId: 315014983 --- pkgs/mockito/lib/src/builder.dart | 40 +++++-- pkgs/mockito/test/builder_test.dart | 156 ++++++++++++++++++---------- 2 files changed, 131 insertions(+), 65 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 99a2a3fe4..c63746269 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -176,9 +176,13 @@ class _MockLibraryInfo { if (field.isPrivate || field.isStatic) { continue; } - // Handle getters when we handle non-nullable return types. + final getter = field.getter; + if (getter != null && _returnTypeIsNonNullable(getter)) { + cBuilder.methods.add( + Method((mBuilder) => _buildOverridingGetter(mBuilder, getter))); + } final setter = field.setter; - if (setter != null) { + if (setter != null && _hasNonNullableParameter(setter)) { cBuilder.methods.add( Method((mBuilder) => _buildOverridingSetter(mBuilder, setter))); } @@ -196,7 +200,7 @@ class _MockLibraryInfo { }); } - bool _returnTypeIsNonNullable(MethodElement method) => + bool _returnTypeIsNonNullable(ExecutableElement method) => typeSystem.isPotentiallyNonNullable(method.returnType); // Returns whether [method] has at least one parameter whose type is @@ -210,7 +214,7 @@ class _MockLibraryInfo { // } // final c1 = C(); // m's parameter's type is nullable. // final c2 = C(); // m's parameter's type is non-nullable. - bool _hasNonNullableParameter(MethodElement method) => + bool _hasNonNullableParameter(ExecutableElement method) => method.parameters.any((p) => typeSystem.isPotentiallyNonNullable(p.type)); /// Build a method which overrides [method], with all non-nullable @@ -222,8 +226,6 @@ class _MockLibraryInfo { // tests for typedefs, old-style function parameters, function types, type // variables, non-nullable type variables (bounded to Object, I think), // dynamic. - // TODO(srawlins): This method declares no specific non-null return values - // yet. void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { // TODO(srawlins): generator methods like async*, sync*. var name = method.displayName; @@ -261,10 +263,7 @@ class _MockLibraryInfo { refer(parameter.displayName); } } - // TODO(srawlins): Optionally pass a non-null return value to `noSuchMethod` - // which `Mock.noSuchMethod` will simply return, in order to satisfy runtime - // type checks. - // TODO(srawlins): Handle getter invocations with `Invocation.getter`. + final invocation = refer('Invocation').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), @@ -392,6 +391,27 @@ class _MockLibraryInfo { }); } + /// Build a getter which overrides [getter]. + /// + /// This new method just calls `super.noSuchMethod`, optionally passing a + /// return value for non-nullable getters. + void _buildOverridingGetter( + MethodBuilder builder, PropertyAccessorElement getter) { + builder + ..name = getter.displayName + ..type = MethodType.getter + ..returns = _typeReference(getter.returnType); + + final invocation = refer('Invocation').property('getter').call([ + refer('#${getter.displayName}'), + ]); + final noSuchMethodArgs = [invocation, _dummyValue(getter.returnType)]; + final returnNoSuchMethod = + refer('super').property('noSuchMethod').call(noSuchMethodArgs); + + builder.body = returnNoSuchMethod.code; + } + /// Build a setter which overrides [setter], widening the single parameter /// type to be nullable if it is non-nullable. /// diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 37c6188df..fb550e86b 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -72,34 +72,6 @@ void main() { ); }); - test( - 'generates mock for an imported class but does not override private ' - 'or static fields', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - int _a; - static int b; - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo {} - '''), - }, - ); - }); - test( 'generates mock for an imported class but does not override any ' 'extension methods', () async { @@ -664,34 +636,108 @@ void main() { ); }); - test('overrides getters and setters', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - int get a => n + 1; - int _b; - set b(int value) => _b = value; - } - '''), - }, - outputs: { - // TODO(srawlins): The getter will appear when it has a non-nullable - // return type. - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('overrides non-nullable instance getters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int get m => 7; + } + '''), + _containsAllOf( + 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - set b(int value) => super.noSuchMethod(Invocation.setter(#b, [value])); - } - '''), - }, + test('does not override nullable instance getters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int? get m => 7; + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); + + test('overrides non-nullable instance setters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void set m(int a) {} + } + '''), + _containsAllOf( + 'set m(int a) => super.noSuchMethod(Invocation.setter(#m, [a]));'), + ); + }); + + test('does not override nullable instance setters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void set m(int? a) {} + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); + + test('overrides non-nullable fields', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int m; + } + '''), + _containsAllOf( + 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);', + 'set m(int _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), + ); + }); + + test('overrides final non-nullable fields', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + final int m; + Foo(this.m); + } + '''), + _containsAllOf( + 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);'), + ); + }); + + test('does not override nullable fields', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int? m; + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); + + test('does not override private fields', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int _a; + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); + + test('does not override static fields', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + static int b; + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), ); }); From 999335f25606205ed683a9107b8f5375b41bf9b6 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 8 Jun 2020 12:59:45 -0400 Subject: [PATCH 192/607] MockBuilder: Widen the type of non-nullable parameters to be nullable. The mechanism for doing this is to add some `forceNullable` parameters here and there, to ensure that overriding method parameters and overriding setter parameters are nullable. Other parameters, like those of function-typed parameters or generic function types, are not widened. Include plenty of tests, and update all test expectations to now include widened parameters. Update a few tests as well to be simpler, including only relevant content, and testing only relevant content. PiperOrigin-RevId: 315296150 --- pkgs/mockito/lib/src/builder.dart | 42 +-- pkgs/mockito/test/builder_test.dart | 382 ++++++++++++++-------------- 2 files changed, 225 insertions(+), 199 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index c63746269..a543527cc 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -222,10 +222,8 @@ class _MockLibraryInfo { /// /// This new method just calls `super.noSuchMethod`, optionally passing a /// return value for methods with a non-nullable return type. - // TODO(srawlins): This method does no widening yet. Widen parameters. Include - // tests for typedefs, old-style function parameters, function types, type - // variables, non-nullable type variables (bounded to Object, I think), - // dynamic. + // TODO(srawlins): Include widening tests for typedefs, old-style function + // parameters, function types. void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { // TODO(srawlins): generator methods like async*, sync*. var name = method.displayName; @@ -252,13 +250,16 @@ class _MockLibraryInfo { for (final parameter in method.parameters) { if (parameter.isRequiredPositional) { - builder.requiredParameters.add(_matchingParameter(parameter)); + builder.requiredParameters + .add(_matchingParameter(parameter, forceNullable: true)); invocationPositionalArgs.add(refer(parameter.displayName)); } else if (parameter.isOptionalPositional) { - builder.optionalParameters.add(_matchingParameter(parameter)); + builder.optionalParameters + .add(_matchingParameter(parameter, forceNullable: true)); invocationPositionalArgs.add(refer(parameter.displayName)); } else if (parameter.isNamed) { - builder.optionalParameters.add(_matchingParameter(parameter)); + builder.optionalParameters + .add(_matchingParameter(parameter, forceNullable: true)); invocationNamedArgs[refer('#${parameter.displayName}')] = refer(parameter.displayName); } @@ -377,13 +378,19 @@ class _MockLibraryInfo { } /// Returns a [Parameter] which matches [parameter]. + /// + /// If [parameter] is unnamed (like a positional parameter in a function + /// type), a [defaultName] can be passed as the name. + /// + /// If the type needs to be nullable, rather than matching the nullability of + /// [parameter], use [forceNullable]. Parameter _matchingParameter(ParameterElement parameter, - {String defaultName}) { + {String defaultName, bool forceNullable = false}) { var name = parameter.name?.isEmpty ?? false ? defaultName : parameter.name; return Parameter((pBuilder) { pBuilder ..name = name - ..type = _typeReference(parameter.type); + ..type = _typeReference(parameter.type, forceNullable: forceNullable); if (parameter.isNamed) pBuilder.named = true; if (parameter.defaultValueCode != null) { pBuilder.defaultTo = Code(parameter.defaultValueCode); @@ -416,7 +423,6 @@ class _MockLibraryInfo { /// type to be nullable if it is non-nullable. /// /// This new setter just calls `super.noSuchMethod`. - // TODO(srawlins): This method does no widening yet. void _buildOverridingSetter( MethodBuilder builder, PropertyAccessorElement setter) { builder @@ -430,7 +436,7 @@ class _MockLibraryInfo { if (parameter.isRequiredPositional) { builder.requiredParameters.add(Parameter((pBuilder) => pBuilder ..name = parameter.displayName - ..type = _typeReference(parameter.type))); + ..type = _typeReference(parameter.type, forceNullable: true))); invocationPositionalArgs.add(refer(parameter.displayName)); } } @@ -458,6 +464,9 @@ class _MockLibraryInfo { /// Create a reference for [type], properly referencing all attached types. /// + /// If the type needs to be nullable, rather than matching the nullability of + /// [type], use [forceNullable]. + /// /// This creates proper references for: /// * InterfaceTypes (classes, generic classes), /// * FunctionType parameters (like `void callback(int i)`), @@ -466,12 +475,13 @@ class _MockLibraryInfo { /// * type variables. // TODO(srawlins): Contribute this back to a common location, like // package:source_gen? - Reference _typeReference(analyzer.DartType type) { + Reference _typeReference(analyzer.DartType type, + {bool forceNullable = false}) { if (type is analyzer.InterfaceType) { return TypeReference((b) { b ..symbol = type.name - ..isNullable = typeSystem.isPotentiallyNullable(type) + ..isNullable = forceNullable || typeSystem.isPotentiallyNullable(type) ..url = _typeImport(type) ..types.addAll(type.typeArguments.map(_typeReference)); }); @@ -481,6 +491,7 @@ class _MockLibraryInfo { // [type] represents a FunctionTypedFormalParameter. return FunctionType((b) { b + // TODO(srawlins): Fix FunctionType to take an `isNullable` value. ..returnType = _typeReference(type.returnType) ..requiredParameters .addAll(type.normalParameterTypes.map(_typeReference)) @@ -495,7 +506,8 @@ class _MockLibraryInfo { var typedef = element.enclosingElement; b ..symbol = typedef.name - ..url = _typeImport(type); + ..url = _typeImport(type) + ..isNullable = forceNullable || typeSystem.isNullable(type); for (var typeArgument in type.typeArguments) { b.types.add(_typeReference(typeArgument)); } @@ -504,7 +516,7 @@ class _MockLibraryInfo { return TypeReference((b) { b ..symbol = type.name - ..isNullable = typeSystem.isNullable(type); + ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { return refer(type.displayName, _typeImport(type)); diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index fb550e86b..11273eeac 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -83,9 +83,7 @@ void main() { extension X on Foo { dynamic x(int m, String n) => n + 1; } - class Foo { - dynamic a(int m, String n) => n + 1; - } + class Foo {} '''), }, outputs: { @@ -96,10 +94,7 @@ void main() { /// A class which mocks [Foo]. /// /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic a(int m, String n) => - super.noSuchMethod(Invocation.method(#a, [m, n])); - } + class MockFoo extends _i1.Mock implements _i2.Foo {} '''), }, ); @@ -134,15 +129,15 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic a(int m, String n) => + dynamic a(int? m, String? n) => super.noSuchMethod(Invocation.method(#a, [m, n])); - dynamic b(List list) => + dynamic b(List? list) => super.noSuchMethod(Invocation.method(#b, [list])); - void c(String one, [String two, String three = ""]) => + void c(String? one, [String? two, String? three = ""]) => super.noSuchMethod(Invocation.method(#c, [one, two, three])); - void d(String one, {String two, String three = ""}) => super + void d(String? one, {String? two, String? three = ""}) => super .noSuchMethod(Invocation.method(#d, [one], {#two: two, #three: three})); - _i3.Future e(String s) async => + _i3.Future e(String? s) async => super.noSuchMethod(Invocation.method(#e, [s]), Future.value(null)); } '''), @@ -155,12 +150,8 @@ void main() { { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' - class Foo { - dynamic a(int m, String n) => n + 1; - } - class Bar { - dynamic b(List list) => list.length; - } + class Foo {} + class Bar {} '''), 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; @@ -170,26 +161,10 @@ void main() { ''' }, outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic a(int m, String n) => - super.noSuchMethod(Invocation.method(#a, [m, n])); - } - - /// A class which mocks [Bar]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockBar extends _i1.Mock implements _i2.Bar { - dynamic b(List list) => - super.noSuchMethod(Invocation.method(#b, [list])); - } - '''), + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + 'class MockBar extends _i1.Mock implements _i2.Bar {}', + ), }, ); }); @@ -199,9 +174,7 @@ void main() { { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' - class Foo { - dynamic a(int m) => m + 1; - } + class Foo {} '''), 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; @@ -211,17 +184,8 @@ void main() { ''' }, outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic a(int m) => super.noSuchMethod(Invocation.method(#a, [m])); - } - '''), + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), }, ); }); @@ -231,12 +195,8 @@ void main() { { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' - class Foo { - dynamic a(int m) => m + 1; - } - class Bar { - dynamic b(int m) => m + 1; - } + class Foo {} + class Bar {} '''), 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; @@ -246,24 +206,10 @@ void main() { ''' }, outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic a(int m) => super.noSuchMethod(Invocation.method(#a, [m])); - } - - /// A class which mocks [Bar]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockBar extends _i1.Mock implements _i2.Bar { - dynamic b(int m) => super.noSuchMethod(Invocation.method(#b, [m])); - } - '''), + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + 'class MockBar extends _i1.Mock implements _i2.Bar {}', + ), }, ); }); @@ -290,9 +236,10 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - void f(dynamic a, int b) => super.noSuchMethod(Invocation.method(#f, [a, b])); - void g(T c) => super.noSuchMethod(Invocation.method(#g, [c])); - void h(U d) => super.noSuchMethod(Invocation.method(#h, [d])); + void f(dynamic a, int? b) => + super.noSuchMethod(Invocation.method(#f, [a, b])); + void g(T? c) => super.noSuchMethod(Invocation.method(#g, [c])); + void h(U? d) => super.noSuchMethod(Invocation.method(#h, [d])); } '''), }, @@ -320,7 +267,7 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic f(List<_i2.Foo> list) => + dynamic f(List<_i2.Foo>? list) => super.noSuchMethod(Invocation.method(#f, [list])); } '''), @@ -354,9 +301,9 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic f(_i2.Callback c) => super.noSuchMethod(Invocation.method(#f, [c])); - dynamic g(_i2.Callback2 c) => super.noSuchMethod(Invocation.method(#g, [c])); - dynamic h(_i2.Callback3<_i2.Foo> c) => + dynamic f(_i2.Callback? c) => super.noSuchMethod(Invocation.method(#f, [c])); + dynamic g(_i2.Callback2? c) => super.noSuchMethod(Invocation.method(#g, [c])); + dynamic h(_i2.Callback3<_i2.Foo>? c) => super.noSuchMethod(Invocation.method(#h, [c])); } '''), @@ -408,53 +355,132 @@ void main() { ); }); - test('correctly matches nullability of parameters', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' + test('widens the type of parameters to be nullable', () async { + await _expectSingleNonNullableOutput( + dedent(r''' abstract class Foo { - void f(int? a, int b); - void g(List a, List b); - void h(int? Function() a, int Function() b); - void i(void Function(int?) a, void Function(int) b); - void j(int? a(), int b()); - void k(void a(int? x), void b(int x)); - void l(T? a, T b); - void m(dynamic a, int b); + void m(int? a, int b); } '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + _containsAllOf( + 'void m(int? a, int? b) => super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - void f(int? a, int b) => super.noSuchMethod(Invocation.method(#f, [a, b])); - void g(List a, List b) => - super.noSuchMethod(Invocation.method(#g, [a, b])); - void h(int? Function() a, int Function() b) => - super.noSuchMethod(Invocation.method(#h, [a, b])); - void i(void Function(int?) a, void Function(int) b) => - super.noSuchMethod(Invocation.method(#i, [a, b])); - void j(int? Function() a, int Function() b) => - super.noSuchMethod(Invocation.method(#j, [a, b])); - void k(void Function(int?) a, void Function(int) b) => - super.noSuchMethod(Invocation.method(#k, [a, b])); - void l(T? a, T b) => super.noSuchMethod(Invocation.method(#l, [a, b])); - void m(dynamic a, int b) => super.noSuchMethod(Invocation.method(#m, [a, b])); + test( + 'widens the type of potentially non-nullable type variables to be ' + 'nullable', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(int? a, T b); } '''), - }, + _containsAllOf( + 'void m(int? a, T? b) => super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches nullability of type arguments of a parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(List a, List b); + } + '''), + _containsAllOf('void m(List? a, List? b) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test( + 'matches nullability of return type of a generic function-typed ' + 'parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(int? Function() a, int Function() b); + } + '''), + // `a` and `b` should be nullable. code_builder needs a fix, allowing a + // FunctionTypeBuilder to take an `isNullable` value. + _containsAllOf('void m(int? Function() a, int Function() b) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test( + 'matches nullability of parameter types within a generic function-typed ' + 'parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(void Function(int?) a, void Function(int) b); + } + '''), + // `a` and `b` should be nullable. code_builder needs a fix, allowing a + // FunctionTypeBuilder to take an `isNullable` value. + _containsAllOf('void m(void Function(int?) a, void Function(int) b) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches nullability of return type of a function-typed parameter', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(int? a(), int b()); + } + '''), + // `a` and `b` should be nullable. code_builder needs a fix, allowing a + // FunctionTypeBuilder to take an `isNullable` value. + _containsAllOf('void m(int? Function() a, int Function() b) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test( + 'matches nullability of parameter types within a function-typed ' + 'parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(void a(int? x), void b(int x)); + } + '''), + // `a` and `b` should be nullable. code_builder needs a fix, allowing a + // FunctionTypeBuilder to take an `isNullable` value. + _containsAllOf('void m(void Function(int?) a, void Function(int) b) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches nullability of a generic parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(T? a, T b); + } + '''), + _containsAllOf( + 'void m(T? a, T? b) => super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches nullability of a dynamic parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(dynamic a, int b); + } + '''), + _containsAllOf('void m(dynamic a, int? b) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), ); }); - test('correctly matches nullability of return types', () async { + test('matches nullability of return types', () async { await _testWithNonNullable( { ...annotationsAsset, @@ -479,12 +505,12 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - int f(int a) => super.noSuchMethod(Invocation.method(#f, [a]), 0); - int? g(int a) => super.noSuchMethod(Invocation.method(#g, [a])); - List h(int a) => super.noSuchMethod(Invocation.method(#h, [a]), []); - List i(int a) => super.noSuchMethod(Invocation.method(#i, [a]), []); - dynamic j(int a) => super.noSuchMethod(Invocation.method(#j, [a])); - T? k(int a) => super.noSuchMethod(Invocation.method(#k, [a])); + int f(int? a) => super.noSuchMethod(Invocation.method(#f, [a]), 0); + int? g(int? a) => super.noSuchMethod(Invocation.method(#g, [a])); + List h(int? a) => super.noSuchMethod(Invocation.method(#h, [a]), []); + List i(int? a) => super.noSuchMethod(Invocation.method(#i, [a]), []); + dynamic j(int? a) => super.noSuchMethod(Invocation.method(#j, [a])); + T? k(int? a) => super.noSuchMethod(Invocation.method(#k, [a])); } '''), }, @@ -492,30 +518,14 @@ void main() { }); test('overrides abstract methods', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - dynamic f(int a); - dynamic _g(int a); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic f(int a) => super.noSuchMethod(Invocation.method(#f, [a])); - } - '''), - }, + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + dynamic f(int a); + } + '''), + _containsAllOf( + 'dynamic f(int? a) => super.noSuchMethod(Invocation.method(#f, [a]));'), ); }); @@ -597,7 +607,7 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - void a(T m) => super.noSuchMethod(Invocation.method(#a, [m])); + void a(T? m) => super.noSuchMethod(Invocation.method(#a, [m])); } '''), }, @@ -627,8 +637,8 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic f(int a) => super.noSuchMethod(Invocation.method(#f, [a])); - dynamic g(int a) => + dynamic f(int? a) => super.noSuchMethod(Invocation.method(#f, [a])); + dynamic g(int? a) => super.noSuchMethod(Invocation.method(#g, [a])); } '''), @@ -667,7 +677,7 @@ void main() { } '''), _containsAllOf( - 'set m(int a) => super.noSuchMethod(Invocation.setter(#m, [a]));'), + 'set m(int? a) => super.noSuchMethod(Invocation.setter(#m, [a]));'), ); }); @@ -691,7 +701,7 @@ void main() { '''), _containsAllOf( 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);', - 'set m(int _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), + 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), ); }); @@ -741,35 +751,39 @@ void main() { ); }); - test('overrides operators', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - int _b; - int operator +(Foo other) => _b + other._b; - bool operator ==(Object other) => other is Foo && _b == other._b; - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('overrides binary operators', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int operator +(Foo other) => 7; + } + '''), + _containsAllOf('int operator +(_i2.Foo? other) =>', + 'super.noSuchMethod(Invocation.method(#+, [other]), 0);'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - int operator +(_i2.Foo other) => - super.noSuchMethod(Invocation.method(#+, [other]), 0); - bool operator ==(Object other) => - super.noSuchMethod(Invocation.method(#==, [other]), false); - } - '''), - }, + test('overrides index operators', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int operator [](int x) => 7; + } + '''), + _containsAllOf( + 'int operator [](int? x) => super.noSuchMethod(Invocation.method(#[], [x]), 0);'), + ); + }); + + test('overrides unary operators', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int operator ~() => 7; + } + '''), + _containsAllOf( + 'int operator ~() => super.noSuchMethod(Invocation.method(#~, []), 0);'), ); }); From dd6a9f4d4debd74de0729cd0d64dc01d054a2c06 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 8 Jun 2020 17:46:11 -0400 Subject: [PATCH 193/607] MockBuilder: Do not be concerned with method modifiers. An overriding method and an overridden method need not have matching modifiers. For example, an async method can be overridden by a synchronous method; the modifiers are not part of the signature. Introduce support for returning a simple dummy Iterable (an empty List) and a simple dummy Stream (an empty Stream). Improve existing tests and introduce new tests. PiperOrigin-RevId: 315357440 --- pkgs/mockito/lib/src/builder.dart | 19 +-- pkgs/mockito/test/builder_test.dart | 180 +++++++++++++++++----------- 2 files changed, 122 insertions(+), 77 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a543527cc..e1fe97229 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -15,6 +15,7 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; +import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/dart/element/type_system.dart'; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; @@ -62,6 +63,7 @@ class MockBuilder implements Builder { final mockLibrary = Library((b) { var mockLibraryInfo = _MockLibraryInfo(classesToMock, sourceLibIsNonNullable: sourceLibIsNonNullable, + typeProvider: entryLib.typeProvider, typeSystem: entryLib.typeSystem); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); @@ -89,6 +91,9 @@ class MockBuilder implements Builder { class _MockLibraryInfo { final bool sourceLibIsNonNullable; + /// The type provider which applies to the source library. + final TypeProvider typeProvider; + /// The type system which applies to the source library. final TypeSystem typeSystem; @@ -108,7 +113,7 @@ class _MockLibraryInfo { /// Build mock classes for [classesToMock], a list of classes obtained from a /// `@GenerateMocks` annotation. _MockLibraryInfo(List classesToMock, - {this.sourceLibIsNonNullable, this.typeSystem}) { + {this.sourceLibIsNonNullable, this.typeProvider, this.typeSystem}) { for (final classToMock in classesToMock) { final dartTypeToMock = classToMock.toTypeValue(); if (dartTypeToMock == null) { @@ -225,7 +230,6 @@ class _MockLibraryInfo { // TODO(srawlins): Include widening tests for typedefs, old-style function // parameters, function types. void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { - // TODO(srawlins): generator methods like async*, sync*. var name = method.displayName; if (method.isOperator) name = 'operator$name'; builder @@ -236,13 +240,6 @@ class _MockLibraryInfo { builder.types.addAll(method.typeParameters.map(_typeParameterReference)); } - if (method.isAsynchronous) { - builder.modifier = - method.isGenerator ? MethodModifier.asyncStar : MethodModifier.async; - } else if (method.isGenerator) { - builder.modifier = MethodModifier.syncStar; - } - // These two variables store the arguments that will be passed to the // [Invocation] built for `noSuchMethod`. final invocationPositionalArgs = []; @@ -293,6 +290,8 @@ class _MockLibraryInfo { .call([_dummyValue(typeArgument)]); } else if (type.isDartCoreInt) { return literalNum(0); + } else if (type.isDartCoreIterable) { + return literalList([]); } else if (type.isDartCoreList) { return literalList([]); } else if (type.isDartCoreMap) { @@ -303,6 +302,8 @@ class _MockLibraryInfo { // This is perhaps a dangerous hack. The code, `{}`, is parsed as a Set // literal if it is used in a context which explicitly expects a Set. return literalMap({}); + } else if (type.element?.declaration == typeProvider.streamElement) { + return refer('Stream').property('empty').call([]); } else if (type.isDartCoreString) { return literalString(''); } else { diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 11273eeac..fafa3a134 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -100,48 +100,63 @@ void main() { ); }); - test('generates a mock class and overrides method parameters', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - dynamic a(int m, String n) => n + 1; - dynamic b(List list) => list.length; - void c(String one, [String two, String three = ""]) => print('$one$two$three'); - void d(String one, {String two, String three = ""}) => print('$one$two$three'); - Future e(String s) async => print(s); - // TODO(srawlins): Figure out async*; doesn't work yet. `isGenerator` - // does not appear to be working. - // Stream f(String s) async* => print(s); - // Iterable g(String s) sync* => print(s); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - import 'dart:async' as _i3; + test('overrides methods, matching optional positional parameters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m(int a, [int b, int c = 0]) {} + } + '''), + _containsAllOf('void m(int? a, [int? b, int? c = 0]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b, c]));'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic a(int? m, String? n) => - super.noSuchMethod(Invocation.method(#a, [m, n])); - dynamic b(List? list) => - super.noSuchMethod(Invocation.method(#b, [list])); - void c(String? one, [String? two, String? three = ""]) => - super.noSuchMethod(Invocation.method(#c, [one, two, three])); - void d(String? one, {String? two, String? three = ""}) => super - .noSuchMethod(Invocation.method(#d, [one], {#two: two, #three: three})); - _i3.Future e(String? s) async => - super.noSuchMethod(Invocation.method(#e, [s]), Future.value(null)); - } - '''), - }, + test('overrides methods, matching named parameters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m(int a, {int b, int c = 0}) {} + } + '''), + _containsAllOf('void m(int? a, {int? b, int? c = 0}) =>', + 'super.noSuchMethod(Invocation.method(#m, [a], {#b: b, #c: c}));'), + ); + }); + + test('overrides async methods legally', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Future m() async => print(s); + } + '''), + _containsAllOf('_i3.Future m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), Future.value(null));'), + ); + }); + + test('overrides async* methods legally', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Stream m() async* { yield 7; } + } + '''), + _containsAllOf('_i3.Stream m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), Stream.empty());'), + ); + }); + + test('overrides sync* methods legally', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Iterable m() sync* { yield 7; } + } + '''), + _containsAllOf('Iterable m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), []);'), ); }); @@ -558,32 +573,61 @@ void main() { ); }); + test('does not override methods with a void return type', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void m(); + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); + + test('does not override methods with an implicit dynamic return type', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + m(); + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); + + test('does not override methods with an explicit dynamic return type', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + dynamic m(); + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); + test('does not override methods with a nullable return type', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - void a(); - b(); - dynamic c(); - void d(); - int? f(); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + int? m(); + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo {} - '''), - }, + test('overrides methods with a non-nullable return type', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + int m(); + } + '''), + _containsAllOf( + 'int m() => super.noSuchMethod(Invocation.method(#m, []), 0);'), ); }); @@ -876,11 +920,11 @@ void main() { await _expectSingleNonNullableOutput( dedent(r''' class Foo { - Future m1() async => false; + Future m() async => false; } '''), - _containsAllOf('_i3.Future m1() async =>', - 'super.noSuchMethod(Invocation.method(#m1, []), Future.value(false));'), + _containsAllOf('_i3.Future m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), Future.value(false));'), ); }); From d83db54717547a08dfc6cea5c5cb04cb6e3fc366 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 8 Jun 2020 21:13:07 -0400 Subject: [PATCH 194/607] MockBuilder: Improve bad-path messaging and tests. Add cases for attempted mocking of extensions, and bad GenerateMocks construction. Break out some more tests into smaller test cases. Rename a test helper to be private: _expectBuilderThrows; private helps to indicate if it becomes unused. PiperOrigin-RevId: 315393913 --- pkgs/mockito/lib/src/builder.dart | 4 +- pkgs/mockito/test/builder_test.dart | 211 ++++++++++++++-------------- 2 files changed, 112 insertions(+), 103 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index e1fe97229..96465efa5 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -52,10 +52,12 @@ class MockBuilder implements Builder { if (annotation == null) continue; final generateMocksValue = annotation.computeConstantValue(); // TODO(srawlins): handle `generateMocksValue == null`? + // I am unable to think of a case which results in this situation. final classesField = generateMocksValue.getField('classes'); if (classesField.isNull) { throw InvalidMockitoAnnotationException( - 'The "classes" argument has unknown types'); + 'The GenerateMocks "classes" argument is missing, includes an ' + 'unknown type, or includes an extension'); } classesToMock.addAll(classesField.toListValue()); } diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index fafa3a134..3798bff8b 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -44,59 +44,51 @@ void main() {} void main() { test( - 'generates mock for an imported class but does not override private ' - 'or static methods or methods w/ zero parameters', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - dynamic a() => 7; - int _b(int x) => 8; - static int c(int y) => 9; - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + 'generates a mock class but does not override methods w/ zero parameters', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + dynamic a() => 7; + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo {} - '''), - }, + test('generates a mock class but does not override private methods', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + int _b(int x) => 8; + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), ); }); - test( - 'generates mock for an imported class but does not override any ' - 'extension methods', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - extension X on Foo { - dynamic x(int m, String n) => n + 1; - } - class Foo {} - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('generates a mock class but does not override static methods', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + static int c(int y) => 9; + } + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo {} - '''), - }, + test('generates a mock class but does not override any extension methods', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + extension X on Foo { + dynamic x(int m, String n) => n + 1; + } + class Foo {} + '''), + _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), ); }); @@ -185,23 +177,12 @@ void main() { }); test('generates generic mock classes', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo {} - '''), - 'foo|test/foo_test.dart': ''' - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - @GenerateMocks([Foo]) - void main() {} - ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), - }, + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo {} + '''), + _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), ); }); @@ -229,35 +210,30 @@ void main() { ); }); - test('writes non-interface types w/o imports', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - void f(dynamic a, int b) {} - void g(T c) {} - void h(U d) {} - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('writes dynamic, void w/o import prefix', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m(dynamic a, int b) {} + } + '''), + _containsAllOf( + 'void m(dynamic a, int? b) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));', + ), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - void f(dynamic a, int? b) => - super.noSuchMethod(Invocation.method(#f, [a, b])); - void g(T? c) => super.noSuchMethod(Invocation.method(#g, [c])); - void h(U? d) => super.noSuchMethod(Invocation.method(#h, [d])); + test('writes type variables types w/o import prefixes', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m(T a) {} } '''), - }, + _containsAllOf( + 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a]));', + ), ); }); @@ -1031,8 +1007,26 @@ void main() { ); }); + test('throws when GenerateMocks is missing an argument', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + // Missing required argument to GenerateMocks. + @GenerateMocks() + void main() {} + '''), + }, + message: contains('The GenerateMocks "classes" argument is missing'), + ); + }); + test('throws when GenerateMocks references an unresolved type', () async { - expectBuilderThrows( + _expectBuilderThrows( assets: { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -1045,19 +1039,18 @@ void main() { void main() {} '''), }, - message: 'The "classes" argument has unknown types', + message: contains('includes an unknown type'), ); }); test('throws when GenerateMocks references a non-type', () async { - expectBuilderThrows( + _expectBuilderThrows( assets: { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} '''), 'foo|test/foo_test.dart': dedent(''' - // missing foo.dart import. import 'package:mockito/annotations.dart'; @GenerateMocks([7]) void main() {} @@ -1068,7 +1061,7 @@ void main() { }); test('throws when GenerateMocks references a typedef', () async { - expectBuilderThrows( + _expectBuilderThrows( assets: { ...annotationsAsset, ...simpleTestAsset, @@ -1081,7 +1074,7 @@ void main() { }); test('throws when GenerateMocks references an enum', () async { - expectBuilderThrows( + _expectBuilderThrows( assets: { ...annotationsAsset, ...simpleTestAsset, @@ -1093,6 +1086,19 @@ void main() { ); }); + test('throws when GenerateMocks references an extension', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + extension Foo on String {} + '''), + }, + message: contains('includes an extension'), + ); + }); + test('given a pre-non-nullable library, does not override any members', () async { await _testPreNonNullable( @@ -1133,7 +1139,7 @@ Future _testPreNonNullable(Map sourceAssets, /// Test [MockBuilder] in a package which has opted into the non-nullable type /// system, and with the non-nullable experiment enabled. Future _testWithNonNullable(Map sourceAssets, - {Map*/ dynamic> outputs}) async { + {Map>*/ dynamic> outputs}) async { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), @@ -1151,7 +1157,7 @@ Future _testWithNonNullable(Map sourceAssets, /// enabled. Future _expectSingleNonNullableOutput( String sourceAssetText, - /*String|Matcher*/ dynamic output) async { + /*String|Matcher>*/ dynamic output) async { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), @@ -1177,12 +1183,13 @@ TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( /// Expect that [testBuilder], given [assets], throws an /// [InvalidMockitoAnnotationException] with a message containing [message]. -void expectBuilderThrows( - {@required Map assets, @required String message}) { +void _expectBuilderThrows( + {@required Map assets, + @required dynamic /*String|Matcher>*/ message}) { expect( () async => await testBuilder(buildMocks(BuilderOptions({})), assets), throwsA(TypeMatcher() - .having((e) => e.message, 'message', contains(message)))); + .having((e) => e.message, 'message', message))); } /// Dedent [input], so that each line is shifted to the left, so that the first From 5ad95ba4fe9031f76a779f35087927f4b3ebfe39 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 9 Jun 2020 17:55:41 -0400 Subject: [PATCH 195/607] MockBuilder: catch when users try to mock an un-subtypable class. The Dart language forbids mocking these classes, so any test would fail to run. PiperOrigin-RevId: 315566299 --- pkgs/mockito/lib/src/builder.dart | 8 ++- pkgs/mockito/test/builder_test.dart | 79 ++++++++++++++++------------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 96465efa5..b9350306d 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -130,8 +130,12 @@ class _MockLibraryInfo { 'The "classes" argument includes an enum: ' '${elementToMock.displayName}'); } - // TODO(srawlins): Catch when someone tries to generate mocks for an - // un-subtypable class, like bool, String, FutureOr, etc. + if (typeProvider.nonSubtypableClasses.contains(elementToMock)) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-subtypable type: ' + '${elementToMock.displayName}. It is illegal to subtype this ' + 'type.'); + } mockClasses.add(_buildMockClass(dartTypeToMock, elementToMock)); } else if (elementToMock is GenericFunctionTypeElement && elementToMock.enclosingElement is FunctionTypeAliasElement) { diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 3798bff8b..409a23cd2 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -920,35 +920,6 @@ void main() { ); }); - test('deduplicates fake classes', () async { - await _expectSingleNonNullableOutput( - dedent(r''' - class Foo { - Bar m1() => Bar('name1'); - Bar m2() => Bar('name2'); - } - class Bar { - final String name; - Bar(this.name); - } - '''), - dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - class _FakeBar extends _i1.Fake implements _i2.Bar {} - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); - _i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar()); - } - '''), - ); - }); - test('creates dummy non-null return values for enums', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -1007,13 +978,39 @@ void main() { ); }); + test('deduplicates fake classes', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Bar m1() => Bar('name1'); + Bar m2() => Bar('name2'); + } + class Bar { + final String name; + Bar(this.name); + } + '''), + dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + class _FakeBar extends _i1.Fake implements _i2.Bar {} + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo { + _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); + _i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar()); + } + '''), + ); + }); + test('throws when GenerateMocks is missing an argument', () async { _expectBuilderThrows( assets: { ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo {} - '''), 'foo|test/foo_test.dart': dedent(''' import 'package:mockito/annotations.dart'; // Missing required argument to GenerateMocks. @@ -1047,9 +1044,6 @@ void main() { _expectBuilderThrows( assets: { ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo {} - '''), 'foo|test/foo_test.dart': dedent(''' import 'package:mockito/annotations.dart'; @GenerateMocks([7]) @@ -1099,6 +1093,21 @@ void main() { ); }); + test('throws when GenerateMocks references a non-subtypeable type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + @GenerateMocks([int]) + void main() {} + '''), + }, + message: contains( + 'The "classes" argument includes a non-subtypable type: int'), + ); + }); + test('given a pre-non-nullable library, does not override any members', () async { await _testPreNonNullable( From 510f0925fb5296f06f69ffc6761449d271b56901 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 11 Jun 2020 18:58:36 -0400 Subject: [PATCH 196/607] MockBuilder: Refactor exception-throwing paths; add more * Almost all sanity checking was done in the _MockLibraryInfo constructor. Yuck. Move this to a new method. * Throw when a mock of the same name already exists. Someone is likely migrating existing tests, and would be in for a surprise. * Throw when asked to generate for two unique classes which have the same name. The code-generating API does not yet have a mechanism for user-supplied names to avoid such a conflict. * Test that the GenerateMocks annotation can be written multiple times, and that mocks from each occurrence are generated. * Check that `entryLib` is not null. I can't reproduce this in a test, but encountered it while testing on a real test: tableview_test.dart. * Deduplicate classes which are asked to be mocked multiple times. PiperOrigin-RevId: 315995820 --- pkgs/mockito/lib/src/builder.dart | 144 +++++++++++++++++++++------- pkgs/mockito/test/builder_test.dart | 131 +++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 35 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b9350306d..db31c5aba 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -39,9 +39,10 @@ class MockBuilder implements Builder { @override Future build(BuildStep buildStep) async { final entryLib = await buildStep.inputLibrary; + if (entryLib == null) return; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); - final classesToMock = []; + final objectsToMock = {}; for (final element in entryLib.topLevelElements) { final annotation = element.metadata.firstWhere( @@ -59,9 +60,14 @@ class MockBuilder implements Builder { 'The GenerateMocks "classes" argument is missing, includes an ' 'unknown type, or includes an extension'); } - classesToMock.addAll(classesField.toListValue()); + objectsToMock.addAll(classesField.toListValue()); } + var classesToMock = + _mapAnnotationValuesToClasses(objectsToMock, entryLib.typeProvider); + + _checkClassesToMockAreValid(classesToMock, entryLib); + final mockLibrary = Library((b) { var mockLibraryInfo = _MockLibraryInfo(classesToMock, sourceLibIsNonNullable: sourceLibIsNonNullable, @@ -84,6 +90,99 @@ class MockBuilder implements Builder { await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); } + /// Map the values passed to the GenerateMocks annotation to the classes which + /// they represent. + /// + /// This function is responsible for ensuring that each value is an + /// appropriate target for mocking. It will throw an + /// [InvalidMockitoAnnotationException] under various conditions. + List _mapAnnotationValuesToClasses( + Iterable objectsToMock, TypeProvider typeProvider) { + var classesToMock = []; + + for (final objectToMock in objectsToMock) { + final typeToMock = objectToMock.toTypeValue(); + if (typeToMock == null) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-type: $objectToMock'); + } + + final elementToMock = typeToMock.element; + if (elementToMock is ClassElement) { + if (elementToMock.isEnum) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes an enum: ' + '${elementToMock.displayName}'); + } + if (typeProvider.nonSubtypableClasses.contains(elementToMock)) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-subtypable type: ' + '${elementToMock.displayName}. It is illegal to subtype this ' + 'type.'); + } + classesToMock.add(typeToMock); + } else if (elementToMock is GenericFunctionTypeElement && + elementToMock.enclosingElement is FunctionTypeAliasElement) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a typedef: ' + '${elementToMock.enclosingElement.displayName}'); + } else { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-class: ' + '${elementToMock.displayName}'); + } + } + return classesToMock; + } + + void _checkClassesToMockAreValid( + List classesToMock, LibraryElement entryLib) { + var classesInEntryLib = entryLib.topLevelElements.whereType(); + var classNamesToMock = {}; + for (var class_ in classesToMock) { + var name = class_.element.name; + if (classNamesToMock.containsKey(name)) { + var firstSource = classNamesToMock[name].source.fullName; + var secondSource = class_.element.source.fullName; + // TODO(srawlins): Support an optional @GenerateMocks API that allows + // users to choose names. One class might be named MockFoo and the other + // named MockPbFoo, for example. + throw InvalidMockitoAnnotationException( + 'The GenerateMocks "classes" argument contains two classes with ' + 'the same name: $name. One declared in $firstSource, the other in ' + '$secondSource.'); + } + classNamesToMock[name] = class_.element as ClassElement; + } + var classNamesToGenerate = classNamesToMock.keys.map((name) => 'Mock$name'); + classNamesToMock.forEach((name, element) { + var conflictingClass = classesInEntryLib.firstWhere( + (c) => c.name == 'Mock${element.name}', + orElse: () => null); + if (conflictingClass != null) { + throw InvalidMockitoAnnotationException( + 'The GenerateMocks "classes" argument contains a class which ' + 'conflicts with another class declared in this library: ' + '${conflictingClass.name}'); + } + var preexistingMock = classesInEntryLib.firstWhere( + (c) => + c.interfaces.map((type) => type.element).contains(element) && + _isMockClass(c.supertype), + orElse: () => null); + if (preexistingMock != null) { + throw InvalidMockitoAnnotationException( + 'The GenerateMocks "classes" argument contains a class which ' + 'appears to already be mocked inline: ${preexistingMock.name}'); + } + }); + } + + /// Return whether [type] is the Mock class declared by mockito. + bool _isMockClass(analyzer.InterfaceType type) => + type.element.name == 'Mock' && + type.element.source.fullName.endsWith('lib/src/mock.dart'); + @override final buildExtensions = const { '.dart': ['.mocks.dart'] @@ -114,44 +213,16 @@ class _MockLibraryInfo { /// Build mock classes for [classesToMock], a list of classes obtained from a /// `@GenerateMocks` annotation. - _MockLibraryInfo(List classesToMock, + _MockLibraryInfo(List classesToMock, {this.sourceLibIsNonNullable, this.typeProvider, this.typeSystem}) { for (final classToMock in classesToMock) { - final dartTypeToMock = classToMock.toTypeValue(); - if (dartTypeToMock == null) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-type: $classToMock'); - } - - final elementToMock = dartTypeToMock.element; - if (elementToMock is ClassElement) { - if (elementToMock.isEnum) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes an enum: ' - '${elementToMock.displayName}'); - } - if (typeProvider.nonSubtypableClasses.contains(elementToMock)) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-subtypable type: ' - '${elementToMock.displayName}. It is illegal to subtype this ' - 'type.'); - } - mockClasses.add(_buildMockClass(dartTypeToMock, elementToMock)); - } else if (elementToMock is GenericFunctionTypeElement && - elementToMock.enclosingElement is FunctionTypeAliasElement) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a typedef: ' - '${elementToMock.enclosingElement.displayName}'); - } else { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-class: ' - '${elementToMock.displayName}'); - } + mockClasses.add(_buildMockClass(classToMock)); } } - Class _buildMockClass(analyzer.DartType dartType, ClassElement classToMock) { - final className = dartType.displayName; + Class _buildMockClass(analyzer.DartType dartType) { + final classToMock = dartType.element as ClassElement; + final className = dartType.name; return Class((cBuilder) { cBuilder @@ -330,6 +401,9 @@ class _MockLibraryInfo { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); } else { + // There is a potential for these names to collide. If one mock class + // requires a fake for a certain Foo, and another mock class requires a + // fake for a different Foo, they will collide. var fakeName = '_Fake${dartType.name}'; // Only make one fake class for each class that needs to be faked. if (!fakedClassElements.contains(elementToFake)) { diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 409a23cd2..b703f4056 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -33,6 +33,15 @@ class GenerateMocks { ''' }; +const mockitoAssets = { + 'mockito|lib/mockito.dart': ''' +export 'src/mock.dart'; +''', + 'mockito|lib/src/mock.dart': ''' +class Mock {} +''' +}; + const simpleTestAsset = { 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; @@ -176,6 +185,60 @@ void main() { ); }); + test('deduplicates classes listed multiply in GenerateMocks', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo, Foo]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': dedent(r''' + import 'package:mockito/mockito.dart' as _i1; + import 'package:foo/foo.dart' as _i2; + + /// A class which mocks [Foo]. + /// + /// See the documentation for Mockito's code generation for more information. + class MockFoo extends _i1.Mock implements _i2.Foo {} + '''), + }, + ); + }); + + test('generates mock classes from multiple annotations', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void fooTests() {} + @GenerateMocks([Bar]) + void barTests() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + 'class MockBar extends _i1.Mock implements _i2.Bar {}', + ), + }, + ); + }); + test('generates generic mock classes', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -1040,6 +1103,74 @@ void main() { ); }); + test('throws when two distinct classes with the same name are mocked', + () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/a.dart': dedent(r''' + class Foo {} + '''), + 'foo|lib/b.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/a.dart' as a; + import 'package:foo/b.dart' as b; + import 'package:mockito/annotations.dart'; + @GenerateMocks([a.Foo, b.Foo]) + void main() {} + '''), + }, + message: contains( + 'contains two classes with the same name: Foo. One declared in ' + '/foo/lib/a.dart, the other in /foo/lib/b.dart'), + ); + }); + + test('throws when a mock class of the same name already exists', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void main() {} + class MockFoo {} + '''), + }, + message: contains( + 'contains a class which conflicts with another class declared in ' + 'this library: MockFoo'), + ); + }); + + test('throws when a mock class of class-to-mock already exists', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...mockitoAssets, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + import 'package:mockito/mockito.dart'; + @GenerateMocks([Foo]) + void main() {} + class FakeFoo extends Mock implements Foo {} + '''), + }, + message: contains( + 'contains a class which appears to already be mocked inline: FakeFoo'), + ); + }); + test('throws when GenerateMocks references a non-type', () async { _expectBuilderThrows( assets: { From 467f994f12903db1c86d0b1688a6593564e04d1b Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 11 Jun 2020 22:46:40 -0400 Subject: [PATCH 197/607] MockBuilder: Make function-typed parameters nullable. * Update all test expectations to include nullable function-typed parameters. * Split apart a test case into individual assertions. PiperOrigin-RevId: 316027767 --- pkgs/mockito/lib/src/builder.dart | 3 +- pkgs/mockito/test/builder_test.dart | 106 ++++++++++++++-------------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index db31c5aba..7c82bdf21 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -572,7 +572,8 @@ class _MockLibraryInfo { // [type] represents a FunctionTypedFormalParameter. return FunctionType((b) { b - // TODO(srawlins): Fix FunctionType to take an `isNullable` value. + ..isNullable = + forceNullable || typeSystem.isPotentiallyNullable(type) ..returnType = _typeReference(type.returnType) ..requiredParameters .addAll(type.normalParameterTypes.map(_typeReference)) diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index b703f4056..ba0e3b969 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -365,47 +365,55 @@ void main() { ); }); - test('imports libraries for function types with external types', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - import 'dart:async'; - class Foo { - dynamic f(Foo c()) {} - dynamic g(void c(Foo f)) {} - dynamic h(void c(Foo f, [Foo g])) {} - dynamic i(void c(Foo f, {Foo g})) {} - dynamic j(Foo Function() c) {} - dynamic k(void Function(Foo f) c) {} - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + test('prefixes parameter type on generic function-typed parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + import 'dart:async'; + class Foo { + dynamic m(void Function(Foo f) a) {} + } + '''), + _containsAllOf('dynamic m(void Function(_i2.Foo)? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - dynamic f(_i2.Foo Function() c) => - super.noSuchMethod(Invocation.method(#f, [c])); - dynamic g(void Function(_i2.Foo) c) => - super.noSuchMethod(Invocation.method(#g, [c])); - dynamic h(void Function(_i2.Foo, [_i2.Foo]) c) => - super.noSuchMethod(Invocation.method(#h, [c])); - dynamic i(void Function(_i2.Foo, {_i2.Foo g}) c) => - super.noSuchMethod(Invocation.method(#i, [c])); - dynamic j(_i2.Foo Function() c) => - super.noSuchMethod(Invocation.method(#j, [c])); - dynamic k(void Function(_i2.Foo) c) => - super.noSuchMethod(Invocation.method(#k, [c])); - } - '''), - }, + test('prefixes return type on generic function-typed parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + import 'dart:async'; + class Foo { + void m(Foo Function() a) {} + } + '''), + _containsAllOf('void m(_i2.Foo Function()? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('prefixes parameter type on function-typed parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + import 'dart:async'; + class Foo { + void m(void a(Foo f)) {} + } + '''), + _containsAllOf('void m(void Function(_i2.Foo)? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('prefixes return type on function-typed parameter', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + import 'dart:async'; + class Foo { + void m(Foo a()) {} + } + '''), + _containsAllOf('void m(_i2.Foo Function()? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), ); }); @@ -456,9 +464,7 @@ void main() { void m(int? Function() a, int Function() b); } '''), - // `a` and `b` should be nullable. code_builder needs a fix, allowing a - // FunctionTypeBuilder to take an `isNullable` value. - _containsAllOf('void m(int? Function() a, int Function() b) =>', + _containsAllOf('void m(int? Function()? a, int Function()? b) =>', 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), ); }); @@ -472,9 +478,7 @@ void main() { void m(void Function(int?) a, void Function(int) b); } '''), - // `a` and `b` should be nullable. code_builder needs a fix, allowing a - // FunctionTypeBuilder to take an `isNullable` value. - _containsAllOf('void m(void Function(int?) a, void Function(int) b) =>', + _containsAllOf('void m(void Function(int?)? a, void Function(int)? b) =>', 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), ); }); @@ -487,9 +491,7 @@ void main() { void m(int? a(), int b()); } '''), - // `a` and `b` should be nullable. code_builder needs a fix, allowing a - // FunctionTypeBuilder to take an `isNullable` value. - _containsAllOf('void m(int? Function() a, int Function() b) =>', + _containsAllOf('void m(int? Function()? a, int Function()? b) =>', 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), ); }); @@ -503,9 +505,7 @@ void main() { void m(void a(int? x), void b(int x)); } '''), - // `a` and `b` should be nullable. code_builder needs a fix, allowing a - // FunctionTypeBuilder to take an `isNullable` value. - _containsAllOf('void m(void Function(int?) a, void Function(int) b) =>', + _containsAllOf('void m(void Function(int?)? a, void Function(int)? b) =>', 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), ); }); @@ -710,8 +710,6 @@ void main() { '''), }, outputs: { - // TODO(srawlins): The getter will appear when it has a non-nullable - // return type. 'foo|test/foo_test.mocks.dart': dedent(r''' import 'package:mockito/mockito.dart' as _i1; import 'package:foo/foo.dart' as _i2; From 8a0454f21e69c8a9be9a2f8374e6461f84fc4e50 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 15 Jun 2020 17:48:42 -0400 Subject: [PATCH 198/607] Add null safety README to mockito. This documents the code generation feature, and why its needed. This is a tricky balance between getting the users up-and-running with code generation, and explaining why its needed, because some may consider it a pain, if its their first introduction to build_runner. PiperOrigin-RevId: 316549387 --- pkgs/mockito/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index dcb356514..c54bdca29 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -31,6 +31,11 @@ var cat = MockCat(); By declaring a class which extends Mockito's Mock class and implements the Cat class under test, we have a class which supports stubbing and verifying. +**Using Dart's new null safety feature?** Read the [NULL_SAFETY_README][] for +help on creating mocks of classes featuring non-nullable types. + +[NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md + ## Let's verify some behaviour! ```dart From 770ad91963cca36c0d0483e47a35d20206ab1591 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 17 Jun 2020 17:33:49 -0400 Subject: [PATCH 199/607] MockBuilder: Include type arguments on dummy return values. Without type arguments, runtime errors can take place. A stub method may try to use `[]` as a return value for type `[]`, but inference does not occur, so `[]` is implicitly `[]`. Additionally, include type arguments on function types. Clean up tests which are affected by these two fixes. PiperOrigin-RevId: 316966473 --- pkgs/mockito/lib/src/builder.dart | 195 ++++++++++++++++------------ pkgs/mockito/test/builder_test.dart | 174 +++++++++++++++++++------ 2 files changed, 248 insertions(+), 121 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7c82bdf21..030d51036 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -356,106 +356,132 @@ class _MockLibraryInfo { } Expression _dummyValue(analyzer.DartType type) { - if (type.isDartCoreBool) { + if (type is analyzer.FunctionType) { + return _dummyFunctionValue(type); + } + + if (type is! analyzer.InterfaceType) { + // TODO(srawlins): This case is not known. + return literalNull; + } + + var interfaceType = type as analyzer.InterfaceType; + var typeArguments = interfaceType.typeArguments; + if (interfaceType.isDartCoreBool) { return literalFalse; - } else if (type.isDartCoreDouble) { + } else if (interfaceType.isDartCoreDouble) { return literalNum(0.0); - } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { - var typeArgument = (type as analyzer.InterfaceType).typeArguments.first; + } else if (interfaceType.isDartAsyncFuture || + interfaceType.isDartAsyncFutureOr) { + var typeArgument = typeArguments.first; return refer('Future') .property('value') .call([_dummyValue(typeArgument)]); - } else if (type.isDartCoreInt) { + } else if (interfaceType.isDartCoreInt) { return literalNum(0); - } else if (type.isDartCoreIterable) { + } else if (interfaceType.isDartCoreIterable) { return literalList([]); - } else if (type.isDartCoreList) { - return literalList([]); - } else if (type.isDartCoreMap) { - return literalMap({}); - } else if (type.isDartCoreNum) { + } else if (interfaceType.isDartCoreList) { + assert(typeArguments.length == 1); + var elementType = _typeReference(typeArguments[0]); + return literalList([], elementType); + } else if (interfaceType.isDartCoreMap) { + assert(typeArguments.length == 2); + var keyType = _typeReference(typeArguments[0]); + var valueType = _typeReference(typeArguments[1]); + return literalMap({}, keyType, valueType); + } else if (interfaceType.isDartCoreNum) { return literalNum(0); - } else if (type.isDartCoreSet) { - // This is perhaps a dangerous hack. The code, `{}`, is parsed as a Set - // literal if it is used in a context which explicitly expects a Set. - return literalMap({}); - } else if (type.element?.declaration == typeProvider.streamElement) { - return refer('Stream').property('empty').call([]); - } else if (type.isDartCoreString) { + } else if (interfaceType.isDartCoreSet) { + assert(typeArguments.length == 1); + var elementType = _typeReference(typeArguments[0]); + return literalSet({}, elementType); + } else if (interfaceType.element?.declaration == + typeProvider.streamElement) { + assert(typeArguments.length == 1); + var elementType = _typeReference(typeArguments[0]); + return TypeReference((b) { + b + ..symbol = 'Stream' + ..types.add(elementType); + }).property('empty').call([]); + } else if (interfaceType.isDartCoreString) { return literalString(''); - } else { - // This class is unknown; we must likely generate a fake class, and return - // an instance here. - return _dummyValueImplementing(type); } + + // This class is unknown; we must likely generate a fake class, and return + // an instance here. + return _dummyValueImplementing(type); } - Expression _dummyValueImplementing(analyzer.DartType dartType) { - // For each type parameter on [classToMock], the Mock class needs a type - // parameter with same type variables, and a mirrored type argument for - // the "implements" clause. - var typeArguments = []; - var elementToFake = dartType.element; - if (elementToFake is ClassElement) { - if (elementToFake.isEnum) { - return _typeReference(dartType).property( - elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); + Expression _dummyFunctionValue(analyzer.FunctionType type) { + return Method((b) { + // The positional parameters in a FunctionType have no names. This + // counter lets us create unique dummy names. + var counter = 0; + for (final parameter in type.parameters) { + if (parameter.isRequiredPositional) { + b.requiredParameters + .add(_matchingParameter(parameter, defaultName: '__p$counter')); + counter++; + } else if (parameter.isOptionalPositional) { + b.optionalParameters + .add(_matchingParameter(parameter, defaultName: '__p$counter')); + counter++; + } else if (parameter.isNamed) { + b.optionalParameters.add(_matchingParameter(parameter)); + } + } + if (type.returnType.isVoid) { + b.body = Code(''); } else { - // There is a potential for these names to collide. If one mock class - // requires a fake for a certain Foo, and another mock class requires a - // fake for a different Foo, they will collide. - var fakeName = '_Fake${dartType.name}'; - // Only make one fake class for each class that needs to be faked. - if (!fakedClassElements.contains(elementToFake)) { - fakeClasses.add(Class((cBuilder) { - cBuilder - ..name = fakeName - ..extend = refer('Fake', 'package:mockito/mockito.dart'); - if (elementToFake.typeParameters != null) { - for (var typeParameter in elementToFake.typeParameters) { - cBuilder.types.add(_typeParameterReference(typeParameter)); - typeArguments.add(refer(typeParameter.name)); - } + b.body = _dummyValue(type.returnType).code; + } + }).closure; + } + + Expression _dummyValueImplementing(analyzer.InterfaceType dartType) { + // For each type parameter on [dartType], the Mock class needs a type + // parameter with same type variables, and a mirrored type argument for the + // "implements" clause. + var typeParameters = []; + var elementToFake = dartType.element; + if (elementToFake.isEnum) { + return _typeReference(dartType).property( + elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); + } else { + // There is a potential for these names to collide. If one mock class + // requires a fake for a certain Foo, and another mock class requires a + // fake for a different Foo, they will collide. + var fakeName = '_Fake${dartType.name}'; + // Only make one fake class for each class that needs to be faked. + if (!fakedClassElements.contains(elementToFake)) { + fakeClasses.add(Class((cBuilder) { + cBuilder + ..name = fakeName + ..extend = refer('Fake', 'package:mockito/mockito.dart'); + if (elementToFake.typeParameters != null) { + for (var typeParameter in elementToFake.typeParameters) { + cBuilder.types.add(_typeParameterReference(typeParameter)); + typeParameters.add(refer(typeParameter.name)); } - cBuilder.implements.add(TypeReference((b) { - b - ..symbol = dartType.name - ..url = _typeImport(dartType) - ..types.addAll(typeArguments); - })); + } + cBuilder.implements.add(TypeReference((b) { + b + ..symbol = dartType.name + ..url = _typeImport(dartType) + ..types.addAll(typeParameters); })); - fakedClassElements.add(elementToFake); - } - return refer(fakeName).newInstance([]); + })); + fakedClassElements.add(elementToFake); } - } else if (dartType is analyzer.FunctionType) { - return Method((b) { - // The positional parameters in a FunctionType have no names. This - // counter lets us create unique dummy names. - var counter = 0; - for (final parameter in dartType.parameters) { - if (parameter.isRequiredPositional) { - b.requiredParameters - .add(_matchingParameter(parameter, defaultName: '__p$counter')); - counter++; - } else if (parameter.isOptionalPositional) { - b.optionalParameters - .add(_matchingParameter(parameter, defaultName: '__p$counter')); - counter++; - } else if (parameter.isNamed) { - b.optionalParameters.add(_matchingParameter(parameter)); - } - } - if (dartType.returnType.isVoid) { - b.body = Code(''); - } else { - b.body = _dummyValue(dartType.returnType).code; - } - }).closure; + var typeArguments = dartType.typeArguments; + return TypeReference((b) { + b + ..symbol = fakeName + ..types.addAll(typeArguments.map(_typeReference)); + }).newInstance([]); } - - // We shouldn't get here. - return literalNull; } /// Returns a [Parameter] which matches [parameter]. @@ -582,6 +608,9 @@ class _MockLibraryInfo { for (var parameter in type.namedParameterTypes.entries) { b.namedParameters[parameter.key] = _typeReference(parameter.value); } + if (type.typeFormals != null) { + b.types.addAll(type.typeFormals.map(_typeParameterReference)); + } }); } return TypeReference((b) { diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index ba0e3b969..578bb45b6 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -145,7 +145,7 @@ void main() { } '''), _containsAllOf('_i3.Stream m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), Stream.empty());'), + 'super.noSuchMethod(Invocation.method(#m, []), Stream.empty());'), ); }); @@ -534,40 +534,63 @@ void main() { ); }); - test('matches nullability of return types', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' + test('matches nullability of non-nullable return type', () async { + await _expectSingleNonNullableOutput( + dedent(r''' abstract class Foo { - int f(int a); - int? g(int a); - List h(int a); - List i(int a); - j(int a); - T? k(int a); + int m(int a); } '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; + _containsAllOf( + 'int m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]), 0);'), + ); + }); - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - int f(int? a) => super.noSuchMethod(Invocation.method(#f, [a]), 0); - int? g(int? a) => super.noSuchMethod(Invocation.method(#g, [a])); - List h(int? a) => super.noSuchMethod(Invocation.method(#h, [a]), []); - List i(int? a) => super.noSuchMethod(Invocation.method(#i, [a]), []); - dynamic j(int? a) => super.noSuchMethod(Invocation.method(#j, [a])); - T? k(int? a) => super.noSuchMethod(Invocation.method(#k, [a])); + test('matches nullability of nullable return type', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + int? m(int a); } '''), - }, + _containsAllOf( + 'int? m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches nullability of return type type arguments', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + List m(int a); + } + '''), + _containsAllOf('List m(int? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]), []);'), + ); + }); + + test('matches nullability of nullable type variable return type', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + T? m(int a); + } + '''), + _containsAllOf( + 'T? m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('overrides implicit return type with dynamic', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + m(int a); + } + '''), + _containsAllOf( + 'dynamic m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), ); }); @@ -923,8 +946,8 @@ void main() { List m() => [Foo()]; } '''), - _containsAllOf( - 'List<_i2.Foo> m() => super.noSuchMethod(Invocation.method(#m, []), []);'), + _containsAllOf('List<_i2.Foo> m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>[]);'), ); }); @@ -935,8 +958,8 @@ void main() { Set m() => {Foo()}; } '''), - _containsAllOf( - 'Set<_i2.Foo> m() => super.noSuchMethod(Invocation.method(#m, []), {});'), + _containsAllOf('Set<_i2.Foo> m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>{});'), ); }); @@ -947,8 +970,20 @@ void main() { Map m() => {7: Foo()}; } '''), - _containsAllOf( - 'Map m() => super.noSuchMethod(Invocation.method(#m, []), {});'), + _containsAllOf('Map m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), {});'), + ); + }); + + test('creates dummy non-null raw-typed return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + Map m(); + } + '''), + _containsAllOf('Map m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), {});'), ); }); @@ -965,6 +1000,18 @@ void main() { ); }); + test('creates dummy non-null Stream return value', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + Stream m(); + } + '''), + _containsAllOf('Stream m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), Stream.empty());'), + ); + }); + test('creates dummy non-null return values for unknown classes', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -981,6 +1028,19 @@ void main() { ); }); + test('creates dummy non-null return values for generic type', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + Bar m(); + } + class Bar {} + '''), + _containsAllOf('Bar m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), _FakeBar());'), + ); + }); + test('creates dummy non-null return values for enums', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -998,7 +1058,7 @@ void main() { }); test( - 'creates a dummy non-null return function-typed value, with optional ' + 'creates a dummy non-null function-typed return value, with optional ' 'parameters', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -1012,7 +1072,7 @@ void main() { }); test( - 'creates a dummy non-null return function-typed value, with named ' + 'creates a dummy non-null function-typed return value, with named ' 'parameters', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -1026,7 +1086,7 @@ void main() { }); test( - 'creates a dummy non-null return function-typed value, with non-core ' + 'creates a dummy non-null function-typed return value, with non-core ' 'return type', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -1039,6 +1099,44 @@ void main() { ); }); + test('creates a dummy non-null generic function-typed return value', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + T Function(T) m() => (int i, [String s]) {}; + } + '''), + _containsAllOf('T Function(T) m() =>', + 'super.noSuchMethod(Invocation.method(#m, []), (T __p0) => null);'), + ); + }); + + test('generates a fake class used in return values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Bar m1() => Bar('name1'); + } + class Bar {} + '''), + _containsAllOf('class _FakeBar extends _i1.Fake implements _i2.Bar {}'), + ); + }); + + test('generates a fake generic class used in return values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Bar m1() => Bar('name1'); + } + class Bar {} + '''), + _containsAllOf( + 'class _FakeBar extends _i1.Fake implements _i2.Bar {}'), + ); + }); + test('deduplicates fake classes', () async { await _expectSingleNonNullableOutput( dedent(r''' From 0107c5b710f4fee75dd1fb40d8d8288af43b63e2 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 19 Jun 2020 18:05:16 -0400 Subject: [PATCH 200/607] Prepare mockito for null safety: Change [_findAfter] to throw instead of return null, in the case that a match could not be found. The way the code is before, where `firstWhere` can return null via `orElse`, the type of list is inferred to be a list of nullable elements. The migration tool feeds this nullability up very high. Removing the `orElse` and catching the possible StateError is safer. * Add /*late*/ hints for the migration tool. PiperOrigin-RevId: 317387499 --- pkgs/mockito/example/iss/iss.dart | 2 +- pkgs/mockito/lib/src/mock.dart | 11 +++++------ pkgs/mockito/test/invocation_matcher_test.dart | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index eef8df43d..86e95d516 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -22,7 +22,7 @@ import 'package:http/http.dart'; class IssLocator { final Client client; - Point _position; + /*late*/ Point _position; Future _ongoingRequest; IssLocator(this.client); diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 9b2e3b1c7..793f8f6a5 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -658,9 +658,8 @@ class _VerifyCall { } RealCall _findAfter(DateTime dt) { - return matchingInvocations.firstWhere( - (inv) => !inv.verified && inv.timeStamp.isAfter(dt), - orElse: () => null); + return matchingInvocations + .firstWhere((inv) => !inv.verified && inv.timeStamp.isAfter(dt)); } void _checkWith(bool never) { @@ -1001,11 +1000,11 @@ _InOrderVerification get verifyInOrder { _verifyCalls.clear(); var matchedCalls = []; for (var verifyCall in tmpVerifyCalls) { - var matched = verifyCall._findAfter(dt); - if (matched != null) { + try { + var matched = verifyCall._findAfter(dt); matchedCalls.add(matched); dt = matched.timeStamp; - } else { + } on StateError { var mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); var allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index dc5bb5252..97c8a9e78 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -153,7 +153,7 @@ abstract class Interface { /// /// Any call always returns an [Invocation]. class Stub implements Interface { - static Invocation lastInvocation; + static /*late*/ Invocation lastInvocation; const Stub(); From da88278384ead5b5b039d4c8bcdae9bdf5088b87 Mon Sep 17 00:00:00 2001 From: srawlins Date: Sat, 20 Jun 2020 14:05:25 -0400 Subject: [PATCH 201/607] MockBuilder: Throw when mocking a class with a method with a non-nullable type variable as return type. Take the example: class Foo { T m() { ... } } Under the null safety Dart feature, mockito's code-generation cannot return null during stubbing or verification (as T may be non-nullable), nor can it create a dummy value to return; T is an unknown type. PiperOrigin-RevId: 317475281 --- pkgs/mockito/lib/src/builder.dart | 31 +++++++++++---- pkgs/mockito/test/builder_test.dart | 61 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 030d51036..24eb7a3c6 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -20,6 +20,7 @@ import 'package:analyzer/dart/element/type_system.dart'; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; +import 'package:meta/meta.dart'; /// For a source Dart library, generate the mocks referenced therein. /// @@ -154,7 +155,7 @@ class MockBuilder implements Builder { } classNamesToMock[name] = class_.element as ClassElement; } - var classNamesToGenerate = classNamesToMock.keys.map((name) => 'Mock$name'); + classNamesToMock.forEach((name, element) { var conflictingClass = classesInEntryLib.firstWhere( (c) => c.name == 'Mock${element.name}', @@ -165,6 +166,7 @@ class MockBuilder implements Builder { 'conflicts with another class declared in this library: ' '${conflictingClass.name}'); } + var preexistingMock = classesInEntryLib.firstWhere( (c) => c.interfaces.map((type) => type.element).contains(element) && @@ -175,6 +177,18 @@ class MockBuilder implements Builder { 'The GenerateMocks "classes" argument contains a class which ' 'appears to already be mocked inline: ${preexistingMock.name}'); } + + var unstubbableMethods = element.methods.where((m) => + m.returnType is analyzer.TypeParameterType && + entryLib.typeSystem.isPotentiallyNonNullable(m.returnType)); + if (unstubbableMethods.isNotEmpty) { + var unstubbableMethodNames = + unstubbableMethods.map((m) => "'$name.${m.name}'").join(', '); + throw InvalidMockitoAnnotationException( + 'Mockito cannot generate a valid mock class which implements ' + "'$name'. The method(s) $unstubbableMethodNames, which each return a " + 'non-nullable value of unknown type, cannot be stubbed.'); + } }); } @@ -223,10 +237,11 @@ class _MockLibraryInfo { Class _buildMockClass(analyzer.DartType dartType) { final classToMock = dartType.element as ClassElement; final className = dartType.name; + final mockClassName = 'Mock$className'; return Class((cBuilder) { cBuilder - ..name = 'Mock$className' + ..name = mockClassName ..extend = refer('Mock', 'package:mockito/mockito.dart') ..docs.add('/// A class which mocks [$className].') ..docs.add('///') @@ -275,8 +290,8 @@ class _MockLibraryInfo { } if (_returnTypeIsNonNullable(method) || _hasNonNullableParameter(method)) { - cBuilder.methods.add( - Method((mBuilder) => _buildOverridingMethod(mBuilder, method))); + cBuilder.methods.add(Method((mBuilder) => + _buildOverridingMethod(mBuilder, method, className: className))); } } }); @@ -304,9 +319,8 @@ class _MockLibraryInfo { /// /// This new method just calls `super.noSuchMethod`, optionally passing a /// return value for methods with a non-nullable return type. - // TODO(srawlins): Include widening tests for typedefs, old-style function - // parameters, function types. - void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { + void _buildOverridingMethod(MethodBuilder builder, MethodElement method, + {@required String className}) { var name = method.displayName; if (method.isOperator) name = 'operator$name'; builder @@ -339,6 +353,9 @@ class _MockLibraryInfo { } } + if (_returnTypeIsNonNullable(method) && + method.returnType is analyzer.TypeParameterType) {} + final invocation = refer('Invocation').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 578bb45b6..aebddb366 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -1166,6 +1166,67 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'non-nullable class-declared type variable return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + T m(int a); + } + '''), + }, + message: contains( + "Mockito cannot generate a valid mock class which implements 'Foo'. " + "The method(s) 'Foo.m', which each return a non-nullable value of " + 'unknown type, cannot be stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'non-nullable method-declared type variable return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + T m(int a); + } + '''), + }, + message: contains( + "Mockito cannot generate a valid mock class which implements 'Foo'. " + "The method(s) 'Foo.m', which each return a non-nullable value of " + 'unknown type, cannot be stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'non-nullable method-declared bounded type variable return type', + () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + T m(int a); + } + '''), + }, + message: contains( + "Mockito cannot generate a valid mock class which implements 'Foo'. " + "The method(s) 'Foo.m', which each return a non-nullable value of " + 'unknown type, cannot be stubbed.'), + ); + }); + test('throws when GenerateMocks is missing an argument', () async { _expectBuilderThrows( assets: { From a53db55e3c5b06f51e7d826806672f93b0e420db Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 22 Jun 2020 16:58:10 -0400 Subject: [PATCH 202/607] MockBuilder: Fix two bugs regarding the GenerateMocks annotation: * Generate mocks for all @GenerateMocks annotations on a top-level element. Previously only the first was respected. * If a private class is listed in @GenerateMocks, throw at compile time. PiperOrigin-RevId: 317729616 --- pkgs/mockito/lib/src/builder.dart | 38 +++++++++++++++---------- pkgs/mockito/test/builder_test.dart | 43 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 24eb7a3c6..539042a3a 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -46,22 +46,25 @@ class MockBuilder implements Builder { final objectsToMock = {}; for (final element in entryLib.topLevelElements) { - final annotation = element.metadata.firstWhere( - (annotation) => - annotation.element is ConstructorElement && - annotation.element.enclosingElement.name == 'GenerateMocks', - orElse: () => null); - if (annotation == null) continue; - final generateMocksValue = annotation.computeConstantValue(); - // TODO(srawlins): handle `generateMocksValue == null`? - // I am unable to think of a case which results in this situation. - final classesField = generateMocksValue.getField('classes'); - if (classesField.isNull) { - throw InvalidMockitoAnnotationException( - 'The GenerateMocks "classes" argument is missing, includes an ' - 'unknown type, or includes an extension'); + // TODO(srawlins): Re-think the idea of multiple @GenerateMocks + // annotations, on one element or even on different elements in a library. + for (final annotation in element.metadata) { + if (annotation == null) continue; + if (annotation.element is! ConstructorElement || + annotation.element.enclosingElement.name != 'GenerateMocks') { + continue; + } + final generateMocksValue = annotation.computeConstantValue(); + // TODO(srawlins): handle `generateMocksValue == null`? + // I am unable to think of a case which results in this situation. + final classesField = generateMocksValue.getField('classes'); + if (classesField.isNull) { + throw InvalidMockitoAnnotationException( + 'The GenerateMocks "classes" argument is missing, includes an ' + 'unknown type, or includes an extension'); + } + objectsToMock.addAll(classesField.toListValue()); } - objectsToMock.addAll(classesField.toListValue()); } var classesToMock = @@ -121,6 +124,11 @@ class MockBuilder implements Builder { '${elementToMock.displayName}. It is illegal to subtype this ' 'type.'); } + if (elementToMock.isPrivate) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a private type: ' + '${elementToMock.displayName}.'); + } classesToMock.add(typeToMock); } else if (elementToMock is GenericFunctionTypeElement && elementToMock.enclosingElement is FunctionTypeAliasElement) { diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index aebddb366..41e1f3e2a 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -239,6 +239,32 @@ void main() { ); }); + test('generates mock classes from multiple annotations on a single element', + () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + @GenerateMocks([Bar]) + void barTests() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + 'class MockBar extends _i1.Mock implements _i2.Bar {}', + ), + }, + ); + }); + test('generates generic mock classes', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -1242,6 +1268,23 @@ void main() { ); }); + test('throws when GenerateMocks is given a private class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + // Missing required argument to GenerateMocks. + @GenerateMocks([_Foo]) + void main() {} + class _Foo {} + '''), + }, + message: + contains('The "classes" argument includes a private type: _Foo.'), + ); + }); + test('throws when GenerateMocks references an unresolved type', () async { _expectBuilderThrows( assets: { From f33b0912a580dd3bf0c59accf3ca257fc8c4ff86 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 24 Jun 2020 17:09:13 -0400 Subject: [PATCH 203/607] MockBuilder: Throw when a mocked method has a private return or parameter type. This takes on a few forms: * If a method has a private InterfaceType return type, like `_Bar`, throw. * If a method has a private InterfaceType parameter type, like `_Bar`, throw. This is not strictly necessary; we could expand the parameter type to `Object?` or something, but that would be very strange. For now, this is unsupported. * If a method has a FunctionType return type or parameter type which itself has a private return type, like `_Bar Function()`, or private parameter type, like `void Function(_Bar)`, throw. This includes fixes for two other forms: * If a method has a non-nullable TypeParameterType return type, like `T`, throw. * If a method has a non-nullable FunctionType return type which itself has a non-nullable return type, like `T Function()`, throw. In all of the above cases, only consider methods which are part of the public interface: only public instance methods. PiperOrigin-RevId: 318137699 --- pkgs/mockito/lib/src/builder.dart | 86 +++++++++++++++++--- pkgs/mockito/test/builder_test.dart | 120 +++++++++++++++++++++++++--- 2 files changed, 184 insertions(+), 22 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 539042a3a..412a51266 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -186,20 +186,84 @@ class MockBuilder implements Builder { 'appears to already be mocked inline: ${preexistingMock.name}'); } - var unstubbableMethods = element.methods.where((m) => - m.returnType is analyzer.TypeParameterType && - entryLib.typeSystem.isPotentiallyNonNullable(m.returnType)); - if (unstubbableMethods.isNotEmpty) { - var unstubbableMethodNames = - unstubbableMethods.map((m) => "'$name.${m.name}'").join(', '); - throw InvalidMockitoAnnotationException( - 'Mockito cannot generate a valid mock class which implements ' - "'$name'. The method(s) $unstubbableMethodNames, which each return a " - 'non-nullable value of unknown type, cannot be stubbed.'); - } + _checkMethodsToStubAreValid(element, entryLib); }); } + /// Throws if any public instance methods of [classElement] are not valid + /// stubbing candidates. + /// + /// A method is not valid for stubbing if: + /// - It has a private parameter or return type; Mockito cannot write the + /// signature of such a method. + /// - It has a non-nullable type variable return type, for example `T m()`. + /// Mockito cannot generate dummy return values for unknown types. + void _checkMethodsToStubAreValid( + ClassElement classElement, LibraryElement entryLib) { + var className = classElement.name; + //var unstubbableErrorMessages = []; + + /*for (var method in classElement.methods) { + if (method.isPrivate || method.isStatic) continue; + _checkFunction(method.type, method.name, entryLib); + }*/ + var unstubbableErrorMessages = classElement.methods + .where((m) => !m.isPrivate && !m.isStatic) + .expand((m) => _checkFunction(m.type, m.name, className, entryLib)) + .toList(); + + if (unstubbableErrorMessages.isNotEmpty) { + var joinedMessages = + unstubbableErrorMessages.map((m) => ' $m').join('\n'); + throw InvalidMockitoAnnotationException( + 'Mockito cannot generate a valid mock class which implements ' + "'$className' for the following reasons:\n$joinedMessages"); + } + } + + List _checkFunction(analyzer.FunctionType function, String name, + String className, LibraryElement entryLib) { + var errorMessages = []; + var returnType = function.returnType; + if (returnType is analyzer.InterfaceType) { + if (returnType.element?.isPrivate ?? false) { + errorMessages + .add("The method '$className.$name' features a private return " + 'type, and cannot be stubbed.'); + } + } else if (returnType is analyzer.FunctionType) { + errorMessages + .addAll(_checkFunction(returnType, name, className, entryLib)); + } else if (returnType is analyzer.TypeParameterType) { + if (function.returnType is analyzer.TypeParameterType && + entryLib.typeSystem.isPotentiallyNonNullable(function.returnType)) { + errorMessages + .add("The method '$className.$name' features a non-nullable " + 'unknown return type, and cannot be stubbed.'); + } + } + + for (var parameter in function.parameters) { + var parameterType = parameter.type; + var parameterTypeElement = parameterType.element; + if (parameterType is analyzer.InterfaceType) { + if (parameterTypeElement?.isPrivate ?? false) { + // Technically, we can expand the type in the mock to something like + // `Object?`. However, until there is a decent use case, we will not + // generate such a mock. + errorMessages.add( + "The method '$className.$name' features a private parameter " + "type, '${parameterTypeElement.name}', and cannot be stubbed."); + } + } else if (parameterType is analyzer.FunctionType) { + errorMessages + .addAll(_checkFunction(parameterType, name, className, entryLib)); + } + } + + return errorMessages; + } + /// Return whether [type] is the Mock class declared by mockito. bool _isMockClass(analyzer.InterfaceType type) => type.element.name == 'Mock' && diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index 41e1f3e2a..dbf44915c 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -1130,10 +1130,12 @@ void main() { await _expectSingleNonNullableOutput( dedent(r''' class Foo { - T Function(T) m() => (int i, [String s]) {}; + T? Function(T) m() => (int i, [String s]) {}; } '''), - _containsAllOf('T Function(T) m() =>', + // TODO(srawlins): This output is invalid: `T __p0` is out of the scope + // where T is defined. + _containsAllOf('T? Function(T) m() =>', 'super.noSuchMethod(Invocation.method(#m, []), (T __p0) => null);'), ); }); @@ -1192,6 +1194,105 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'private return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + _Bar m(int a); + } + class _Bar {} + '''), + }, + message: contains( + "The method 'Foo.m' features a private return type, and cannot be " + 'stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a return ' + 'function type, with a private return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + _Bar Function() m(); + } + class _Bar {} + '''), + }, + message: contains("The method 'Foo.m' features a private return type, " + 'and cannot be stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'private parameter type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m(_Bar a) {} + } + class _Bar {} + '''), + }, + message: contains( + "The method 'Foo.m' features a private parameter type, '_Bar', and " + 'cannot be stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'function parameter type, with a private return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m(_Bar Function() a) {} + } + class _Bar {} + '''), + }, + message: contains( + "The method 'Foo.m' features a private return type, and cannot be " + 'stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a return ' + 'function type, with a private parameter type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + Function(_Bar) m(); + } + class _Bar {} + '''), + }, + message: contains( + "The method 'Foo.m' features a private parameter type, '_Bar', and " + 'cannot be stubbed.'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a ' 'non-nullable class-declared type variable return type', () async { @@ -1206,9 +1307,8 @@ void main() { '''), }, message: contains( - "Mockito cannot generate a valid mock class which implements 'Foo'. " - "The method(s) 'Foo.m', which each return a non-nullable value of " - 'unknown type, cannot be stubbed.'), + "The method 'Foo.m' features a non-nullable unknown return type, and " + 'cannot be stubbed.'), ); }); @@ -1226,9 +1326,8 @@ void main() { '''), }, message: contains( - "Mockito cannot generate a valid mock class which implements 'Foo'. " - "The method(s) 'Foo.m', which each return a non-nullable value of " - 'unknown type, cannot be stubbed.'), + "The method 'Foo.m' features a non-nullable unknown return type, and " + 'cannot be stubbed.'), ); }); @@ -1247,9 +1346,8 @@ void main() { '''), }, message: contains( - "Mockito cannot generate a valid mock class which implements 'Foo'. " - "The method(s) 'Foo.m', which each return a non-nullable value of " - 'unknown type, cannot be stubbed.'), + "The method 'Foo.m' features a non-nullable unknown return type, and " + 'cannot be stubbed.'), ); }); From 7033cb811fd77ce96ea8278d127a1e205e4ab60c Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 25 Jun 2020 12:51:22 -0400 Subject: [PATCH 204/607] MockBuilder: refactor much of the class-gathering code into a class. The class is not terribly complicated. It contains the entryLib which was previously a parameter to almost every other MockBuilder method. This shrinks the build method, and isolates the task of gathering class names from @GenerateMocks annotations. This will also make the next step much easier: named mocks with possible type parameters in GenerateMock annotations, like `@GenerateMock(Of>(), as: #MockFooOfString)`. PiperOrigin-RevId: 318290959 --- pkgs/mockito/lib/src/builder.dart | 114 +++++++++++++++++------------- 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 412a51266..ebf98b758 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -38,11 +38,58 @@ import 'package:meta/meta.dart'; /// 'foo.mocks.dart' will be created. class MockBuilder implements Builder { @override - Future build(BuildStep buildStep) async { + Future build(BuildStep buildStep) async { final entryLib = await buildStep.inputLibrary; if (entryLib == null) return; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); + final mockTargetGatherer = _MockTargetGatherer(entryLib); + + final mockLibrary = Library((b) { + var mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._classesToMock, + sourceLibIsNonNullable: sourceLibIsNonNullable, + typeProvider: entryLib.typeProvider, + typeSystem: entryLib.typeSystem); + b.body.addAll(mockLibraryInfo.fakeClasses); + b.body.addAll(mockLibraryInfo.mockClasses); + }); + + if (mockLibrary.body.isEmpty) { + // Nothing to mock here! + return; + } + + final emitter = + DartEmitter.scoped(useNullSafetySyntax: sourceLibIsNonNullable); + final mockLibraryContent = + DartFormatter().format(mockLibrary.accept(emitter).toString()); + + await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); + } + + @override + final buildExtensions = const { + '.dart': ['.mocks.dart'] + }; +} + +/// This class gathers and verifies mock targets referenced in `GenerateMocks` +/// annotations. +// TODO(srawlins): This also needs to gather mock targets (with overridden +// names, type arguments, etc.) found in `GenerateMock` annotations. +class _MockTargetGatherer { + final LibraryElement _entryLib; + + final List _classesToMock; + + _MockTargetGatherer._(this._entryLib, this._classesToMock) { + _checkClassesToMockAreValid(); + } + + /// Searches the top-level elements of [entryLib] for `GenerateMocks` + /// annotations and creates a [_MockTargetGatherer] with all of the classes + /// identified as mocking targets. + factory _MockTargetGatherer(LibraryElement entryLib) { final objectsToMock = {}; for (final element in entryLib.topLevelElements) { @@ -70,28 +117,7 @@ class MockBuilder implements Builder { var classesToMock = _mapAnnotationValuesToClasses(objectsToMock, entryLib.typeProvider); - _checkClassesToMockAreValid(classesToMock, entryLib); - - final mockLibrary = Library((b) { - var mockLibraryInfo = _MockLibraryInfo(classesToMock, - sourceLibIsNonNullable: sourceLibIsNonNullable, - typeProvider: entryLib.typeProvider, - typeSystem: entryLib.typeSystem); - b.body.addAll(mockLibraryInfo.fakeClasses); - b.body.addAll(mockLibraryInfo.mockClasses); - }); - - if (mockLibrary.body.isEmpty) { - // Nothing to mock here! - return; - } - - final emitter = - DartEmitter.scoped(useNullSafetySyntax: sourceLibIsNonNullable); - final mockLibraryContent = - DartFormatter().format(mockLibrary.accept(emitter).toString()); - - await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); + return _MockTargetGatherer._(entryLib, classesToMock); } /// Map the values passed to the GenerateMocks annotation to the classes which @@ -100,7 +126,7 @@ class MockBuilder implements Builder { /// This function is responsible for ensuring that each value is an /// appropriate target for mocking. It will throw an /// [InvalidMockitoAnnotationException] under various conditions. - List _mapAnnotationValuesToClasses( + static List _mapAnnotationValuesToClasses( Iterable objectsToMock, TypeProvider typeProvider) { var classesToMock = []; @@ -144,11 +170,11 @@ class MockBuilder implements Builder { return classesToMock; } - void _checkClassesToMockAreValid( - List classesToMock, LibraryElement entryLib) { - var classesInEntryLib = entryLib.topLevelElements.whereType(); + void _checkClassesToMockAreValid() { + var classesInEntryLib = + _entryLib.topLevelElements.whereType(); var classNamesToMock = {}; - for (var class_ in classesToMock) { + for (var class_ in _classesToMock) { var name = class_.element.name; if (classNamesToMock.containsKey(name)) { var firstSource = classNamesToMock[name].source.fullName; @@ -186,7 +212,7 @@ class MockBuilder implements Builder { 'appears to already be mocked inline: ${preexistingMock.name}'); } - _checkMethodsToStubAreValid(element, entryLib); + _checkMethodsToStubAreValid(element); }); } @@ -198,18 +224,11 @@ class MockBuilder implements Builder { /// signature of such a method. /// - It has a non-nullable type variable return type, for example `T m()`. /// Mockito cannot generate dummy return values for unknown types. - void _checkMethodsToStubAreValid( - ClassElement classElement, LibraryElement entryLib) { + void _checkMethodsToStubAreValid(ClassElement classElement) { var className = classElement.name; - //var unstubbableErrorMessages = []; - - /*for (var method in classElement.methods) { - if (method.isPrivate || method.isStatic) continue; - _checkFunction(method.type, method.name, entryLib); - }*/ var unstubbableErrorMessages = classElement.methods .where((m) => !m.isPrivate && !m.isStatic) - .expand((m) => _checkFunction(m.type, m.name, className, entryLib)) + .expand((m) => _checkFunction(m.type, m.name, className)) .toList(); if (unstubbableErrorMessages.isNotEmpty) { @@ -221,8 +240,8 @@ class MockBuilder implements Builder { } } - List _checkFunction(analyzer.FunctionType function, String name, - String className, LibraryElement entryLib) { + List _checkFunction( + analyzer.FunctionType function, String name, String className) { var errorMessages = []; var returnType = function.returnType; if (returnType is analyzer.InterfaceType) { @@ -232,11 +251,10 @@ class MockBuilder implements Builder { 'type, and cannot be stubbed.'); } } else if (returnType is analyzer.FunctionType) { - errorMessages - .addAll(_checkFunction(returnType, name, className, entryLib)); + errorMessages.addAll(_checkFunction(returnType, name, className)); } else if (returnType is analyzer.TypeParameterType) { if (function.returnType is analyzer.TypeParameterType && - entryLib.typeSystem.isPotentiallyNonNullable(function.returnType)) { + _entryLib.typeSystem.isPotentiallyNonNullable(function.returnType)) { errorMessages .add("The method '$className.$name' features a non-nullable " 'unknown return type, and cannot be stubbed.'); @@ -256,8 +274,7 @@ class MockBuilder implements Builder { "type, '${parameterTypeElement.name}', and cannot be stubbed."); } } else if (parameterType is analyzer.FunctionType) { - errorMessages - .addAll(_checkFunction(parameterType, name, className, entryLib)); + errorMessages.addAll(_checkFunction(parameterType, name, className)); } } @@ -268,11 +285,6 @@ class MockBuilder implements Builder { bool _isMockClass(analyzer.InterfaceType type) => type.element.name == 'Mock' && type.element.source.fullName.endsWith('lib/src/mock.dart'); - - @override - final buildExtensions = const { - '.dart': ['.mocks.dart'] - }; } class _MockLibraryInfo { @@ -500,7 +512,7 @@ class _MockLibraryInfo { // This class is unknown; we must likely generate a fake class, and return // an instance here. - return _dummyValueImplementing(type); + return _dummyValueImplementing(type as analyzer.InterfaceType); } Expression _dummyFunctionValue(analyzer.FunctionType type) { From 6da9bfe6a419240ef43e611c8cd1bbc551c6b565 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 26 Jun 2020 16:22:59 -0400 Subject: [PATCH 205/607] MockBuilder: Improve private type detection. In particular, these new error states are checked: * Private type arguments in methods, like `List<_C> m(List<_C> a);` * Private type parameter bounds in methods, like `m();` * Private type parameter bounds in classes, like `class C {}` PiperOrigin-RevId: 318532151 --- pkgs/mockito/lib/src/builder.dart | 107 ++++++++++++++++++++++++---- pkgs/mockito/test/builder_test.dart | 80 +++++++++++++++++++++ 2 files changed, 174 insertions(+), 13 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index ebf98b758..d8aaacbfa 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -155,6 +155,16 @@ class _MockTargetGatherer { 'The "classes" argument includes a private type: ' '${elementToMock.displayName}.'); } + var typeParameterErrors = + _checkTypeParameters(elementToMock.typeParameters, elementToMock); + if (typeParameterErrors.isNotEmpty) { + var joinedMessages = + typeParameterErrors.map((m) => ' $m').join('\n'); + throw InvalidMockitoAnnotationException( + 'Mockito cannot generate a valid mock class which implements ' + "'${elementToMock.displayName}' for the following reasons:\n" + '$joinedMessages'); + } classesToMock.add(typeToMock); } else if (elementToMock is GenericFunctionTypeElement && elementToMock.enclosingElement is FunctionTypeAliasElement) { @@ -220,15 +230,15 @@ class _MockTargetGatherer { /// stubbing candidates. /// /// A method is not valid for stubbing if: - /// - It has a private parameter or return type; Mockito cannot write the - /// signature of such a method. + /// - It has a private type anywhere in its signature; Mockito cannot override + /// such a method. /// - It has a non-nullable type variable return type, for example `T m()`. /// Mockito cannot generate dummy return values for unknown types. void _checkMethodsToStubAreValid(ClassElement classElement) { var className = classElement.name; var unstubbableErrorMessages = classElement.methods .where((m) => !m.isPrivate && !m.isStatic) - .expand((m) => _checkFunction(m.type, m.name, className)) + .expand((m) => _checkFunction(m.type, m)) .toList(); if (unstubbableErrorMessages.isNotEmpty) { @@ -240,24 +250,33 @@ class _MockTargetGatherer { } } + /// Checks [function] for properties that would make it un-stubbable. + /// + /// Types are checked in the following positions: + /// - return type + /// - parameter types + /// - bounds of type parameters + /// - type arguments List _checkFunction( - analyzer.FunctionType function, String name, String className) { + analyzer.FunctionType function, Element enclosingElement) { var errorMessages = []; var returnType = function.returnType; if (returnType is analyzer.InterfaceType) { if (returnType.element?.isPrivate ?? false) { - errorMessages - .add("The method '$className.$name' features a private return " - 'type, and cannot be stubbed.'); + errorMessages.add( + '${enclosingElement.fullName} features a private return type, and ' + 'cannot be stubbed.'); } + errorMessages.addAll( + _checkTypeArguments(returnType.typeArguments, enclosingElement)); } else if (returnType is analyzer.FunctionType) { - errorMessages.addAll(_checkFunction(returnType, name, className)); + errorMessages.addAll(_checkFunction(returnType, enclosingElement)); } else if (returnType is analyzer.TypeParameterType) { if (function.returnType is analyzer.TypeParameterType && _entryLib.typeSystem.isPotentiallyNonNullable(function.returnType)) { errorMessages - .add("The method '$className.$name' features a non-nullable " - 'unknown return type, and cannot be stubbed.'); + .add('${enclosingElement.fullName} features a non-nullable unknown ' + 'return type, and cannot be stubbed.'); } } @@ -270,14 +289,62 @@ class _MockTargetGatherer { // `Object?`. However, until there is a decent use case, we will not // generate such a mock. errorMessages.add( - "The method '$className.$name' features a private parameter " - "type, '${parameterTypeElement.name}', and cannot be stubbed."); + '${enclosingElement.fullName} features a private parameter type, ' + "'${parameterTypeElement.name}', and cannot be stubbed."); } + errorMessages.addAll( + _checkTypeArguments(parameterType.typeArguments, enclosingElement)); } else if (parameterType is analyzer.FunctionType) { - errorMessages.addAll(_checkFunction(parameterType, name, className)); + errorMessages.addAll(_checkFunction(parameterType, enclosingElement)); } } + errorMessages + .addAll(_checkTypeParameters(function.typeFormals, enclosingElement)); + errorMessages + .addAll(_checkTypeArguments(function.typeArguments, enclosingElement)); + + return errorMessages; + } + + /// Checks the bounds of [typeParameters] for properties that would make the + /// enclosing method un-stubbable. + static List _checkTypeParameters( + List typeParameters, Element enclosingElement) { + var errorMessages = []; + for (var element in typeParameters) { + var typeParameter = element.bound; + if (typeParameter == null) continue; + if (typeParameter is analyzer.InterfaceType) { + if (typeParameter.element?.isPrivate ?? false) { + errorMessages.add( + '${enclosingElement.fullName} features a private type parameter ' + 'bound, and cannot be stubbed.'); + } + } + } + return errorMessages; + } + + /// Checks [typeArguments] for properties that would make the enclosing + /// method un-stubbable. + /// + /// See [_checkMethodsToStubAreValid] for what properties make a function + /// un-stubbable. + List _checkTypeArguments( + List typeArguments, Element enclosingElement) { + var errorMessages = []; + for (var typeArgument in typeArguments) { + if (typeArgument is analyzer.InterfaceType) { + if (typeArgument.element?.isPrivate ?? false) { + errorMessages.add( + '${enclosingElement.fullName} features a private type argument, ' + 'and cannot be stubbed.'); + } + } else if (typeArgument is analyzer.FunctionType) { + errorMessages.addAll(_checkFunction(typeArgument, enclosingElement)); + } + } return errorMessages; } @@ -764,3 +831,17 @@ class InvalidMockitoAnnotationException implements Exception { /// A [MockBuilder] instance for use by `build.yaml`. Builder buildMocks(BuilderOptions options) => MockBuilder(); + +extension on Element { + /// Returns the "full name" of a class or method element. + String get fullName { + if (this is ClassElement) { + return "The class '$name'"; + } else if (this is MethodElement) { + var className = enclosingElement.name; + return "The method '$className.$name'"; + } else { + return 'unknown element'; + } + } +} diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder_test.dart index dbf44915c..8ca269b95 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder_test.dart @@ -1214,6 +1214,26 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a method with a return ' + 'type with private type arguments', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + List<_Bar> m(int a); + } + class _Bar {} + '''), + }, + message: contains( + "The method 'Foo.m' features a private type argument, and cannot be " + 'stubbed.'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a return ' 'function type, with a private return type', () async { @@ -1253,6 +1273,26 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'parameter with private type arguments', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m(List<_Bar> a) {} + } + class _Bar {} + '''), + }, + message: contains( + "The method 'Foo.m' features a private type argument, and cannot be " + 'stubbed.'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a ' 'function parameter type, with a private return type', () async { @@ -1293,6 +1333,46 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a type parameter with a ' + 'private bound', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m(int a) {} + } + class _Bar {} + '''), + }, + message: contains( + "The class 'Foo' features a private type parameter bound, and cannot " + 'be stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'type parameter with a private bound', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m(int a) {} + } + class _Bar {} + '''), + }, + message: contains( + "The method 'Foo.m' features a private type parameter bound, and " + 'cannot be stubbed.'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a ' 'non-nullable class-declared type variable return type', () async { From f767d92ed74134ac06013e910d05ce7750238d8f Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 6 Jul 2020 19:59:25 -0400 Subject: [PATCH 206/607] Bump dependency version constraints of analyzer, meta, pedantic. The analyzer constraint needed to be bumped; errors were found using `pub downgrade`. The meta and pedantic APIs can introduce conflicting members (`unawaited`) in certain combinations. PiperOrigin-RevId: 319884416 --- pkgs/mockito/pubspec.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index f89993c01..9a89f1b81 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -5,21 +5,21 @@ description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.4.0 <3.0.0' + sdk: '>=2.7.0 <3.0.0' dependencies: - analyzer: ^0.38.0 - build: ^1.1.3 - code_builder: ^3.2.0 + analyzer: ^0.39.11 + build: ^1.3.0 + code_builder: ^3.4.0 collection: ^1.1.0 dart_style: ^1.3.0 matcher: ^0.12.3 - meta: ^1.0.4 + meta: '>=1.0.4 <1.2.0' test_api: ^0.2.1 dev_dependencies: build_runner: ^1.0.0 - build_test: ^0.10.0 + build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' - pedantic: ^1.3.0 + pedantic: '>=1.3.0 <1.9.1' test: ^1.5.1 From c8774f774f96b0a3ec3482760b6f77ed936e3a1d Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 7 Jul 2020 14:07:57 -0700 Subject: [PATCH 207/607] Travis: Don't test on stable. Need to wait for 2.9.0 --- pkgs/mockito/.travis.yml | 51 +++++++++++++++++++++------------------ pkgs/mockito/pubspec.yaml | 5 +++- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 26f0d103f..bd700e133 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -11,30 +11,33 @@ stages: # 3. Then run tests compiled via dartdevc and dart2js. jobs: include: - - stage: presubmit - name: "2.4.0 analyzer" - script: ./tool/travis.sh dartanalyzer - dart: 2.4.0 - - stage: presubmit - name: "2.4.0 vm test" - script: ./tool/travis.sh vm_test - dart: 2.4.0 - - stage: build - name: "2.4.0 DDC build" - script: ./tool/travis.sh dartdevc_build - dart: 2.4.0 - - stage: testing - name: "2.4.0 DDC test" - script: ./tool/travis.sh dartdevc_test - dart: 2.4.0 - - stage: testing - name: "2.4.0 dart2js test" - script: ./tool/travis.sh dart2js_test - dart: 2.4.0 - - stage: testing - name: "2.4.0 code coverage" - script: ./tool/travis.sh coverage - dart: 2.4.0 + # mockito tests cannot run on stable until stable is >= 2.9.0, as "2.9.0" + # is the version required by analyzer. See + # https://github.com/dart-lang/build/issues/2685. + #- stage: presubmit + # name: "2.7.0 analyzer" + # script: ./tool/travis.sh dartanalyzer + # dart: 2.7.0 + #- stage: presubmit + # name: "2.7.0 vm test" + # script: ./tool/travis.sh vm_test + # dart: 2.7.0 + #- stage: build + # name: "2.7.0 DDC build" + # script: ./tool/travis.sh dartdevc_build + # dart: 2.7.0 + #- stage: testing + # name: "2.7.0 DDC test" + # script: ./tool/travis.sh dartdevc_test + # dart: 2.7.0 + #- stage: testing + # name: "2.7.0 dart2js test" + # script: ./tool/travis.sh dart2js_test + # dart: 2.7.0 + #- stage: testing + # name: "2.7.0 code coverage" + # script: ./tool/travis.sh coverage + # dart: 2.7.0 - stage: presubmit name: "dev dartfmt" diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 9a89f1b81..c44c9701e 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.1.0 - dart_style: ^1.3.0 + dart_style: ^1.3.6 matcher: ^0.12.3 meta: '>=1.0.4 <1.2.0' test_api: ^0.2.1 @@ -23,3 +23,6 @@ dev_dependencies: build_web_compilers: '>=1.0.0 <3.0.0' pedantic: '>=1.3.0 <1.9.1' test: ^1.5.1 + +dependency_overrides: + build_resolvers: ^1.3.10 From 70b1a5051e7c332149488ba50e7d4f918d84d0e5 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 13 Jul 2020 19:46:17 -0400 Subject: [PATCH 208/607] Sync small changes from GitHub; add NULL_SAFETY_README PiperOrigin-RevId: 321290594 --- pkgs/mockito/CHANGELOG.md | 18 +- pkgs/mockito/NULL_SAFETY_README.md | 259 +++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 9 deletions(-) create mode 100644 pkgs/mockito/NULL_SAFETY_README.md diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 5f40fbc8f..897214148 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -24,25 +24,25 @@ ## 4.0.0 * Replace the dependency on the - _[test](https://pub.dev/packages/test)_ package with a dependency on - the new _[test_api](https://pub.dev/packages/test_api)_ package. - This dramatically reduces mockito's transitive dependencies. + _[test](https://pub.dev/packages/test)_ package with a dependency on the new + _[test_api](https://pub.dev/packages/test_api)_ package. This dramatically + reduces mockito's transitive dependencies. This bump can result in runtime errors when coupled with a version of the test package older than 1.4.0. ## 3.0.2 -* Rollback the _[test_api](https://pub.dev/packages/test_api)_ part of - the 3.0.1 release. This was breaking tests that use Flutter's current test - tools, and will instead be released as part of Mockito 4.0.0. +* Rollback the _[test_api](https://pub.dev/packages/test_api)_ part of the 3.0.1 + release. This was breaking tests that use Flutter's current test tools, and + will instead be released as part of Mockito 4.0.0. ## 3.0.1 * Replace the dependency on the - _[test](https://pub.dev/packages/test)_ package with a dependency on - the new _[test_api](https://pub.dev/packages/test_api)_ package. - This dramatically reduces mockito's transitive dependencies. + _[test](https://pub.dev/packages/test)_ package with a dependency on the new + _[test_api](https://pub.dev/packages/test_api)_ package. This dramatically + reduces mockito's transitive dependencies. * Internal improvements to tests and examples. ## 3.0.0 diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md new file mode 100644 index 000000000..cb8013466 --- /dev/null +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -0,0 +1,259 @@ +# Null Safety + +Dart's new null safety type system allows method parameter types and method +return types to be non-nullable. For example: + +```dart +class HttpServer { + Uri start(int port) { + // ... + } +} +``` + +The method, `start`, takes a _non_-nullable int argument, and returns a +_non_-nullable Url. Under the null safety type system, it is illegal to pass +`null` to `start`, and it is illegal for `start` (or any overriding methods in +any sub-classes) to return `null`. This plays havoc with the mechanisms that +Mockito uses to stub methods. + + + +Here is the standard way of defining a mock for the Foo class: + +```dart +class MockHttpServer extends Mock implements HttpServer {} +``` + +And here is the standard way of stubbing the `start` method. + +```dart +var server = MockHttpServer(); +var uri = Uri.parse('http://localhost:8080'); +when(server.start(any)).thenReturn(uri); +``` + +This code is, unfortunately, illegal under null safety in two ways. For details, +see the section at the bottom, **Problems with typical mocking and stubbing**. + +## Solutions + +There are two ways to write a mock class that supports non-nullable types: we +can use the [build_runner] package to _generate_ a mock class, or we can +manually implement it, overriding specific methods to handle non-nullability. + +### Code generation + +Mockito provides a "one size fits all" code-generating solution for packages +that use null safety which can generate a mock for any class. To direct Mockito +to generate mock classes, use the new `@GenerateMocks` annotation, and import +the generated mocks library. Let's continue with the HTTP server example: + +```dart +// http_server.dart: +class HttpServer { + Uri start(int port) { + // ... + } +} +``` + +```dart +// http_server_test.dart: +import 'package:test/test.dart'; +import 'http_server.dart'; + +void main() { + test('test', () { + var httpServer = MockHttpServer(); + // We want to stub the `start` method. + }); +} +``` + +In order to generate a mock for the HttpServer class, we edit +`http_server_test.dart`: + +1. import mockito's annotations library, +2. annotate a top-level library member (like an import, or the main function) + with `@GenerateMocks`, +3. import the generated mocks library, +4. change `httpServer` from an HttpServer to the generated class, + MockHttpServer. + +```dart +// http_server_test.dart: +import 'package:mockito/annotations.dart'; +import 'package:test/test.dart'; +import 'http_server.dart'; +import 'http_server_test.mocks.dart'; + +@GenerateMocks([HttpServer]) +void main() { + test('test', () { + var httpServer = MockHttpServer(); + // We want to stub the `start` method. + }); +} +``` + +We need to then run build_runner to generate the new library: + + + +```shell +pub run build_runner build +``` + +build_runner will generate the `http_server_test.mocks.dart` file which we +import in `http_server_test.dart`. The path is taken directly from the file in +which `@GenerateMocks` was found, changing the `.dart` suffix to `.mocks.dart`. +If we previously had a shared mocks file which declared mocks to be used by +multiple tests, for example named `shared_mocks.dart`, we can edit that file to +generate mocks, and then import `shared_mocks.mocks.dart` in the tests which +previously imported `shared_mocks.dart`. + +### Manual mock implementaion + +**In the general case, we strongly recommend generating mocks with the above +method.** However, there may be cases where a manual mock implementation is more +desirable. + +Perhaps code generation with [build_runner] is not a good route for our package. +Perhaps we wish to mock just one class which has mostly nullable parameter types +and return types. In this case, it may not be too onerous to implement the mock +class manually. + +#### The general process + +Only public methods (including getters, setters, and operators) which either +have a non-nullable return type, or a parameter with a non-nullable type, need +to be overridden. For each such method: + +1. Override the method with a new declaration inside the mock class. +2. For each non-nullable parameter, expand its type to be nullable. +3. Call `super.noSuchMethod`, passing in an Invocation object which includes + all of the values passed to the override. +4. If the return type is non-nullable, pass a second argument to + `super.noSuchMethod`, a value which can function as a return value. + +Let's look at an example HttpServer class again: + +```dart +class HttpServer { + void start(int port) { ... } + + Uri get uri { ... } +} +``` + +Before null safety, implementing a mock class was a simple one-liner: + +```dart +class MockHttpServer extends Mock implements HttpServer {} +``` + +This class implements each of the methods in HttpServer's public interface via +the `noSuchMethod` method found in the Mock class. Under null safety, we need to +override that implementation for every method which has one or more non-nullable +parameters, or which has a non-nullable return type. + +#### Manually override a method with a non-nullable parameter type. + +First let's override `start` with an implementation that expands the single +parameter's type to be nullable, and call `super.noSuchMethod` to handle the +stubbing and verifying: + +```dart +class MockHttpServer extends Mock implements HttpServer { + @override + void start(int? port) => + super.noSuchMethod(Invocation.method(#start, [port])); +} +``` + +There is a lot going on in this snippet. Let's examine it carefully: + +1. We expand every non-nullable parameter to be nullable. `HttpServer.start`'s + `port` parameter is a non-nullable int, so our override expands this to + parameter to be a nullable int. This is done by adding a `?` after the + parameter type. +2. We just write a simple arrow function which calls `super.noSuchMethod`, + passing in the contents of the current call to `MockHttpServer.start`. In + this case, the single argument is an [Invocation]. +3. Since `start` is a method, we use the [`Invocation.method`] constructor. +4. The first argument to `Invocation.method` is the name of the method, as a + Symbol: `#start`. +5. The second argument to `Invocation.method` is the exact list of positional + arguments, unchanged, which was passed to `MockHttpServer.start`: `[port]`. + +That's it! The override implementation is all boilerplate. See the API for +[`Invocation.method`] to see how named parameters are passed. See the other +[Invocation] constructors to see how to implement an override for an operator or +a setter. + +#### Manually override a method with a non-nullable return type. + +Next let's override `get uri`. It would be illegal to override a getter which +returns a non-nullable Uri with a getter which returns a _nullable_ Uri. +Instead, the override must use an actual Uri object to satisfy the type +contract; this getter will also just call `super.noSuchMethod`, but will pass an +additional argument: + +```dart +class MockHttpServer extends Mock implements HttpServer { + @override + Uri get uri => + super.noSuchMethod(Invocation.getter(#uri), Uri.http('example.org', '/')); +} +``` + +This looks quite similar to the override which expands non-nullable parameters. +The key difference is the second argument passed to `super.noSuchMethod`: the +real Uri object. During stubbing and verification, `Mock.noSuchMethod` will +return this value from its own implementation of `get uri`, to avoid any runtime +error. + +This return value is specified for exactly one purpose: _to satisfy the +non-nullable return type_. This return value must not be confused with the stub +return values used in `thenReturn` or `thenAnswer`. Let's look at some examples: + +```dart +var httpServer = MockHttpServer(); +when(httpServer.uri).thenReturn(Uri.http('dart.dev', '/')); +httpServer.uri; +verify(httpServer.uri).called(1); +``` + +During stubbing (`when`) and verification (`verify`), the value returned by +`httpServer.uri` is immediately dropped and never used. In a real call, like the +middle line, `Mock.noSuchMethod` searches for a matching stubbed call and +returns the associated return value. Our override of `get uri` then returns that +value as well. + +## Problems with typical mocking and stubbing + +Why does mocking have to change under null safety? What about mockito's regular +mocking depends so much on `null`? + +### Argument matchers + +Mockito's helpful argument matchers like `any`, `argThat`, `captureAny`, etc. +all return `null`. In the code above, `null` is being passed to `start`. `null` +is used because, _before_ null safety, it is the only value that is legally +assignable to any type. Under null safety, however, it is illegal to pass `null` +where a non-nullable int is expected. + +### Return types + +MockHttpServer's implementation of `start` is inherited from mockito's `Mock`. +`Mock` implements `start` by declaring a `noSuchMethod` override. During a +`when` call, or a `verify` call, the `noSuchMethod` implementation always +returns `null`. Again, prior to null safety, `null` is the only value that fits +any return type, but under null safety, the `start` method must not return +`null`. + +[build_runner]: https://pub.dev/packages/build_runner + +[Invocation]: https://api.dart.dev/stable/dart-core/Invocation-class.html +[`Invocation.method`]: https://api.dart.dev/stable/dart-core/Invocation/Invocation.method.html From 31c83e8ba806e57b095899548b44df4ac42bd5d2 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 16 Jul 2020 10:24:30 -0400 Subject: [PATCH 209/607] MockBuilder: Add GenerateMocks customClasses field and constructor parameter. This field allows more customizable mock generation: * A mock class name can be specified, in order to avoid name collisions. * A mock class can extend a class with type arguments. E.g. @GenerateMocks([], customMocks: MockSpec>()) generates class MockFoo implements Mock extends Foo {} PiperOrigin-RevId: 321560783 --- pkgs/mockito/lib/annotations.dart | 56 ++- pkgs/mockito/lib/src/builder.dart | 265 ++++++---- .../auto_mocks_test.dart} | 78 ++- .../test/builder/custom_mocks_test.dart | 474 ++++++++++++++++++ 4 files changed, 733 insertions(+), 140 deletions(-) rename pkgs/mockito/test/{builder_test.dart => builder/auto_mocks_test.dart} (97%) create mode 100644 pkgs/mockito/test/builder/custom_mocks_test.dart diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index bf1f5daad..551778eba 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -12,8 +12,62 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// An annotation to direct Mockito to generate mock classes. +/// +/// During [code generation][NULL_SAFETY_README], Mockito will generate a +/// `Mock{Type} extends Mock` class for each class to be mocked, in +/// `{name}.mocks.dart`, where `{name}` is the basename of the file in which +/// `@GenerateMocks` is used. +/// +/// For example, if `@GenerateMocks([Foo])` is found at the top-level of a Dart +/// library, `foo_test.dart`, then Mockito will generate +/// `class MockFoo extends Mock implements Foo` in a new library, +/// `foo_test.mocks.dart`. +/// +/// If the class-to-mock is generic, then the mock will be identically generic. +/// For example, given the class `class Foo`, Mockito will generate +/// `class MockFoo extends Mock implements Foo`. +/// +/// Custom mocks can be generated with the `customMocks:` named argument. Each +/// mock is specified with a [MockSpec] object. +/// +/// [NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md class GenerateMocks { final List classes; + final List customMocks; - const GenerateMocks(this.classes); + const GenerateMocks(this.classes, {this.customMocks = const []}); +} + +/// A specification of how to mock a specific class. +/// +/// The type argument `T` is the class-to-mock. If this class is generic, and no +/// explicit type arguments are given, then the mock class is generic. +/// If the class is generic, and `T` has been specified with type argument(s), +/// the mock class is not generic, and it extends the mocked class using the +/// given type arguments. +/// +/// The name of the mock class is either specified with the `as` named argument, +/// or is the name of the class being mocked, prefixed with 'Mock'. +/// +/// For example, given the generic class, `class Foo`, then this +/// annotation: +/// +/// ```dart +/// @GenerateMocks([], customMocks: [ +/// MockSpec(), +/// MockSpec>(as: #MockFooOfInt), +/// ]) +/// ``` +/// +/// directs Mockito to generate two mocks: +/// `class MockFoo extends Mocks implements Foo` and +/// `class MockFooOfInt extends Mock implements Foo`. +// TODO(srawlins): Document this in NULL_SAFETY_README.md. +// TODO(srawlins): Add 'returnNullOnMissingStub'. +// TODO(srawlins): Add 'mixingIn'. +class MockSpec { + final Symbol mockName; + + const MockSpec({Symbol as}) : mockName = as; } diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index d8aaacbfa..fe21e9f5c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:analyzer/dart/element/type_provider.dart'; @@ -46,7 +45,7 @@ class MockBuilder implements Builder { final mockTargetGatherer = _MockTargetGatherer(entryLib); final mockLibrary = Library((b) { - var mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._classesToMock, + var mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets, sourceLibIsNonNullable: sourceLibIsNonNullable, typeProvider: entryLib.typeProvider, typeSystem: entryLib.typeSystem); @@ -73,16 +72,26 @@ class MockBuilder implements Builder { }; } +class _MockTarget { + /// The class to be mocked. + final analyzer.InterfaceType classType; + + /// The desired name of the mock class. + final String mockName; + + _MockTarget(this.classType, this.mockName); + + ClassElement get classElement => classType.element; +} + /// This class gathers and verifies mock targets referenced in `GenerateMocks` /// annotations. -// TODO(srawlins): This also needs to gather mock targets (with overridden -// names, type arguments, etc.) found in `GenerateMock` annotations. class _MockTargetGatherer { final LibraryElement _entryLib; - final List _classesToMock; + final List<_MockTarget> _mockTargets; - _MockTargetGatherer._(this._entryLib, this._classesToMock) { + _MockTargetGatherer._(this._entryLib, this._mockTargets) { _checkClassesToMockAreValid(); } @@ -90,34 +99,68 @@ class _MockTargetGatherer { /// annotations and creates a [_MockTargetGatherer] with all of the classes /// identified as mocking targets. factory _MockTargetGatherer(LibraryElement entryLib) { - final objectsToMock = {}; + final mockTargets = <_MockTarget>{}; for (final element in entryLib.topLevelElements) { // TODO(srawlins): Re-think the idea of multiple @GenerateMocks // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation == null) continue; - if (annotation.element is! ConstructorElement || - annotation.element.enclosingElement.name != 'GenerateMocks') { - continue; + if (annotation.element is! ConstructorElement) continue; + final annotationClass = annotation.element.enclosingElement.name; + // TODO(srawlins): check library as well. + if (annotationClass == 'GenerateMocks') { + mockTargets + .addAll(_mockTargetsFromGenerateMocks(annotation, entryLib)); } - final generateMocksValue = annotation.computeConstantValue(); - // TODO(srawlins): handle `generateMocksValue == null`? - // I am unable to think of a case which results in this situation. - final classesField = generateMocksValue.getField('classes'); - if (classesField.isNull) { - throw InvalidMockitoAnnotationException( - 'The GenerateMocks "classes" argument is missing, includes an ' - 'unknown type, or includes an extension'); - } - objectsToMock.addAll(classesField.toListValue()); } } - var classesToMock = - _mapAnnotationValuesToClasses(objectsToMock, entryLib.typeProvider); + return _MockTargetGatherer._(entryLib, mockTargets.toList()); + } - return _MockTargetGatherer._(entryLib, classesToMock); + static Iterable<_MockTarget> _mockTargetsFromGenerateMocks( + ElementAnnotation annotation, LibraryElement entryLib) { + final generateMocksValue = annotation.computeConstantValue(); + final classesField = generateMocksValue.getField('classes'); + if (classesField.isNull) { + throw InvalidMockitoAnnotationException( + 'The GenerateMocks "classes" argument is missing, includes an ' + 'unknown type, or includes an extension'); + } + final mockTargets = <_MockTarget>[]; + for (var objectToMock in classesField.toListValue()) { + final typeToMock = objectToMock.toTypeValue(); + if (typeToMock == null) { + throw InvalidMockitoAnnotationException( + 'The "classes" argument includes a non-type: $objectToMock'); + } + if (typeToMock.isDynamic) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock `dynamic`'); + } + final type = _determineDartType(typeToMock, entryLib.typeProvider); + final mockName = 'Mock${type.element.name}'; + mockTargets.add(_MockTarget(type, mockName)); + } + final customMocksField = generateMocksValue.getField('customMocks'); + if (customMocksField != null && !customMocksField.isNull) { + for (var mockSpec in customMocksField.toListValue()) { + final mockSpecType = mockSpec.type; + assert(mockSpecType.typeArguments.length == 1); + final typeToMock = mockSpecType.typeArguments.single; + if (typeToMock.isDynamic) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock `dynamic`; be sure to declare type ' + 'arguments on MockSpec(), in @GenerateMocks.'); + } + var type = _determineDartType(typeToMock, entryLib.typeProvider); + final mockName = mockSpec.getField('mockName').toSymbolValue() ?? + 'Mock${type.element.name}'; + mockTargets.add(_MockTarget(type, mockName)); + } + } + return mockTargets; } /// Map the values passed to the GenerateMocks annotation to the classes which @@ -126,89 +169,75 @@ class _MockTargetGatherer { /// This function is responsible for ensuring that each value is an /// appropriate target for mocking. It will throw an /// [InvalidMockitoAnnotationException] under various conditions. - static List _mapAnnotationValuesToClasses( - Iterable objectsToMock, TypeProvider typeProvider) { - var classesToMock = []; - - for (final objectToMock in objectsToMock) { - final typeToMock = objectToMock.toTypeValue(); - if (typeToMock == null) { + static analyzer.InterfaceType _determineDartType( + analyzer.DartType typeToMock, TypeProvider typeProvider) { + final elementToMock = typeToMock.element; + if (elementToMock is ClassElement) { + if (elementToMock.isEnum) { throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-type: $objectToMock'); + 'Mockito cannot mock an enum: ${elementToMock.displayName}'); } - - final elementToMock = typeToMock.element; - if (elementToMock is ClassElement) { - if (elementToMock.isEnum) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes an enum: ' - '${elementToMock.displayName}'); - } - if (typeProvider.nonSubtypableClasses.contains(elementToMock)) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-subtypable type: ' - '${elementToMock.displayName}. It is illegal to subtype this ' - 'type.'); - } - if (elementToMock.isPrivate) { - throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a private type: ' - '${elementToMock.displayName}.'); - } - var typeParameterErrors = - _checkTypeParameters(elementToMock.typeParameters, elementToMock); - if (typeParameterErrors.isNotEmpty) { - var joinedMessages = - typeParameterErrors.map((m) => ' $m').join('\n'); - throw InvalidMockitoAnnotationException( - 'Mockito cannot generate a valid mock class which implements ' - "'${elementToMock.displayName}' for the following reasons:\n" - '$joinedMessages'); - } - classesToMock.add(typeToMock); - } else if (elementToMock is GenericFunctionTypeElement && - elementToMock.enclosingElement is FunctionTypeAliasElement) { + if (typeProvider.nonSubtypableClasses.contains(elementToMock)) { throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a typedef: ' - '${elementToMock.enclosingElement.displayName}'); - } else { + 'Mockito cannot mock a non-subtypable type: ' + '${elementToMock.displayName}. It is illegal to subtype this ' + 'type.'); + } + if (elementToMock.isPrivate) { throw InvalidMockitoAnnotationException( - 'The "classes" argument includes a non-class: ' - '${elementToMock.displayName}'); + 'Mockito cannot mock a private type: ' + '${elementToMock.displayName}.'); } + var typeParameterErrors = + _checkTypeParameters(elementToMock.typeParameters, elementToMock); + if (typeParameterErrors.isNotEmpty) { + var joinedMessages = + typeParameterErrors.map((m) => ' $m').join('\n'); + throw InvalidMockitoAnnotationException( + 'Mockito cannot generate a valid mock class which implements ' + "'${elementToMock.displayName}' for the following reasons:\n" + '$joinedMessages'); + } + return typeToMock as analyzer.InterfaceType; + } else if (elementToMock is GenericFunctionTypeElement && + elementToMock.enclosingElement is FunctionTypeAliasElement) { + throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: ' + '${elementToMock.enclosingElement.displayName}'); + } else { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock a non-class: ${elementToMock.displayName}'); } - return classesToMock; } void _checkClassesToMockAreValid() { var classesInEntryLib = _entryLib.topLevelElements.whereType(); var classNamesToMock = {}; - for (var class_ in _classesToMock) { - var name = class_.element.name; + var uniqueNameSuggestion = + "use the 'customMocks' argument in @GenerateMocks to specify a unique " + 'name'; + for (final mockTarget in _mockTargets) { + var name = mockTarget.mockName; if (classNamesToMock.containsKey(name)) { var firstSource = classNamesToMock[name].source.fullName; - var secondSource = class_.element.source.fullName; - // TODO(srawlins): Support an optional @GenerateMocks API that allows - // users to choose names. One class might be named MockFoo and the other - // named MockPbFoo, for example. + var secondSource = mockTarget.classElement.source.fullName; throw InvalidMockitoAnnotationException( - 'The GenerateMocks "classes" argument contains two classes with ' - 'the same name: $name. One declared in $firstSource, the other in ' - '$secondSource.'); + 'Mockito cannot generate two mocks with the same name: $name (for ' + '${classNamesToMock[name].name} declared in $firstSource, and for ' + '${mockTarget.classElement.name} declared in $secondSource); ' + '$uniqueNameSuggestion.'); } - classNamesToMock[name] = class_.element as ClassElement; + classNamesToMock[name] = mockTarget.classElement; } classNamesToMock.forEach((name, element) { - var conflictingClass = classesInEntryLib.firstWhere( - (c) => c.name == 'Mock${element.name}', + var conflictingClass = classesInEntryLib.firstWhere((c) => c.name == name, orElse: () => null); if (conflictingClass != null) { throw InvalidMockitoAnnotationException( - 'The GenerateMocks "classes" argument contains a class which ' - 'conflicts with another class declared in this library: ' - '${conflictingClass.name}'); + 'Mockito cannot generate a mock with a name which conflicts with ' + 'another class declared in this library: ${conflictingClass.name}; ' + '$uniqueNameSuggestion.'); } var preexistingMock = classesInEntryLib.firstWhere( @@ -218,8 +247,9 @@ class _MockTargetGatherer { orElse: () => null); if (preexistingMock != null) { throw InvalidMockitoAnnotationException( - 'The GenerateMocks "classes" argument contains a class which ' - 'appears to already be mocked inline: ${preexistingMock.name}'); + 'The GenerateMocks annotation contains a class which appears to ' + 'already be mocked inline: ${preexistingMock.name}; ' + '$uniqueNameSuggestion.'); } _checkMethodsToStubAreValid(element); @@ -376,23 +406,48 @@ class _MockLibraryInfo { /// fake classes are added to the generated library. final fakedClassElements = []; - /// Build mock classes for [classesToMock], a list of classes obtained from a - /// `@GenerateMocks` annotation. - _MockLibraryInfo(List classesToMock, + /// Build mock classes for [mockTargets]. + _MockLibraryInfo(Iterable<_MockTarget> mockTargets, {this.sourceLibIsNonNullable, this.typeProvider, this.typeSystem}) { - for (final classToMock in classesToMock) { - mockClasses.add(_buildMockClass(classToMock)); + for (final mockTarget in mockTargets) { + mockClasses.add(_buildMockClass(mockTarget)); } } - Class _buildMockClass(analyzer.DartType dartType) { - final classToMock = dartType.element as ClassElement; - final className = dartType.name; - final mockClassName = 'Mock$className'; + bool _hasExplicitTypeArguments(analyzer.InterfaceType type) { + if (type.typeArguments == null) return false; + + // If it appears that one type argument was given, then they all were. This + // returns the wrong result when the type arguments given are all `dynamic`, + // or are each equal to the bound of the corresponding type parameter. There + // may not be a way to get around this. + for (var i = 0; i < type.typeArguments.length; i++) { + var typeArgument = type.typeArguments[i]; + var bound = + type.element.typeParameters[i].bound ?? typeProvider.dynamicType; + // If [type] was given to @GenerateMocks as a Type, and no explicit type + // argument is given, [typeArgument] is `dynamic` (_not_ the bound, as one + // might think). We determine that an explicit type argument was given if + // it is not `dynamic`. + // + // If, on the other hand, [type] was given to @GenerateMock as a type + // argument to `Of()`, and no type argument is given, [typeArgument] is + // the bound of the corresponding type paramter (dynamic or otherwise). We + // determine that an explicit type argument was given if [typeArgument] is + // is not [bound]. + if (!typeArgument.isDynamic && typeArgument != bound) return true; + } + return false; + } + + Class _buildMockClass(_MockTarget mockTarget) { + final typeToMock = mockTarget.classType; + final classToMock = mockTarget.classElement; + final className = classToMock.name; return Class((cBuilder) { cBuilder - ..name = mockClassName + ..name = mockTarget.mockName ..extend = refer('Mock', 'package:mockito/mockito.dart') ..docs.add('/// A class which mocks [$className].') ..docs.add('///') @@ -402,7 +457,19 @@ class _MockLibraryInfo { // parameter with same type variables, and a mirrored type argument for // the "implements" clause. var typeArguments = []; - if (classToMock.typeParameters != null) { + if (_hasExplicitTypeArguments(typeToMock)) { + // [typeToMock] is a reference to a type with type arguments (for + // example: `Foo`). Generate a non-generic mock class which + // implements the mock target with said type arguments. For example: + // `class MockFoo extends Mock implements Foo {}` + for (var typeArgument in typeToMock.typeArguments) { + typeArguments.add(refer(typeArgument.element.name)); + } + } else if (classToMock.typeParameters != null) { + // [typeToMock] is a simple reference to a generic type (for example: + // `Foo`, a reference to `class Foo {}`). Generate a generic mock + // class which perfectly mirrors the type parameters on [typeToMock], + // forwarding them to the "implements" clause. for (var typeParameter in classToMock.typeParameters) { cBuilder.types.add(_typeParameterReference(typeParameter)); typeArguments.add(refer(typeParameter.name)); @@ -410,8 +477,8 @@ class _MockLibraryInfo { } cBuilder.implements.add(TypeReference((b) { b - ..symbol = dartType.name - ..url = _typeImport(dartType) + ..symbol = classToMock.name + ..url = _typeImport(mockTarget.classType) ..types.addAll(typeArguments); })); diff --git a/pkgs/mockito/test/builder_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart similarity index 97% rename from pkgs/mockito/test/builder_test.dart rename to pkgs/mockito/test/builder/auto_mocks_test.dart index 8ca269b95..c456abe27 100644 --- a/pkgs/mockito/test/builder_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -27,8 +27,15 @@ const annotationsAsset = { 'mockito|lib/annotations.dart': ''' class GenerateMocks { final List classes; + final List customMocks; - const GenerateMocks(this.classes); + const GenerateMocks(this.classes, {this.customMocks = []}); +} + +class MockSpec { + final Symbol mockName; + + const MockSpec({Symbol as}) : mockName = as; } ''' }; @@ -185,34 +192,6 @@ void main() { ); }); - test('deduplicates classes listed multiply in GenerateMocks', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo {} - '''), - 'foo|test/foo_test.dart': ''' - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - @GenerateMocks([Foo, Foo]) - void main() {} - ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo {} - '''), - }, - ); - }); - test('generates mock classes from multiple annotations', () async { await _testWithNonNullable( { @@ -1194,6 +1173,27 @@ void main() { ); }); + test('throws when GenerateMocks is given a class multiple times', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo, Foo]) + void main() {} + ''' + }, + message: contains( + 'Mockito cannot generate two mocks with the same name: MockFoo (for ' + 'Foo declared in /foo/lib/foo.dart, and for Foo declared in ' + '/foo/lib/foo.dart)'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a ' 'private return type', () async { @@ -1452,14 +1452,12 @@ void main() { ...annotationsAsset, 'foo|test/foo_test.dart': dedent(''' import 'package:mockito/annotations.dart'; - // Missing required argument to GenerateMocks. @GenerateMocks([_Foo]) void main() {} class _Foo {} '''), }, - message: - contains('The "classes" argument includes a private type: _Foo.'), + message: contains('Mockito cannot mock a private type: _Foo.'), ); }); @@ -1501,8 +1499,9 @@ void main() { '''), }, message: contains( - 'contains two classes with the same name: Foo. One declared in ' - '/foo/lib/a.dart, the other in /foo/lib/b.dart'), + 'Mockito cannot generate two mocks with the same name: MockFoo (for ' + 'Foo declared in /foo/lib/a.dart, and for Foo declared in ' + '/foo/lib/b.dart)'), ); }); @@ -1522,8 +1521,8 @@ void main() { '''), }, message: contains( - 'contains a class which conflicts with another class declared in ' - 'this library: MockFoo'), + 'Mockito cannot generate a mock with a name which conflicts with ' + 'another class declared in this library: MockFoo'), ); }); @@ -1572,7 +1571,7 @@ void main() { typedef Foo = void Function(); '''), }, - message: 'The "classes" argument includes a typedef: Foo', + message: 'Mockito cannot mock a typedef: Foo', ); }); @@ -1585,7 +1584,7 @@ void main() { enum Foo {} '''), }, - message: 'The "classes" argument includes an enum: Foo', + message: 'Mockito cannot mock an enum: Foo', ); }); @@ -1612,8 +1611,7 @@ void main() { void main() {} '''), }, - message: contains( - 'The "classes" argument includes a non-subtypable type: int'), + message: contains('Mockito cannot mock a non-subtypable type: int'), ); }); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart new file mode 100644 index 000000000..fbcfa1fa6 --- /dev/null +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -0,0 +1,474 @@ +// Copyright 2020 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@TestOn('vm') +import 'package:build/build.dart'; +import 'package:build/experiments.dart'; +import 'package:build_test/build_test.dart'; +import 'package:meta/meta.dart'; +import 'package:mockito/src/builder.dart'; +import 'package:package_config/package_config.dart'; +import 'package:test/test.dart'; + +Builder buildMocks(BuilderOptions options) => MockBuilder(); + +const annotationsAsset = { + 'mockito|lib/annotations.dart': ''' +class GenerateMocks { + final List classes; + final List customMocks; + + const GenerateMocks(this.classes, {this.customMocks = []}); +} + +class MockSpec { + final Symbol mockName; + + const MockSpec({Symbol as}) : mockName = as; +} +''' +}; + +const mockitoAssets = { + 'mockito|lib/mockito.dart': ''' +export 'src/mock.dart'; +''', + 'mockito|lib/src/mock.dart': ''' +class Mock {} +''' +}; + +const simpleTestAsset = { + 'foo|test/foo_test.dart': ''' +import 'package:foo/foo.dart'; +import 'package:mockito/annotations.dart'; +@GenerateMocks([], customMocks: [MockSpec()]) +void main() {} +''' +}; + +void main() { + test('generates a generic mock class without type arguments', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + ), + }, + ); + }); + + test('generates a generic mock class with type arguments', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks( + [], customMocks: [MockSpec>(as: #MockFooOfIntBool)]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFooOfIntBool extends _i1.Mock implements _i2.Foo {}', + ), + }, + ); + }); + + test('generates a generic mock class with type arguments but no name', + () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec>()]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + ), + }, + ); + }); + + test('generates a generic, bounded mock class without type arguments', + () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + ), + }, + ); + }); + + test('generates mock classes from multiple annotations', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec()]) + void fooTests() {} + @GenerateMocks([], customMocks: [MockSpec()]) + void barTests() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}', + 'class MockBar extends _i1.Mock implements _i2.Bar {}', + ), + }, + ); + }); + + test('generates mock classes from multiple annotations on a single element', + () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/a.dart': dedent(r''' + class Foo {} + '''), + 'foo|lib/b.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/a.dart' as a; + import 'package:foo/b.dart' as b; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec(as: #MockAFoo)]) + @GenerateMocks([], customMocks: [MockSpec(as: #MockBFoo)]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockAFoo extends _i1.Mock implements _i2.Foo {}', + 'class MockBFoo extends _i1.Mock implements _i3.Foo {}', + ), + }, + ); + }); + + test( + 'throws when GenerateMock is given a class with a type parameter with a ' + 'private bound', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m(int a) {} + } + class _Bar {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains( + "The class 'Foo' features a private type parameter bound, and cannot " + 'be stubbed.'), + ); + }); + + test("throws when GenerateMock's Of argument is missing a type argument", + () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + // Missing required type argument to GenerateMock. + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains('Mockito cannot mock `dynamic`'), + ); + }); + + test('throws when GenerateMock is given a private class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec<_Foo>()]) + void main() {} + class _Foo {} + '''), + }, + message: contains('Mockito cannot mock a private type: _Foo.'), + ); + }); + + test('throws when two distinct classes with the same name are mocked', + () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/a.dart': dedent(r''' + class Foo {} + '''), + 'foo|lib/b.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/a.dart' as a; + import 'package:foo/b.dart' as b; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec()]) + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains( + 'Mockito cannot generate two mocks with the same name: MockFoo (for ' + 'Foo declared in /foo/lib/a.dart, and for Foo declared in ' + '/foo/lib/b.dart)'), + ); + }); + + test('throws when a mock class of the same name already exists', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + class MockFoo {} + '''), + }, + message: contains( + 'Mockito cannot generate a mock with a name which conflicts with ' + 'another class declared in this library: MockFoo'), + ); + }); + + test('throws when a mock class of class-to-mock already exists', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...mockitoAssets, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + import 'package:mockito/mockito.dart'; + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + class FakeFoo extends Mock implements Foo {} + '''), + }, + message: contains( + 'contains a class which appears to already be mocked inline: FakeFoo'), + ); + }); + + test('throws when GenerateMock references a typedef', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + typedef Foo = void Function(); + '''), + }, + message: 'Mockito cannot mock a typedef: Foo', + ); + }); + + test('throws when GenerateMock references an enum', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + enum Foo {} + '''), + }, + message: 'Mockito cannot mock an enum: Foo', + ); + }); + + test('throws when GenerateMock references a non-subtypeable type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains('Mockito cannot mock a non-subtypable type: int'), + ); + }); + + test('given a pre-non-nullable library, does not override any members', + () async { + await _testPreNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + int f(int a); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), + }, + ); + }); +} + +/// Test [MockBuilder] in a package which has not opted into the non-nullable +/// type system. +/// +/// Whether the non-nullable experiment is enabled depends on the SDK executing +/// this test, but that does not affect the opt-in state of the package under +/// test. +Future _testPreNonNullable(Map sourceAssets, + {Map*/ dynamic> outputs}) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 7)) + ]); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + outputs: outputs, packageConfig: packageConfig); +} + +/// Test [MockBuilder] in a package which has opted into the non-nullable type +/// system, and with the non-nullable experiment enabled. +Future _testWithNonNullable(Map sourceAssets, + {Map>*/ dynamic> outputs}) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 9)) + ]); + await withEnabledExperiments( + () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + outputs: outputs, packageConfig: packageConfig), + ['non-nullable'], + ); +} + +/// Test [MockBuilder] on a single source file, in a package which has opted +/// into the non-nullable type system, and with the non-nullable experiment +/// enabled. +Future _expectSingleNonNullableOutput( + String sourceAssetText, + /*String|Matcher>*/ dynamic output) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 9)) + ]); + + await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': sourceAssetText, + }, + outputs: {'foo|test/foo_test.mocks.dart': output}, + packageConfig: packageConfig), + ['non-nullable'], + ); +} + +TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( + b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); + +/// Expect that [testBuilder], given [assets], throws an +/// [InvalidMockitoAnnotationException] with a message containing [message]. +void _expectBuilderThrows( + {@required Map assets, + @required dynamic /*String|Matcher>*/ message}) { + expect( + () async => await testBuilder(buildMocks(BuilderOptions({})), assets), + throwsA(TypeMatcher() + .having((e) => e.message, 'message', message))); +} + +/// Dedent [input], so that each line is shifted to the left, so that the first +/// line is at the 0 column. +String dedent(String input) { + final indentMatch = RegExp(r'^(\s*)').firstMatch(input); + final indent = ''.padRight(indentMatch.group(1).length); + return input.splitMapJoin('\n', + onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), '')); +} From e0640fae6d811b09f5f19abe1075c05a69134bbd Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 20 Jul 2020 09:38:16 -0400 Subject: [PATCH 210/607] Improve error when non-mock method is called in when(). We've seen many users confused about mocking extension methods. There is an FAQ entry, but improving the error message is important as well. PiperOrigin-RevId: 322132516 --- pkgs/mockito/lib/src/mock.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 793f8f6a5..fe7d47c15 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -439,8 +439,8 @@ class PostExpectation { void _completeWhen(Answering answer) { if (_whenCall == null) { throw StateError( - 'Mock method was not called within `when()`. Was a real method ' - 'called?'); + 'No method stub was called from within `when()`. Was a real method ' + 'called, or perhaps an extension method?'); } _whenCall._setExpected(answer); _whenCall = null; From 322f1066c6e75850ffe6f73ac16f05d67bace71d Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 20 Jul 2020 20:35:53 -0400 Subject: [PATCH 211/607] MockBuilder: Change default generated mocks to throw on missing stubs. This also adds a mechanism to MockSpec (used in custom mocks) to revert to the legacy behavior (returning null on missing stubs). PiperOrigin-RevId: 322261918 --- pkgs/mockito/lib/annotations.dart | 9 +- pkgs/mockito/lib/src/builder.dart | 23 +- .../mockito/test/builder/auto_mocks_test.dart | 269 ++++++++++++------ .../test/builder/custom_mocks_test.dart | 105 +++++-- 4 files changed, 300 insertions(+), 106 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 551778eba..36540c66b 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -50,6 +50,9 @@ class GenerateMocks { /// The name of the mock class is either specified with the `as` named argument, /// or is the name of the class being mocked, prefixed with 'Mock'. /// +/// To use the legacy behavior of returning null for unstubbed methods, use +/// `returnNullOnMissingStub: true`. +/// /// For example, given the generic class, `class Foo`, then this /// annotation: /// @@ -64,10 +67,12 @@ class GenerateMocks { /// `class MockFoo extends Mocks implements Foo` and /// `class MockFooOfInt extends Mock implements Foo`. // TODO(srawlins): Document this in NULL_SAFETY_README.md. -// TODO(srawlins): Add 'returnNullOnMissingStub'. // TODO(srawlins): Add 'mixingIn'. class MockSpec { final Symbol mockName; - const MockSpec({Symbol as}) : mockName = as; + final bool returnNullOnMissingStub; + + const MockSpec({Symbol as, this.returnNullOnMissingStub = false}) + : mockName = as; } diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index fe21e9f5c..cb3ff7d02 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -79,7 +79,9 @@ class _MockTarget { /// The desired name of the mock class. final String mockName; - _MockTarget(this.classType, this.mockName); + final bool returnNullOnMissingStub; + + _MockTarget(this.classType, this.mockName, {this.returnNullOnMissingStub}); ClassElement get classElement => classType.element; } @@ -141,7 +143,8 @@ class _MockTargetGatherer { } final type = _determineDartType(typeToMock, entryLib.typeProvider); final mockName = 'Mock${type.element.name}'; - mockTargets.add(_MockTarget(type, mockName)); + mockTargets + .add(_MockTarget(type, mockName, returnNullOnMissingStub: false)); } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { @@ -157,7 +160,10 @@ class _MockTargetGatherer { var type = _determineDartType(typeToMock, entryLib.typeProvider); final mockName = mockSpec.getField('mockName').toSymbolValue() ?? 'Mock${type.element.name}'; - mockTargets.add(_MockTarget(type, mockName)); + final returnNullOnMissingStub = + mockSpec.getField('returnNullOnMissingStub').toBoolValue(); + mockTargets.add(_MockTarget(type, mockName, + returnNullOnMissingStub: returnNullOnMissingStub)); } } return mockTargets; @@ -481,6 +487,9 @@ class _MockLibraryInfo { ..url = _typeImport(mockTarget.classType) ..types.addAll(typeArguments); })); + if (!mockTarget.returnNullOnMissingStub) { + cBuilder.constructors.add(_constructorWithThrowOnMissingStub); + } // Only override members of a class declared in a library which uses the // non-nullable type system. @@ -515,6 +524,14 @@ class _MockLibraryInfo { }); } + /// The default behavior of mocks is to return null for unstubbed methods. To + /// use the new behavior of throwing an error, we must explicitly call + /// `throwOnMissingStub`. + Constructor get _constructorWithThrowOnMissingStub => + Constructor((cBuilder) => cBuilder.body = + refer('throwOnMissingStub', 'package:mockito/mockito.dart') + .call([refer('this').expression]).statement); + bool _returnTypeIsNonNullable(ExecutableElement method) => typeSystem.isPotentiallyNonNullable(method.returnType); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index c456abe27..1ebeb637b 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -35,7 +35,10 @@ class GenerateMocks { class MockSpec { final Symbol mockName; - const MockSpec({Symbol as}) : mockName = as; + final bool returnNullOnMissingStub; + + const MockSpec({Symbol as, this.returnNullOnMissingStub = false}) + : mockName = as; } ''' }; @@ -58,6 +61,11 @@ void main() {} ''' }; +const _constructorWithThrowOnMissingStub = ''' +MockFoo() { + _i1.throwOnMissingStub(this); + }'''; + void main() { test( 'generates a mock class but does not override methods w/ zero parameters', @@ -68,7 +76,11 @@ void main() { dynamic a() => 7; } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -80,7 +92,11 @@ void main() { int _b(int x) => 8; } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -91,7 +107,11 @@ void main() { static int c(int y) => 9; } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -104,7 +124,11 @@ void main() { } class Foo {} '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -185,8 +209,18 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - 'class MockBar extends _i1.Mock implements _i2.Bar {}', + dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + '''), + dedent(''' + class MockBar extends _i1.Mock implements _i2.Bar { + MockBar() { + _i1.throwOnMissingStub(this); + } + } + '''), ), }, ); @@ -211,8 +245,18 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - 'class MockBar extends _i1.Mock implements _i2.Bar {}', + dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + '''), + dedent(''' + class MockBar extends _i1.Mock implements _i2.Bar { + MockBar() { + _i1.throwOnMissingStub(this); + } + } + '''), ), }, ); @@ -237,8 +281,18 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - 'class MockBar extends _i1.Mock implements _i2.Bar {}', + dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + '''), + dedent(''' + class MockBar extends _i1.Mock implements _i2.Bar { + MockBar() { + _i1.throwOnMissingStub(this); + } + } + '''), ), }, ); @@ -249,8 +303,11 @@ void main() { dedent(r''' class Foo {} '''), - _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -271,8 +328,18 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - 'class MockBar extends _i1.Mock implements _i2.Bar {}', + dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + '''), + dedent(''' + class MockBar extends _i1.Mock implements _i2.Bar { + MockBar() { + _i1.throwOnMissingStub(this); + } + } + '''), ), }, ); @@ -326,6 +393,10 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { + MockFoo() { + _i1.throwOnMissingStub(this); + } + dynamic f(List<_i2.Foo>? list) => super.noSuchMethod(Invocation.method(#f, [list])); } @@ -360,6 +431,10 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { + MockFoo() { + _i1.throwOnMissingStub(this); + } + dynamic f(_i2.Callback? c) => super.noSuchMethod(Invocation.method(#f, [c])); dynamic g(_i2.Callback2? c) => super.noSuchMethod(Invocation.method(#g, [c])); dynamic h(_i2.Callback3<_i2.Foo>? c) => @@ -612,77 +687,83 @@ void main() { }); test('does not override methods with all nullable parameters', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo { - void a(int? p) {} - void b(dynamic p) {} - void c(var p) {} - void d(final p) {} - void e(int Function()? p) {} - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo {} - '''), - }, + await _expectSingleNonNullableOutput( + dedent(''' + class Foo { + void a(int? p) {} + void b(dynamic p) {} + void c(var p) {} + void d(final p) {} + void e(int Function()? p) {} + } + '''), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); test('does not override methods with a void return type', () async { await _expectSingleNonNullableOutput( - dedent(r''' - abstract class Foo { - void m(); - } - '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + dedent(''' + abstract class Foo { + void m(); + } + '''), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); test('does not override methods with an implicit dynamic return type', () async { await _expectSingleNonNullableOutput( - dedent(r''' - abstract class Foo { - m(); - } - '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + dedent(''' + abstract class Foo { + m(); + } + '''), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); test('does not override methods with an explicit dynamic return type', () async { await _expectSingleNonNullableOutput( - dedent(r''' - abstract class Foo { - dynamic m(); - } - '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + dedent(''' + abstract class Foo { + dynamic m(); + } + '''), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); test('does not override methods with a nullable return type', () async { await _expectSingleNonNullableOutput( - dedent(r''' - abstract class Foo { - int? m(); - } - '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + dedent(''' + abstract class Foo { + int? m(); + } + '''), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -710,17 +791,8 @@ void main() { '''), }, outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - void a(T? m) => super.noSuchMethod(Invocation.method(#a, [m])); - } - '''), + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'void a(T? m) => super.noSuchMethod(Invocation.method(#a, [m]));'), }, ); }); @@ -746,6 +818,10 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { + MockFoo() { + _i1.throwOnMissingStub(this); + } + dynamic f(int? a) => super.noSuchMethod(Invocation.method(#f, [a])); dynamic g(int? a) => super.noSuchMethod(Invocation.method(#g, [a])); @@ -774,7 +850,11 @@ void main() { int? get m => 7; } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -797,7 +877,11 @@ void main() { void set m(int? a) {} } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -834,7 +918,11 @@ void main() { int? m; } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -845,7 +933,11 @@ void main() { int _a; } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -856,7 +948,11 @@ void main() { static int b; } '''), - _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'), + _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), ); }); @@ -1166,6 +1262,10 @@ void main() { /// /// See the documentation for Mockito's code generation for more information. class MockFoo extends _i1.Mock implements _i2.Foo { + MockFoo() { + _i1.throwOnMissingStub(this); + } + _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); _i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar()); } @@ -1628,8 +1728,13 @@ void main() { '''), }, outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + MockFoo() { + _i1.throwOnMissingStub(this); + } + } + ''')) }, ); }); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index fbcfa1fa6..a7efc7944 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -35,7 +35,10 @@ class GenerateMocks { class MockSpec { final Symbol mockName; - const MockSpec({Symbol as}) : mockName = as; + final bool returnNullOnMissingStub; + + const MockSpec({Symbol as, this.returnNullOnMissingStub = false}) + : mockName = as; } ''' }; @@ -58,6 +61,11 @@ void main() {} ''' }; +const _constructorWithThrowOnMissingStub = ''' +MockFoo() { + _i1.throwOnMissingStub(this); + }'''; + void main() { test('generates a generic mock class without type arguments', () async { await _testWithNonNullable( @@ -74,9 +82,11 @@ void main() { ''' }, outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - ), + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), }, ); }); @@ -97,9 +107,13 @@ void main() { ''' }, outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFooOfIntBool extends _i1.Mock implements _i2.Foo {}', - ), + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFooOfIntBool extends _i1.Mock implements _i2.Foo { + MockFooOfIntBool() { + _i1.throwOnMissingStub(this); + } + } + ''')), }, ); }); @@ -120,9 +134,11 @@ void main() { ''' }, outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - ), + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), }, ); }); @@ -143,9 +159,11 @@ void main() { ''' }, outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - ), + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')), }, ); }); @@ -169,8 +187,18 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}', - 'class MockBar extends _i1.Mock implements _i2.Bar {}', + dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + '''), + dedent(''' + class MockBar extends _i1.Mock implements _i2.Bar { + MockBar() { + _i1.throwOnMissingStub(this); + } + } + '''), ), }, ); @@ -198,13 +226,49 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockAFoo extends _i1.Mock implements _i2.Foo {}', - 'class MockBFoo extends _i1.Mock implements _i3.Foo {}', + dedent(''' + class MockAFoo extends _i1.Mock implements _i2.Foo { + MockAFoo() { + _i1.throwOnMissingStub(this); + } + } + '''), + dedent(''' + class MockBFoo extends _i1.Mock implements _i3.Foo { + MockBFoo() { + _i1.throwOnMissingStub(this); + } + } + '''), ), }, ); }); + test( + 'generates a mock class which uses the old behavior of returning null on ' + 'missing stubs', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo, returnNullOnMissingStub: true)]) + void main() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo {} + ''')), + }, + ); + }); + test( 'throws when GenerateMock is given a class with a type parameter with a ' 'private bound', () async { @@ -384,8 +448,11 @@ void main() { '''), }, outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'class MockFoo extends _i1.Mock implements _i2.Foo {}'), + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')) }, ); }); From 4c0ae46a81479dc834697eb3bd7beed80795568c Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 23 Jul 2020 16:27:22 -0400 Subject: [PATCH 212/607] Change README.md to prefer async/await to Future.value. Fixes dart-lang/mockito#261 PiperOrigin-RevId: 322853868 --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index c54bdca29..a6c8a3809 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -111,7 +111,7 @@ when(mock.methodThatReturnsAStream()) // GOOD when(mock.methodThatReturnsAFuture()) - .thenAnswer((_) => Future.value('Stub')); + .thenAnswer((_) async => 'Stub'); when(mock.methodThatReturnsAStream()) .thenAnswer((_) => Stream.fromIterable(['Stub'])); From 97c1da9d75645231e602572d618c1664040d94fd Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 23 Jul 2020 16:28:34 -0400 Subject: [PATCH 213/607] MockBuilder: Match parameter default values. When overriding a class, the default value of a parameter must be repeated; it is not inferred, and it cannot be changed. While default values of optional parameters must be const, they can still be complex. They can be literals, like strings or numbers, or `const`-instantiated objects, using named constructors, and constructor arguments. To reproduce all of this code, and include the proper imports, we use source_gen's Revivable. PiperOrigin-RevId: 322854095 --- pkgs/mockito/lib/src/builder.dart | 101 +++++- pkgs/mockito/pubspec.yaml | 1 + .../mockito/test/builder/auto_mocks_test.dart | 298 +++++++++++++++++- 3 files changed, 396 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index cb3ff7d02..67a3b77e6 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:analyzer/dart/element/type_provider.dart'; @@ -20,6 +21,7 @@ import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'package:meta/meta.dart'; +import 'package:source_gen/source_gen.dart'; /// For a source Dart library, generate the mocks referenced therein. /// @@ -752,11 +754,97 @@ class _MockLibraryInfo { ..type = _typeReference(parameter.type, forceNullable: forceNullable); if (parameter.isNamed) pBuilder.named = true; if (parameter.defaultValueCode != null) { - pBuilder.defaultTo = Code(parameter.defaultValueCode); + try { + pBuilder.defaultTo = + _expressionFromDartObject(parameter.computeConstantValue()).code; + } on _ReviveError catch (e) { + final method = parameter.enclosingElement; + final clazz = method.enclosingElement; + throw InvalidMockitoAnnotationException( + 'Mockito cannot generate a valid stub for method ' + "'${clazz.displayName}.${method.displayName}'; parameter " + "'${parameter.displayName}' causes a problem: ${e.message}"); + } } }); } + /// Creates a code_builder [Expression] from [object], a constant object from + /// analyzer. + /// + /// This is very similar to Angular's revive code, in + /// angular_compiler/analyzer/di/injector.dart. + Expression _expressionFromDartObject(DartObject object) { + final constant = ConstantReader(object); + if (constant.isNull) { + return literalNull; + } else if (constant.isBool) { + return literalBool(constant.boolValue); + } else if (constant.isDouble) { + return literalNum(constant.doubleValue); + } else if (constant.isInt) { + return literalNum(constant.intValue); + } else if (constant.isString) { + return literalString(constant.stringValue, raw: true); + } else if (constant.isList) { + return literalConstList([ + for (var element in constant.listValue) + _expressionFromDartObject(element) + ]); + } else if (constant.isMap) { + return literalConstMap({ + for (var pair in constant.mapValue.entries) + _expressionFromDartObject(pair.key): + _expressionFromDartObject(pair.value) + }); + } else if (constant.isSet) { + return literalConstSet({ + for (var element in constant.setValue) + _expressionFromDartObject(element) + }); + } else if (constant.isType) { + // TODO(srawlins): It seems like this might be revivable, but Angular + // does not revive Types; we should investigate this if users request it. + throw _ReviveError('default value is a Type: ${object.toTypeValue()}.'); + } else { + // If [constant] is not null, a literal, or a type, then it must be an + // object constructed with `const`. Revive it. + var revivable = constant.revive(); + if (revivable.isPrivate) { + final privateReference = revivable.accessor?.isNotEmpty == true + ? '${revivable.source}::${revivable.accessor}' + : '${revivable.source}'; + throw _ReviveError( + 'default value has a private type: $privateReference.'); + } + if (revivable.source.fragment.isEmpty) { + // We can create this invocation by referring to a const field. + return refer(revivable.accessor, _typeImport(object.type)); + } + + final name = revivable.source.fragment; + final positionalArgs = [ + for (var argument in revivable.positionalArguments) + _expressionFromDartObject(argument) + ]; + final namedArgs = { + for (var pair in revivable.namedArguments.entries) + pair.key: _expressionFromDartObject(pair.value) + }; + final type = refer(name, _typeImport(object.type)); + if (revivable.accessor.isNotEmpty) { + return type.constInstanceNamed( + revivable.accessor, + positionalArgs, + namedArgs, + // No type arguments. See + // https://github.com/dart-lang/source_gen/issues/478. + ); + } + return type.constInstance(positionalArgs, namedArgs); + } + } + /// Build a getter which overrides [getter]. /// /// This new method just calls `super.noSuchMethod`, optionally passing a @@ -903,6 +991,17 @@ class _MockLibraryInfo { } } +/// An exception thrown when reviving a potentially deep value in a constant. +/// +/// This exception should always be caught within this library. An +/// [InvalidMockitoAnnotationException] can be presented to the user after +/// catching this exception. +class _ReviveError implements Exception { + final String message; + + _ReviveError(this.message); +} + /// An exception which is thrown when Mockito encounters an invalid annotation. class InvalidMockitoAnnotationException implements Exception { final String message; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c44c9701e..a52743b58 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: dart_style: ^1.3.6 matcher: ^0.12.3 meta: '>=1.0.4 <1.2.0' + source_gen: ^0.9.7 test_api: ^0.2.1 dev_dependencies: diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 1ebeb637b..0a6d762db 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -132,11 +132,23 @@ void main() { ); }); + test('overrides methods, matching required positional parameters', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m(int a) {} + } + '''), + _containsAllOf('void m(int? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + test('overrides methods, matching optional positional parameters', () async { await _expectSingleNonNullableOutput( dedent(r''' class Foo { - void m(int a, [int b, int c = 0]) {} + void m(int a, [int b, int c = 0]) {} } '''), _containsAllOf('void m(int? a, [int? b, int? c = 0]) =>', @@ -148,7 +160,7 @@ void main() { await _expectSingleNonNullableOutput( dedent(r''' class Foo { - void m(int a, {int b, int c = 0}) {} + void m(int a, {int b, int c = 0}) {} } '''), _containsAllOf('void m(int? a, {int? b, int? c = 0}) =>', @@ -156,6 +168,275 @@ void main() { ); }); + test('matches parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([int a, int b = 0]) {} + } + '''), + _containsAllOf('void m([int? a, int? b = 0]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches boolean literal parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([bool a = true, bool b = false]) {} + } + '''), + _containsAllOf('void m([bool? a = true, bool? b = false]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches number literal parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([int a = 0, double b = 0.5]) {} + } + '''), + _containsAllOf('void m([int? a = 0, double? b = 0.5]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches string literal parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([String a = 'Hello', String b = 'Hello ' r"World"]) {} + } + '''), + _containsAllOf( + "void m([String? a = r'Hello', String? b = r'Hello World']) =>", + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches empty collection literal parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([List a = const [], Map b = const {}]) {} + } + '''), + _containsAllOf( + 'void m([List? a = const [], Map? b = const {}]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + ); + }); + + test('matches non-empty list literal parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([List a = const [1, 2, 3]]) {} + } + '''), + _containsAllOf('void m([List? a = const [1, 2, 3]]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches non-empty map literal parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([Map a = const {1: 'a', 2: 'b'}]) {} + } + '''), + _containsAllOf( + "void m([Map? a = const {1: r'a', 2: r'b'}]) =>", + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches non-empty map literal parameter default values', () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([Map a = const {1: 'a', 2: 'b'}]) {} + } + '''), + _containsAllOf( + "void m([Map? a = const {1: r'a', 2: r'b'}]) =>", + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches parameter default values constructed from a local class', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([Bar a = const Bar()]) {} + } + class Bar { + const Bar(); + } + '''), + _containsAllOf('void m([_i2.Bar? a = const _i2.Bar()]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches parameter default values constructed from a Dart SDK class', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([Duration a = const Duration(days: 1)]) {} + } + '''), + _containsAllOf('void m([Duration? a = const Duration(days: 1)]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches parameter default values constructed from a named constructor', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([Bar a = const Bar.named()]) {} + } + class Bar { + const Bar.named(); + } + '''), + _containsAllOf('void m([_i2.Bar? a = const _i2.Bar.named()]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches parameter default values constructed with positional arguments', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([Bar a = const Bar(7)]) {} + } + class Bar { + final int i; + const Bar(this.i); + } + '''), + _containsAllOf('void m([_i2.Bar? a = const _i2.Bar(7)]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches parameter default values constructed with named arguments', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([Bar a = const Bar(i: 7)]) {} + } + class Bar { + final int i; + const Bar({this.i}); + } + '''), + _containsAllOf('void m([_i2.Bar? a = const _i2.Bar(i: 7)]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches parameter default values constructed with top-level variable', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([int a = x]) {} + } + const x = 1; + '''), + _containsAllOf('void m([int? a = 1]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('matches parameter default values constructed with static field', + () async { + await _expectSingleNonNullableOutput( + dedent(r''' + class Foo { + static const x = 1; + void m([int a = x]) {} + } + '''), + _containsAllOf('void m([int? a = 1]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('throws when given a parameter default value using a private type', () { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m([Bar a = const _Bar()]) {} + } + class Bar {} + class _Bar implements Bar { + const _Bar(); + } + '''), + }, + message: contains( + "Mockito cannot generate a valid stub for method 'Foo.m'; parameter " + "'a' causes a problem: default value has a private type: " + 'asset:foo/lib/foo.dart#_Bar'), + ); + }); + + test( + 'throws when given a parameter default value using a private constructor', + () { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m([Bar a = const Bar._named()]) {} + } + class Bar { + const Bar._named(); + } + '''), + }, + message: contains( + "Mockito cannot generate a valid stub for method 'Foo.m'; parameter " + "'a' causes a problem: default value has a private type: " + 'asset:foo/lib/foo.dart#Bar::_named'), + ); + }); + + test('throws when given a parameter default value which is a type', () { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m([Type a = int]) {} + } + '''), + }, + message: contains( + "Mockito cannot generate a valid stub for method 'Foo.m'; parameter " + "'a' causes a problem: default value is a Type: int"), + ); + }); + test('overrides async methods legally', () async { await _expectSingleNonNullableOutput( dedent(r''' @@ -1807,8 +2088,19 @@ TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( void _expectBuilderThrows( {@required Map assets, @required dynamic /*String|Matcher>*/ message}) { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 9)) + ]); + expect( - () async => await testBuilder(buildMocks(BuilderOptions({})), assets), + () async => await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), assets, + packageConfig: packageConfig), + ['non-nullable'], + ), throwsA(TypeMatcher() .having((e) => e.message, 'message', message))); } From 03de5c879d3a40adf64e9428a17a6c7fcfe10533 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 24 Jul 2020 16:33:12 -0400 Subject: [PATCH 214/607] Add documentation for customMocks, and MockSpec, in NULL_SAFETY_README. Document that a MockSpec lets one customize the name, type arguments, and missing-stub behavior. PiperOrigin-RevId: 323062022 --- pkgs/mockito/NULL_SAFETY_README.md | 44 +++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index cb8013466..82dddceac 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -12,7 +12,7 @@ class HttpServer { ``` The method, `start`, takes a _non_-nullable int argument, and returns a -_non_-nullable Url. Under the null safety type system, it is illegal to pass +_non_-nullable Uri. Under the null safety type system, it is illegal to pass `null` to `start`, and it is illegal for `start` (or any overriding methods in any sub-classes) to return `null`. This plays havoc with the mechanisms that Mockito uses to stub methods. @@ -113,6 +113,48 @@ multiple tests, for example named `shared_mocks.dart`, we can edit that file to generate mocks, and then import `shared_mocks.mocks.dart` in the tests which previously imported `shared_mocks.dart`. +### Custom generated mocks + +Mockito might need some additional input in order to generate the right mock for +certain use cases. We can generate custom mock classes by passing MockSpec +objects to the `customMocks` list argument in `@GenerateMocks`. + +#### Mock with a custom name + +Use MockSpec's constructor's `as` named parameter to use a non-standard name for +the mock class. For example: + +```dart +@GenerateMocks([], customMocks: [MockSpec(as: #BaseMockFoo)]) +``` + +Mockito will generate a mock class called `BaseMockFoo`, instead of the default, +`MockFoo`. This can help to work around name collisions or to give a more +specific name to a mock with type arguments (See below). + +#### Non-generic mock of a generic class + +To generate a mock class which extends a class with type arguments, specify +them on MockSpec's type argument: + +```dart +@GenerateMocks([], customMocks: [MockSpec>(as: #MockFooOfInt)]) +``` + +Mockito will generate `class MockFooOfInt extends Mock implements Foo`. + +#### Old "missing stub" behavior + +When a method of a generated mock class is called, which does not match any +method stub created with the `when` API, the call will throw an exception. To +use the old default behavior of returning null (which doesn't make a lot of +sense in the Null safety type system), for legacy code, use +`returnNullOnMissingStub`: + +```dart +@GenerateMocks([], customMocks: [MockSpec(returnNullOnMissingStub: true)]) +``` + ### Manual mock implementaion **In the general case, we strongly recommend generating mocks with the above From cdbccc11197380746ce63612537da1cdc16dd834 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 24 Jul 2020 14:41:45 -0700 Subject: [PATCH 215/607] Fix pub dependency versions --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index a52743b58..8c4f9228b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dart_style: ^1.3.6 matcher: ^0.12.3 meta: '>=1.0.4 <1.2.0' - source_gen: ^0.9.7 + source_gen: ^0.9.6 test_api: ^0.2.1 dev_dependencies: From c3db8c89d96aa336a34464df9aecc99ab4aa6de8 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 24 Jul 2020 16:33:53 -0700 Subject: [PATCH 216/607] Allow testing with Dart 2.10 dev --- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/all.dart | 6 ++++-- pkgs/mockito/test/builder/auto_mocks_test.dart | 6 +++--- pkgs/mockito/test/builder/custom_mocks_test.dart | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8c4f9228b..b7c8aac12 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: '>=2.7.0 <3.0.0' dependencies: - analyzer: ^0.39.11 + analyzer: ^0.39.15 build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.1.0 diff --git a/pkgs/mockito/test/all.dart b/pkgs/mockito/test/all.dart index 0aa563518..5fcc7b4cf 100644 --- a/pkgs/mockito/test/all.dart +++ b/pkgs/mockito/test/all.dart @@ -15,7 +15,8 @@ // This file explicitly does _not_ end in `_test.dart`, so that it is not picked // up by `pub run test`. It is here for coveralls. -import 'builder_test.dart' as builder_test; +import 'builder/auto_mocks_test.dart' as builder_auto_mocks_test; +import 'builder/custom_mocks_test.dart' as builder_custom_mocks_test; import 'capture_test.dart' as capture_test; import 'invocation_matcher_test.dart' as invocation_matcher_test; import 'mockito_test.dart' as mockito_test; @@ -23,7 +24,8 @@ import 'until_called_test.dart' as until_called_test; import 'verify_test.dart' as verify_test; void main() { - builder_test.main(); + builder_auto_mocks_test.main(); + builder_custom_mocks_test.main(); capture_test.main(); invocation_matcher_test.main(); mockito_test.main(); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 0a6d762db..bfcde9f8f 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2045,7 +2045,7 @@ Future _testWithNonNullable(Map sourceAssets, var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); await withEnabledExperiments( () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, @@ -2063,7 +2063,7 @@ Future _expectSingleNonNullableOutput( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); await withEnabledExperiments( @@ -2091,7 +2091,7 @@ void _expectBuilderThrows( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); expect( diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index a7efc7944..01b7c2e46 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -482,7 +482,7 @@ Future _testWithNonNullable(Map sourceAssets, var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); await withEnabledExperiments( () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, @@ -500,7 +500,7 @@ Future _expectSingleNonNullableOutput( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); await withEnabledExperiments( From 87654ee2fa131a865b167949634df32878baa231 Mon Sep 17 00:00:00 2001 From: Chris Wong Date: Fri, 24 Jul 2020 17:27:02 +1000 Subject: [PATCH 217/607] Fix typo in documentation `identical` is a boolean function, not a matcher. The matcher version is `same`. - https://api.flutter.dev/flutter/dart-core/identical.html - https://api.flutter.dev/flutter/package-matcher_matcher/same.html --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a6c8a3809..77fa978e9 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -166,7 +166,7 @@ verify(cat.lives=9); If an argument other than an ArgMatcher (like [`any`], [`anyNamed`], [`argThat`], [`captureThat`], etc.) is passed to a mock method, then the [`equals`] matcher is used for argument matching. If you need more strict -matching consider use `argThat(identical(arg))`. +matching consider use `argThat(same(arg))`. However, note that `null` cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example: From 2c999b0b3d67c3dbbbf3189941474d3a0d60f6e8 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 27 Jul 2020 18:34:43 -0400 Subject: [PATCH 218/607] Fix builder tests for pub. * Update references in test/all.dart. * Correct references to GenerateMock, a previous design. * Update analyzer to 0.39.15, which also thinks the language version is 2.10. * Downgrade source_gen version to 0.9.6; 0.9.7 doesn't exist yet. PiperOrigin-RevId: 323455052 --- pkgs/mockito/test/builder/auto_mocks_test.dart | 6 +++--- .../mockito/test/builder/custom_mocks_test.dart | 17 ++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index bfcde9f8f..0a6d762db 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2045,7 +2045,7 @@ Future _testWithNonNullable(Map sourceAssets, var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) + languageVersion: LanguageVersion(2, 9)) ]); await withEnabledExperiments( () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, @@ -2063,7 +2063,7 @@ Future _expectSingleNonNullableOutput( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) + languageVersion: LanguageVersion(2, 9)) ]); await withEnabledExperiments( @@ -2091,7 +2091,7 @@ void _expectBuilderThrows( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) + languageVersion: LanguageVersion(2, 9)) ]); expect( diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 01b7c2e46..77a34ee27 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -270,7 +270,7 @@ void main() { }); test( - 'throws when GenerateMock is given a class with a type parameter with a ' + 'throws when GenerateMocks is given a class with a type parameter with a ' 'private bound', () async { _expectBuilderThrows( assets: { @@ -294,14 +294,13 @@ void main() { ); }); - test("throws when GenerateMock's Of argument is missing a type argument", - () async { + test('throws when MockSpec() is missing a type argument', () async { _expectBuilderThrows( assets: { ...annotationsAsset, 'foo|test/foo_test.dart': dedent(''' import 'package:mockito/annotations.dart'; - // Missing required type argument to GenerateMock. + // Missing required type argument to MockSpec. @GenerateMocks([], customMocks: [MockSpec()]) void main() {} '''), @@ -310,7 +309,7 @@ void main() { ); }); - test('throws when GenerateMock is given a private class', () async { + test('throws when MockSpec uses a private class', () async { _expectBuilderThrows( assets: { ...annotationsAsset, @@ -395,7 +394,7 @@ void main() { ); }); - test('throws when GenerateMock references a typedef', () async { + test('throws when MockSpec references a typedef', () async { _expectBuilderThrows( assets: { ...annotationsAsset, @@ -408,7 +407,7 @@ void main() { ); }); - test('throws when GenerateMock references an enum', () async { + test('throws when MockSpec references an enum', () async { _expectBuilderThrows( assets: { ...annotationsAsset, @@ -421,7 +420,7 @@ void main() { ); }); - test('throws when GenerateMock references a non-subtypeable type', () async { + test('throws when MockSpec references a non-subtypeable type', () async { _expectBuilderThrows( assets: { ...annotationsAsset, @@ -500,7 +499,7 @@ Future _expectSingleNonNullableOutput( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) + languageVersion: LanguageVersion(2, 9)) ]); await withEnabledExperiments( From 45a3ed10a443b874a78ea220da19a2ee6f4d4a3c Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 27 Jul 2020 19:05:46 -0400 Subject: [PATCH 219/607] MockGenerator: Rename _ReviveError to _ReviveException. Thanks nbosch! PiperOrigin-RevId: 323460979 --- pkgs/mockito/lib/src/builder.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 67a3b77e6..1fe143c7f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -757,7 +757,7 @@ class _MockLibraryInfo { try { pBuilder.defaultTo = _expressionFromDartObject(parameter.computeConstantValue()).code; - } on _ReviveError catch (e) { + } on _ReviveException catch (e) { final method = parameter.enclosingElement; final clazz = method.enclosingElement; throw InvalidMockitoAnnotationException( @@ -805,7 +805,8 @@ class _MockLibraryInfo { } else if (constant.isType) { // TODO(srawlins): It seems like this might be revivable, but Angular // does not revive Types; we should investigate this if users request it. - throw _ReviveError('default value is a Type: ${object.toTypeValue()}.'); + throw _ReviveException( + 'default value is a Type: ${object.toTypeValue()}.'); } else { // If [constant] is not null, a literal, or a type, then it must be an // object constructed with `const`. Revive it. @@ -814,7 +815,7 @@ class _MockLibraryInfo { final privateReference = revivable.accessor?.isNotEmpty == true ? '${revivable.source}::${revivable.accessor}' : '${revivable.source}'; - throw _ReviveError( + throw _ReviveException( 'default value has a private type: $privateReference.'); } if (revivable.source.fragment.isEmpty) { @@ -996,10 +997,10 @@ class _MockLibraryInfo { /// This exception should always be caught within this library. An /// [InvalidMockitoAnnotationException] can be presented to the user after /// catching this exception. -class _ReviveError implements Exception { +class _ReviveException implements Exception { final String message; - _ReviveError(this.message); + _ReviveException(this.message); } /// An exception which is thrown when Mockito encounters an invalid annotation. From bef6b15b9bc87c6fff78b5a27f46a9afab283dd9 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 29 Jul 2020 14:38:59 -0400 Subject: [PATCH 220/607] MockBuilder: fix static analysis issues: * Remove unused helper in test * Migrate off analyzer displayName getters. PiperOrigin-RevId: 323832498 --- pkgs/mockito/lib/src/builder.dart | 10 +++---- .../test/builder/custom_mocks_test.dart | 26 ------------------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 1fe143c7f..0ce83be1f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -707,7 +707,7 @@ class _MockLibraryInfo { // There is a potential for these names to collide. If one mock class // requires a fake for a certain Foo, and another mock class requires a // fake for a different Foo, they will collide. - var fakeName = '_Fake${dartType.name}'; + var fakeName = '_Fake${elementToFake.name}'; // Only make one fake class for each class that needs to be faked. if (!fakedClassElements.contains(elementToFake)) { fakeClasses.add(Class((cBuilder) { @@ -722,7 +722,7 @@ class _MockLibraryInfo { } cBuilder.implements.add(TypeReference((b) { b - ..symbol = dartType.name + ..symbol = elementToFake.name ..url = _typeImport(dartType) ..types.addAll(typeParameters); })); @@ -928,7 +928,7 @@ class _MockLibraryInfo { if (type is analyzer.InterfaceType) { return TypeReference((b) { b - ..symbol = type.name + ..symbol = type.element.name ..isNullable = forceNullable || typeSystem.isPotentiallyNullable(type) ..url = _typeImport(type) ..types.addAll(type.typeArguments.map(_typeReference)); @@ -967,11 +967,11 @@ class _MockLibraryInfo { } else if (type is analyzer.TypeParameterType) { return TypeReference((b) { b - ..symbol = type.name + ..symbol = type.element.name ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { - return refer(type.displayName, _typeImport(type)); + return refer(type.getDisplayString(), _typeImport(type)); } } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 77a34ee27..586c7c078 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -490,32 +490,6 @@ Future _testWithNonNullable(Map sourceAssets, ); } -/// Test [MockBuilder] on a single source file, in a package which has opted -/// into the non-nullable type system, and with the non-nullable experiment -/// enabled. -Future _expectSingleNonNullableOutput( - String sourceAssetText, - /*String|Matcher>*/ dynamic output) async { - var packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) - ]); - - await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': sourceAssetText, - }, - outputs: {'foo|test/foo_test.mocks.dart': output}, - packageConfig: packageConfig), - ['non-nullable'], - ); -} - TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); From fc5d777e7cbe7bac7e67e8b831343e0b9374f94a Mon Sep 17 00:00:00 2001 From: srawlins Date: Sat, 1 Aug 2020 11:18:20 -0400 Subject: [PATCH 221/607] Add 'withNullability' that will become required. PiperOrigin-RevId: 324399218 --- pkgs/mockito/lib/src/builder.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0ce83be1f..e91bc622b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -971,7 +971,10 @@ class _MockLibraryInfo { ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { - return refer(type.getDisplayString(), _typeImport(type)); + return refer( + type.getDisplayString(withNullability: false), + _typeImport(type), + ); } } From b09c9c8b3dc00c275fe4bbf34d53a2dd40c29c27 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 3 Aug 2020 00:48:26 -0400 Subject: [PATCH 222/607] Use DartType.getDisplayString() instead of toString(). PiperOrigin-RevId: 324540648 --- pkgs/mockito/lib/src/builder.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index e91bc622b..a0fcf4c8c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -805,8 +805,9 @@ class _MockLibraryInfo { } else if (constant.isType) { // TODO(srawlins): It seems like this might be revivable, but Angular // does not revive Types; we should investigate this if users request it. - throw _ReviveException( - 'default value is a Type: ${object.toTypeValue()}.'); + var type = object.toTypeValue(); + var typeStr = type.getDisplayString(withNullability: false); + throw _ReviveException('default value is a Type: $typeStr.'); } else { // If [constant] is not null, a literal, or a type, then it must be an // object constructed with `const`. Revive it. From 2e26d188921c8d056acf9812d0fbfdf9343bae0a Mon Sep 17 00:00:00 2001 From: davidmorgan Date: Tue, 4 Aug 2020 05:13:52 -0400 Subject: [PATCH 223/607] Update //third_party/dart_lang/v2 and macos_sdk to 0a19e8dbfe53d2a9a8fd6850172dce89f96a42e9. Unbundle packages. Fix version used to turn on null safety in mockito tests. Update //third_party/dart_lang/trunk to 0a19e8dbfe53d2a9a8fd6850172dce89f96a42e9. - 0a19e8dbfe53d2a9a8fd6850172dce89f96a42e9 Add test requested in earlier CL by Brian Wilkerson - d3945b56f737ccaf55c86ef0795fce931f44ee7e [ Service ] Fix issue where VM would hang in ServiceIsola... by Ben Konyi - 7bb0d1f39ade6235cb73784a1e8443ad055edb12 [package:js] Add error for params in factories of anon cl... by Srujan Gaddam - 43c68d782df40b28873d9814ce2958ef30a3ff40 bulk fix for `prefer_adjacent_string_concatenation` by pq - 3ec7ea15ecef3afcb1a569f00c452761c22ccad2 [vm] Fix some Fuchsia tests by Liam Appelbe - 6a66061703ee4d955b0720b58e8520f2b9368444 [ Service ] Add getWebSocketTarget RPC to allow for Obser... by Ben Konyi - 536b466be39d0b2e2f4019f83a9f224e45ef11e3 Add tests for unused_element for mixins by Brian Wilkerson - 7723fbb46da686ac392a0d718ffb262cc32b7398 Don't report DEAD_CODE hint for all required switch/case ... by Konstantin Shcheglov - 2bf777ea6e4329cd4f7b651731beee45e09d8db2 bulk fix for `omit_local_variable_types` by pq - 987c9e347ca4e4fce5926b629b929f925a39dee8 bulk fix for `null_closures` by pq - ac79402072ac2043a78b3bec44d39653fb894761 remove-duplicate-case should not remove statements if the... by Brian Wilkerson - 40b880ee61a7896c0ba34f066607c4843df3843e [ddc] Fix import names in ddb script by Nicholas Shahan - d427ab510d93927fc6a772b6caa6d4e7e5df42dd bulk fix for `no_duplicate_case_values` by pq - 6b9b934d84c2ef88248ff4cf5235371a30fda3c4 [vm, gc] Reduce growth rate as the heap size approaches -... by Ryan Macnak - 0f0e04ec3afaa8265b8ec9e5d3e579c0a0c6c9d0 Add empty value class transformer. by jlcontreras - 25e08663cef1f2a7b755c37e642b2e141c93e5bb [cfe] Pass return type as part of callee type for invocat... by Johnni Winther - 56ba48670941b50934d6d6eeb22f276ffb1965c9 [co19] Roll co19 to e3d6ccfe8fa278041316f7dc958fdd8c2b447... by Alexander Thomas - ad9c73cb327098e1a31cfe7520ef037d5afe90c8 [cfe] Use promoted type as type context in variable assig... by Johnni Winther PiperOrigin-RevId: 324772678 --- pkgs/mockito/test/builder/auto_mocks_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 0a6d762db..bfcde9f8f 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2045,7 +2045,7 @@ Future _testWithNonNullable(Map sourceAssets, var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); await withEnabledExperiments( () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, @@ -2063,7 +2063,7 @@ Future _expectSingleNonNullableOutput( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); await withEnabledExperiments( @@ -2091,7 +2091,7 @@ void _expectBuilderThrows( var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 9)) + languageVersion: LanguageVersion(2, 10)) ]); expect( From 76ba45df098607a4621a5224ffbfd6d9846605bb Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 18 Aug 2020 11:03:56 -0400 Subject: [PATCH 224/607] Remove Fake class; export Fake class from test_api. PiperOrigin-RevId: 327228626 --- pkgs/mockito/CHANGELOG.md | 3 ++- pkgs/mockito/lib/mockito.dart | 3 ++- pkgs/mockito/lib/src/mock.dart | 49 +--------------------------------- pkgs/mockito/pubspec.yaml | 2 +- 4 files changed, 6 insertions(+), 51 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 897214148..a4027537e 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,7 +6,8 @@ clients who use the Mock class in unconventional ways, such as overriding `noSuchMethod` on a class which extends Mock. To fix, or prepare such code, add a second parameter to such overriding `noSuchMethod` declaration. -* Increase minimum Dart SDK to `2.4.0`. +* Increase minimum Dart SDK to `2.7.0`. +* Remove Fake class; export identical Fake class from the test_api package. ## 4.1.1 diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 10fb4c503..7399084f3 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +export 'package:test_api/fake.dart' show Fake; + export 'src/mock.dart' show - Fake, Mock, named, // ignore: deprecated_member_use_from_same_package diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index fe7d47c15..e2d416634 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,6 +17,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; +import 'package:test_api/fake.dart'; // ignore: deprecated_member_use import 'package:test_api/test_api.dart'; // TODO(srawlins): Remove this when we no longer need to check for an @@ -170,54 +171,6 @@ class Mock { _realCallsToString(_realCalls.where((call) => !call.verified)); } -/// Extend or mixin this class to mark the implementation as a [Fake]. -/// -/// 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]); - } -} - typedef _ReturnsCannedResponse = CallPair Function(); // When using an [ArgMatcher], we transform our invocation to have knowledge of diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b7c8aac12..5edb46283 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: matcher: ^0.12.3 meta: '>=1.0.4 <1.2.0' source_gen: ^0.9.6 - test_api: ^0.2.1 + test_api: ^0.2.19-nullsafety dev_dependencies: build_runner: ^1.0.0 From 5d8949cbfdf858c1d7808b355f998e60868ef74d Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 21 Aug 2020 11:03:13 -0400 Subject: [PATCH 225/607] Prepare to release 4.1.2 with test_api/Fake change. PiperOrigin-RevId: 327810426 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a4027537e..f49c8cf85 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.1.2-dev +## 4.1.2 * Introduce experimental code-generated mocks. This is primarily to support the new "Non-nullable by default" (NNBD) type system coming soon to Dart. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 5edb46283..18bfdf4ef 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.2-dev +version: 4.1.2 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito From 3a2b0daf34a573c17b79dadeb0d2560e7dd5851a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 21 Aug 2020 10:01:02 -0700 Subject: [PATCH 226/607] Bump meta to 1.3.0-nullsafety --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 18bfdf4ef..1ef97bf4a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: collection: ^1.1.0 dart_style: ^1.3.6 matcher: ^0.12.3 - meta: '>=1.0.4 <1.2.0' + meta: ^1.3.0-nullsafety source_gen: ^0.9.6 test_api: ^0.2.19-nullsafety From 4d70d650d1b5266c65eb291a600f15f1fd87aa07 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 21 Aug 2020 10:07:47 -0700 Subject: [PATCH 227/607] Fix pedantic; test on 2.9.0 --- pkgs/mockito/.travis.yml | 51 ++++++++++++++++++--------------------- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index bd700e133..4f2aa0f59 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -11,33 +11,30 @@ stages: # 3. Then run tests compiled via dartdevc and dart2js. jobs: include: - # mockito tests cannot run on stable until stable is >= 2.9.0, as "2.9.0" - # is the version required by analyzer. See - # https://github.com/dart-lang/build/issues/2685. - #- stage: presubmit - # name: "2.7.0 analyzer" - # script: ./tool/travis.sh dartanalyzer - # dart: 2.7.0 - #- stage: presubmit - # name: "2.7.0 vm test" - # script: ./tool/travis.sh vm_test - # dart: 2.7.0 - #- stage: build - # name: "2.7.0 DDC build" - # script: ./tool/travis.sh dartdevc_build - # dart: 2.7.0 - #- stage: testing - # name: "2.7.0 DDC test" - # script: ./tool/travis.sh dartdevc_test - # dart: 2.7.0 - #- stage: testing - # name: "2.7.0 dart2js test" - # script: ./tool/travis.sh dart2js_test - # dart: 2.7.0 - #- stage: testing - # name: "2.7.0 code coverage" - # script: ./tool/travis.sh coverage - # dart: 2.7.0 + - stage: presubmit + name: "2.9.0 analyzer" + script: ./tool/travis.sh dartanalyzer + dart: 2.9.0 + - stage: presubmit + name: "2.9.0 vm test" + script: ./tool/travis.sh vm_test + dart: 2.9.0 + - stage: build + name: "2.9.0 DDC build" + script: ./tool/travis.sh dartdevc_build + dart: 2.9.0 + - stage: testing + name: "2.9.0 DDC test" + script: ./tool/travis.sh dartdevc_test + dart: 2.9.0 + - stage: testing + name: "2.9.0 dart2js test" + script: ./tool/travis.sh dart2js_test + dart: 2.9.0 + - stage: testing + name: "2.9.0 code coverage" + script: ./tool/travis.sh coverage + dart: 2.9.0 - stage: presubmit name: "dev dartfmt" diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1ef97bf4a..8f9d86345 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -22,7 +22,7 @@ dev_dependencies: build_runner: ^1.0.0 build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' - pedantic: '>=1.3.0 <1.9.1' + pedantic: 1.10.0-nullsafety test: ^1.5.1 dependency_overrides: From 5c1a96a671bfb7f933dd62e17dd1d2c7bbd74661 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 21 Aug 2020 10:18:31 -0700 Subject: [PATCH 228/607] Travis yaml --- pkgs/mockito/.travis.yml | 51 +++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index 4f2aa0f59..fde1da4e6 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -11,30 +11,33 @@ stages: # 3. Then run tests compiled via dartdevc and dart2js. jobs: include: - - stage: presubmit - name: "2.9.0 analyzer" - script: ./tool/travis.sh dartanalyzer - dart: 2.9.0 - - stage: presubmit - name: "2.9.0 vm test" - script: ./tool/travis.sh vm_test - dart: 2.9.0 - - stage: build - name: "2.9.0 DDC build" - script: ./tool/travis.sh dartdevc_build - dart: 2.9.0 - - stage: testing - name: "2.9.0 DDC test" - script: ./tool/travis.sh dartdevc_test - dart: 2.9.0 - - stage: testing - name: "2.9.0 dart2js test" - script: ./tool/travis.sh dart2js_test - dart: 2.9.0 - - stage: testing - name: "2.9.0 code coverage" - script: ./tool/travis.sh coverage - dart: 2.9.0 + # mockito tests cannot run on stable until stable is >= 2.10.0, as "2.10.0" + # is the version required by analyzer. See + # https://github.com/dart-lang/build/issues/2685. + #- stage: presubmit + # name: "2.9.0 analyzer" + # script: ./tool/travis.sh dartanalyzer + # dart: 2.9.0 + #- stage: presubmit + # name: "2.9.0 vm test" + # script: ./tool/travis.sh vm_test + # dart: 2.9.0 + #- stage: build + # name: "2.9.0 DDC build" + # script: ./tool/travis.sh dartdevc_build + # dart: 2.9.0 + #- stage: testing + # name: "2.9.0 DDC test" + # script: ./tool/travis.sh dartdevc_test + # dart: 2.9.0 + #- stage: testing + # name: "2.9.0 dart2js test" + # script: ./tool/travis.sh dart2js_test + # dart: 2.9.0 + #- stage: testing + # name: "2.9.0 code coverage" + # script: ./tool/travis.sh coverage + # dart: 2.9.0 - stage: presubmit name: "dev dartfmt" From 3366e5cf547fefb612bd6c60ae4ea74f7d1edd07 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 24 Aug 2020 14:47:12 -0700 Subject: [PATCH 229/607] Add a dev dependency on package_config --- pkgs/mockito/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8f9d86345..d3bc70d59 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -22,6 +22,7 @@ dev_dependencies: build_runner: ^1.0.0 build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' + package_config: ^1.9.3 pedantic: 1.10.0-nullsafety test: ^1.5.1 From 85b3cd2cac146f911d8a61f5b30c552965b16fb5 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 27 Aug 2020 09:44:46 -0700 Subject: [PATCH 230/607] Do not override non-dev dep --- pkgs/mockito/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index d3bc70d59..801e2bc72 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -10,6 +10,9 @@ environment: dependencies: analyzer: ^0.39.15 build: ^1.3.0 + # We don't actually depend directly on build_resolvers, but this version + # constraint is important to avoid certain bugs. + build_resolvers: ^1.3.10 code_builder: ^3.4.0 collection: ^1.1.0 dart_style: ^1.3.6 @@ -25,6 +28,3 @@ dev_dependencies: package_config: ^1.9.3 pedantic: 1.10.0-nullsafety test: ^1.5.1 - -dependency_overrides: - build_resolvers: ^1.3.10 From 7224b45ba8acb512bba9a16ccfefe23119e14f32 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 27 Aug 2020 16:35:30 -0700 Subject: [PATCH 231/607] Remove dep on build_resolvers --- pkgs/mockito/pubspec.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 801e2bc72..75c991e27 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -10,9 +10,6 @@ environment: dependencies: analyzer: ^0.39.15 build: ^1.3.0 - # We don't actually depend directly on build_resolvers, but this version - # constraint is important to avoid certain bugs. - build_resolvers: ^1.3.10 code_builder: ^3.4.0 collection: ^1.1.0 dart_style: ^1.3.6 From 36fb25f79376ede1b8e6a71b58d117f16feb5aba Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 10 Sep 2020 10:18:44 -0700 Subject: [PATCH 232/607] Fix formatting (dart-lang/mockito#278) --- pkgs/mockito/test/invocation_matcher_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 97c8a9e78..c8b55626b 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -172,7 +172,9 @@ void shouldFail(value, Matcher matcher, expected) { } on TestFailure catch (e) { final matcher = expected is String ? equalsIgnoringWhitespace(expected) - : expected is RegExp ? contains(expected) : expected; + : expected is RegExp + ? contains(expected) + : expected; expect(collapseWhitespace(e.message), matcher, reason: reason); } } From df9241e3a18711357f0ceb63fe12aee58008c574 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 30 Sep 2020 14:13:09 -0700 Subject: [PATCH 233/607] Allow latest pkg:pedantic prerelease (dart-lang/mockito#282) * also bump supported analyzer version --- pkgs/mockito/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 75c991e27..aff57bfc2 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.2 +version: 4.1.3-dev description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -8,7 +8,7 @@ environment: sdk: '>=2.7.0 <3.0.0' dependencies: - analyzer: ^0.39.15 + analyzer: '>=0.39.15 <0.41.0' build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.1.0 @@ -23,5 +23,5 @@ dev_dependencies: build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' package_config: ^1.9.3 - pedantic: 1.10.0-nullsafety + pedantic: ^1.10.0-nullsafety test: ^1.5.1 From d2ec576f64efbc5b4165dbf6ed11b610f0cd2c29 Mon Sep 17 00:00:00 2001 From: Zhen Qiu Date: Fri, 25 Sep 2020 20:51:20 -0400 Subject: [PATCH 234/607] Add an exception builder to Mockito throwOnMissingStub. PiperOrigin-RevId: 333842774 --- pkgs/mockito/README.md | 2 +- pkgs/mockito/lib/src/mock.dart | 8 ++++++-- pkgs/mockito/pubspec.yaml | 12 +++++++----- pkgs/mockito/test/mockito_test.dart | 10 ++++++++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 77fa978e9..a6c8a3809 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -166,7 +166,7 @@ verify(cat.lives=9); If an argument other than an ArgMatcher (like [`any`], [`anyNamed`], [`argThat`], [`captureThat`], etc.) is passed to a mock method, then the [`equals`] matcher is used for argument matching. If you need more strict -matching consider use `argThat(same(arg))`. +matching consider use `argThat(identical(arg))`. However, note that `null` cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example: diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index e2d416634..24d2fbad9 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -47,9 +47,13 @@ void setDefaultResponse( /// Opt-into [Mock] throwing [NoSuchMethodError] for unimplemented methods. /// /// The default behavior when not using this is to always return `null`. -void throwOnMissingStub(Mock mock) { +void throwOnMissingStub( + Mock mock, { + void Function(Invocation) exceptionBuilder, +}) { + exceptionBuilder ??= mock._noSuchMethod; mock._defaultResponse = - () => CallPair.allInvocations(mock._noSuchMethod); + () => CallPair.allInvocations(exceptionBuilder); } /// Extend or mixin this class to mark the implementation as a [Mock]. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index aff57bfc2..18bfdf4ef 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.3-dev +version: 4.1.2 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -8,13 +8,13 @@ environment: sdk: '>=2.7.0 <3.0.0' dependencies: - analyzer: '>=0.39.15 <0.41.0' + analyzer: ^0.39.15 build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.1.0 dart_style: ^1.3.6 matcher: ^0.12.3 - meta: ^1.3.0-nullsafety + meta: '>=1.0.4 <1.2.0' source_gen: ^0.9.6 test_api: ^0.2.19-nullsafety @@ -22,6 +22,8 @@ dev_dependencies: build_runner: ^1.0.0 build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' - package_config: ^1.9.3 - pedantic: ^1.10.0-nullsafety + pedantic: '>=1.3.0 <1.9.1' test: ^1.5.1 + +dependency_overrides: + build_resolvers: ^1.3.10 diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 0b4927aa9..e8ce07c17 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -292,6 +292,16 @@ void main() { when(mock.methodWithoutArgs()).thenReturn('A'); expect(() => mock.methodWithoutArgs(), returnsNormally); }); + + test( + 'should throw the exception when a mock was called without a matching' + 'stub and an exception builder is set.', () { + throwOnMissingStub(mock, exceptionBuilder: (_) { + throw Exception('test message'); + }); + when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); + expect(() => mock.methodWithoutArgs(), throwsException); + }); }); test( From f1fd76ffc9675e81589c92140dc669601234c668 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 7 Oct 2020 17:56:01 -0400 Subject: [PATCH 235/607] Manually import https://github.com/dart-lang/mockito/commit/df9241e3a18711357f0ceb63fe12aee58008c574 PiperOrigin-RevId: 335959114 --- pkgs/mockito/pubspec.yaml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 18bfdf4ef..aff57bfc2 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.2 +version: 4.1.3-dev description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -8,13 +8,13 @@ environment: sdk: '>=2.7.0 <3.0.0' dependencies: - analyzer: ^0.39.15 + analyzer: '>=0.39.15 <0.41.0' build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.1.0 dart_style: ^1.3.6 matcher: ^0.12.3 - meta: '>=1.0.4 <1.2.0' + meta: ^1.3.0-nullsafety source_gen: ^0.9.6 test_api: ^0.2.19-nullsafety @@ -22,8 +22,6 @@ dev_dependencies: build_runner: ^1.0.0 build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' - pedantic: '>=1.3.0 <1.9.1' + package_config: ^1.9.3 + pedantic: ^1.10.0-nullsafety test: ^1.5.1 - -dependency_overrides: - build_resolvers: ^1.3.10 From 341a22fd9f2732d1bd8512dfecf3e62fd09dc0b0 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 8 Oct 2020 11:14:50 -0400 Subject: [PATCH 236/607] Prepare for replacing element of FunctionType to FunctionTypeAliasElement. PiperOrigin-RevId: 336087770 --- pkgs/mockito/lib/src/builder.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a0fcf4c8c..fbbf93ba1 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -207,6 +207,9 @@ class _MockTargetGatherer { '$joinedMessages'); } return typeToMock as analyzer.InterfaceType; + } else if (elementToMock is FunctionTypeAliasElement) { + throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: ' + '${elementToMock.displayName}'); } else if (elementToMock is GenericFunctionTypeElement && elementToMock.enclosingElement is FunctionTypeAliasElement) { throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: ' @@ -956,7 +959,13 @@ class _MockLibraryInfo { }); } return TypeReference((b) { - var typedef = element.enclosingElement; + Element typedef; + if (element is FunctionTypeAliasElement) { + typedef = element; + } else { + typedef = element.enclosingElement; + } + b ..symbol = typedef.name ..url = _typeImport(type) From 1aaed3286252dc1a3e88e758b83b694b8457cca5 Mon Sep 17 00:00:00 2001 From: nbosch Date: Tue, 13 Oct 2020 17:48:25 -0400 Subject: [PATCH 237/607] Automatically apply mockito codegen Since this also uses `build_to: source` only the root package will auto apply the builder, and only if it mentions `mockito` as a direct dependency in the pubspec. PiperOrigin-RevId: 336960382 --- pkgs/mockito/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index 755c286db..3945a1470 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -4,3 +4,4 @@ builders: builder_factories: ["buildMocks"] build_extensions: {".dart": [".mocks.dart"]} build_to: source + auto_apply: dependents From da93200ca1eddd99dc1d4d55ccd5152e5ff91d9d Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 13 Oct 2020 19:43:40 -0400 Subject: [PATCH 238/607] Improve steps in null safety README re: build_runner. PiperOrigin-RevId: 336982432 --- pkgs/mockito/NULL_SAFETY_README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 82dddceac..a1402c9a3 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -97,9 +97,10 @@ void main() { } ``` -We need to then run build_runner to generate the new library: +We need to depend on build_runner. Add a dependency in the `pubspec.yaml` file, +under `dev_dependencies`: something like `build_runner: ^1.10.0`. - +The final step is to run build_runner in order to generate the new library: ```shell pub run build_runner build From 195f14f750da68d72eb1b533054d5c8012914785 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 21 Oct 2020 16:49:22 -0400 Subject: [PATCH 239/607] MockBuilder: Avoid a crash on a non-library input file. Also add a test for @GenerateMocks found in a part file. PiperOrigin-RevId: 338332515 --- pkgs/mockito/lib/src/builder.dart | 1 + .../mockito/test/builder/auto_mocks_test.dart | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index fbbf93ba1..32fc274ad 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -40,6 +40,7 @@ import 'package:source_gen/source_gen.dart'; class MockBuilder implements Builder { @override Future build(BuildStep buildStep) async { + if (!await buildStep.resolver.isLibrary(buildStep.inputId)) return; final entryLib = await buildStep.inputLibrary; if (entryLib == null) return; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index bfcde9f8f..a56b83828 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -473,6 +473,47 @@ void main() { ); }); + test('generates mock classes from part files', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + part 'part.dart'; + ''', + 'foo|test/part.dart': ''' + @GenerateMocks([Foo]) + void fooTests() {} + ''' + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + '''), + ), + }, + ); + }); + + test('does not crash upon finding non-library files', () async { + await _testWithNonNullable( + { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent('class Foo {}'), + 'foo|test/foo_test.dart': "part 'part.dart';", + 'foo|test/part.dart': "part of 'foo_test.dart';", + }, + outputs: {}, + ); + }); + test('generates multiple mock classes', () async { await _testWithNonNullable( { From a4bba523eb211c05095344fe95cd5e7f91d0f205 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 21 Oct 2020 17:27:44 -0400 Subject: [PATCH 240/607] Bump mockito to 4.1.3 for release. PiperOrigin-RevId: 338340131 --- pkgs/mockito/CHANGELOG.md | 7 +++++++ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f49c8cf85..54a02e5b3 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.1.3 + +* Allow using analyzer 0.40. +* `throwOnMissingStub` accepts an optional argument, `exceptionBuilder`, which + will be called to build and throw a custom exception when a missing stub is + called. + ## 4.1.2 * Introduce experimental code-generated mocks. This is primarily to support diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index aff57bfc2..e9e4aa62a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.3-dev +version: 4.1.3 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito From add4730ec43a693489bf75f72b6bbefc5ced47cd Mon Sep 17 00:00:00 2001 From: kevmoo Date: Thu, 22 Oct 2020 00:50:37 -0400 Subject: [PATCH 241/607] Add pkg:http back to dev_dependencies Needed so the examples aren't red! PiperOrigin-RevId: 338401898 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/pubspec.yaml | 3 ++- pkgs/mockito/test/deprecated_apis/verify_test.dart | 11 ++++++++++- pkgs/mockito/test/invocation_matcher_test.dart | 4 +--- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 54a02e5b3..c67c0f078 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.1.4-dev + +* Add `http` back to `dev_dependencies`. It's used by the example. + ## 4.1.3 * Allow using analyzer 0.40. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index e9e4aa62a..51076c30f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 4.1.3 +version: 4.1.4-dev description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -22,6 +22,7 @@ dev_dependencies: build_runner: ^1.0.0 build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' + http: ^0.12.0 package_config: ^1.9.3 pedantic: ^1.10.0-nullsafety test: ^1.5.1 diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index 74cfdcba8..b99838893 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -78,6 +78,15 @@ String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' void main() { _MockedClass mock; + // google3-specific: dart2js writes minified method names differently. + var isDart2js = true; + // asserts are not run in dart2js. + assert(() { + isDart2js = false; + return true; + }()); + // END google3-specific. + setUp(() { mock = _MockedClass(); }); @@ -131,6 +140,6 @@ void main() { verify(mock.methodWithObjArgs(_MockedClass())); }); verify(mock.methodWithObjArgs(m1)); - }); + }, skip: isDart2js); // google3-specific skip. }); } diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index c8b55626b..97c8a9e78 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -172,9 +172,7 @@ void shouldFail(value, Matcher matcher, expected) { } on TestFailure catch (e) { final matcher = expected is String ? equalsIgnoringWhitespace(expected) - : expected is RegExp - ? contains(expected) - : expected; + : expected is RegExp ? contains(expected) : expected; expect(collapseWhitespace(e.message), matcher, reason: reason); } } From 4d0345bd95a4e40fe35a0e4c5d3dcfde74bae101 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 22 Oct 2020 14:44:32 -0400 Subject: [PATCH 242/607] Remove unused dart:async imports. As of Dart 2.1, Future/Stream have been exported from dart:core. PiperOrigin-RevId: 338516982 --- pkgs/mockito/test/deprecated_apis/mockito_test.dart | 2 -- pkgs/mockito/test/mockito_test.dart | 2 -- 2 files changed, 4 deletions(-) diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index ab9a4e33e..8f32a7074 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -17,8 +17,6 @@ @deprecated library mockito.test.deprecated_apis.mockito_test; -import 'dart:async'; - import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index e8ce07c17..2625e6df5 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'dart:async'; - import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; From a896078e26393affdae706911ddf95176cf08bdd Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 17 Nov 2020 10:35:10 -0500 Subject: [PATCH 243/607] Migrate Mockito to null safety. Most changes are very small. Most test classes' APIs were made entirely nullable to be tested w/o the cod-gen API. The code-gen API (builder.dart, annotations.dart, and builder/ tests) was not made null safety yet. That will be a follow up. PiperOrigin-RevId: 342859489 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/annotations.dart | 2 + pkgs/mockito/lib/src/builder.dart | 2 + pkgs/mockito/lib/src/invocation_matcher.dart | 3 +- pkgs/mockito/lib/src/mock.dart | 38 +++++++++---------- pkgs/mockito/pubspec.yaml | 4 +- .../mockito/test/builder/auto_mocks_test.dart | 2 + .../test/builder/custom_mocks_test.dart | 2 + pkgs/mockito/test/capture_test.dart | 13 +++---- .../test/deprecated_apis/capture_test.dart | 9 ++--- .../test/deprecated_apis/mockito_test.dart | 13 +++---- .../deprecated_apis/until_called_test.dart | 21 +++++----- .../test/deprecated_apis/verify_test.dart | 15 ++++---- .../mockito/test/invocation_matcher_test.dart | 14 +++---- pkgs/mockito/test/mockito_test.dart | 14 +++---- pkgs/mockito/test/nnbd_support_test.dart | 8 ++-- pkgs/mockito/test/until_called_test.dart | 23 ++++++----- pkgs/mockito/test/utils.dart | 2 +- pkgs/mockito/test/verify_test.dart | 30 +++++++-------- 19 files changed, 109 insertions(+), 108 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index c67c0f078..dd1ced662 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.1.4-dev +## 5.0.0-dev * Add `http` back to `dev_dependencies`. It's used by the example. diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 36540c66b..5c49c08db 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + /// An annotation to direct Mockito to generate mock classes. /// /// During [code generation][NULL_SAFETY_README], Mockito will generate a diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 32fc274ad..2ad760b8c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index 0b34f4644..7d8d5e905 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -14,7 +14,6 @@ import 'package:collection/collection.dart'; import 'package:matcher/matcher.dart'; -import 'package:meta/meta.dart'; import 'package:mockito/src/mock.dart'; /// Returns a matcher that expects an invocation that matches arguments given. @@ -78,7 +77,7 @@ class _InvocationSignature extends Invocation { final bool isSetter; _InvocationSignature({ - @required this.memberName, + required this.memberName, this.positionalArguments = const [], this.namedArguments = const {}, this.isGetter = false, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 24d2fbad9..247f44f88 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -28,8 +28,8 @@ import 'package:test_api/src/backend/invoker.dart'; bool _whenInProgress = false; bool _untilCalledInProgress = false; bool _verificationInProgress = false; -_WhenCall _whenCall; -_UntilCall _untilCall; +_WhenCall? _whenCall; +_UntilCall? _untilCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; final _TimeStampProvider _timer = _TimeStampProvider(); final List _capturedArgs = []; @@ -49,11 +49,11 @@ void setDefaultResponse( /// The default behavior when not using this is to always return `null`. void throwOnMissingStub( Mock mock, { - void Function(Invocation) exceptionBuilder, + void Function(Invocation)? exceptionBuilder, }) { exceptionBuilder ??= mock._noSuchMethod; mock._defaultResponse = - () => CallPair.allInvocations(exceptionBuilder); + () => CallPair.allInvocations(exceptionBuilder!); } /// Extend or mixin this class to mark the implementation as a [Mock]. @@ -103,8 +103,8 @@ class Mock { final _realCalls = []; final _responses = >[]; - String _givenName; - int _givenHashCode; + String? _givenName; + int? _givenHashCode; _ReturnsCannedResponse _defaultResponse = () => _nullResponse; @@ -120,7 +120,7 @@ class Mock { /// return type. @override @visibleForTesting - dynamic noSuchMethod(Invocation invocation, [Object /*?*/ returnValue]) { + dynamic noSuchMethod(Invocation invocation, [Object? returnValue]) { // noSuchMethod is that 'magic' that allows us to ignore implementing fields // and methods and instead define them later at compile-time per instance. invocation = _useMatchedInvocationIfSet(invocation); @@ -158,7 +158,7 @@ class Mock { @override String toString() => _givenName ?? runtimeType.toString(); - String _realCallsToString([Iterable realCalls]) { + String _realCallsToString([Iterable? realCalls]) { var stringRepresentations = (realCalls ?? _realCalls).map((call) => call.toString()); if (stringRepresentations.any((s) => s.contains('\n'))) { @@ -347,7 +347,7 @@ class _InvocationForMatchedArguments extends Invocation { 'This function does not provide value; hashCode and toString() can be ' 'stubbed individually. This function may be deleted as early as Mockito ' '5.0.0') -T named(T mock, {String name, int hashCode}) => mock +T named(T mock, {String? name, int? hashCode}) => mock .._givenName = name .._givenHashCode = hashCode; @@ -381,7 +381,7 @@ class PostExpectation { /// Store an exception to throw when this method stub is called. void thenThrow(throwable) { - return _completeWhen((_) { + return _completeWhen((Invocation _) { throw throwable; }); } @@ -399,7 +399,7 @@ class PostExpectation { 'No method stub was called from within `when()`. Was a real method ' 'called, or perhaps an extension method?'); } - _whenCall._setExpected(answer); + _whenCall!._setExpected(answer); _whenCall = null; _whenInProgress = false; } @@ -604,7 +604,7 @@ class _UntilCall { class _VerifyCall { final Mock mock; final Invocation verifyInvocation; - List matchingInvocations; + late List matchingInvocations; _VerifyCall(this.mock, this.verifyInvocation) { var expectedMatcher = InvocationMatcher(verifyInvocation); @@ -682,24 +682,24 @@ Null captureAnyNamed(String named) => _registerMatcher(anything, true, named: named, argumentMatcher: 'captureAnyNamed'); /// An argument matcher that matches an argument that matches [matcher]. -Null argThat(Matcher matcher, {String named}) => +Null argThat(Matcher matcher, {String? named}) => _registerMatcher(matcher, false, named: named, argumentMatcher: 'argThat'); /// An argument matcher that matches an argument that matches [matcher], and /// captures the argument for later access with `captured`. -Null captureThat(Matcher matcher, {String named}) => +Null captureThat(Matcher matcher, {String? named}) => _registerMatcher(matcher, true, named: named, argumentMatcher: 'captureThat'); @Deprecated('ArgMatchers no longer need to be wrapped in Mockito 3.0') -Null typed(ArgMatcher matcher, {String named}) => null; +Null typed(ArgMatcher? matcher, {String? named}) => null; @Deprecated('Replace with `argThat`') -Null typedArgThat(Matcher matcher, {String named}) => +Null typedArgThat(Matcher matcher, {String? named}) => argThat(matcher, named: named); @Deprecated('Replace with `captureThat`') -Null typedCaptureThat(Matcher matcher, {String named}) => +Null typedCaptureThat(Matcher matcher, {String? named}) => captureThat(matcher, named: named); /// Registers [matcher] into the stored arguments collections. @@ -710,7 +710,7 @@ Null typedCaptureThat(Matcher matcher, {String named}) => /// [argumentMatcher] is the name of the public API used to register [matcher], /// for error messages. Null _registerMatcher(Matcher matcher, bool capture, - {String named, String argumentMatcher}) { + {String? named, String? argumentMatcher}) { if (!_whenInProgress && !_untilCalledInProgress && !_verificationInProgress) { // It is not meaningful to store argument matchers outside of stubbing // (`when`), or verification (`verify` and `untilCalled`). Such argument @@ -1059,7 +1059,7 @@ InvocationLoader get untilCalled { _untilCalledInProgress = true; return (T _) { _untilCalledInProgress = false; - return _untilCall.invocationFuture; + return _untilCall!.invocationFuture; }; } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 51076c30f..33d142012 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,11 +1,11 @@ name: mockito -version: 4.1.4-dev +version: 5.0.0-nullsafety.0-dev description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.7.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: analyzer: '>=0.39.15 <0.41.0' diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index a56b83828..06dd375c5 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + @TestOn('vm') import 'package:build/build.dart'; import 'package:build/experiments.dart'; diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 586c7c078..778c977d8 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + @TestOn('vm') import 'package:build/build.dart'; import 'package:build/experiments.dart'; diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 7c597ba38..c4423b2de 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -18,12 +18,11 @@ import 'package:test/test.dart'; import 'utils.dart'; class _RealClass { - _RealClass innerObj; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; - set setter(String arg) { + String methodWithNormalArgs(int? x) => 'Real'; + String methodWithListArgs(List? x) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; + set setter(String? arg) { throw StateError('I must be mocked'); } } @@ -31,7 +30,7 @@ class _RealClass { class _MockedClass extends Mock implements _RealClass {} void main() { - _MockedClass mock; + late _MockedClass mock; var isNsmForwarding = assessNsmForwarding(); diff --git a/pkgs/mockito/test/deprecated_apis/capture_test.dart b/pkgs/mockito/test/deprecated_apis/capture_test.dart index c4b265792..dfd357609 100644 --- a/pkgs/mockito/test/deprecated_apis/capture_test.dart +++ b/pkgs/mockito/test/deprecated_apis/capture_test.dart @@ -23,10 +23,9 @@ import 'package:test/test.dart'; import '../utils.dart'; class _RealClass { - _RealClass innerObj; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; + String methodWithNormalArgs(int? x) => 'Real'; + String methodWithListArgs(List? x) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; set setter(String arg) { throw StateError('I must be mocked'); } @@ -35,7 +34,7 @@ class _RealClass { class MockedClass extends Mock implements _RealClass {} void main() { - MockedClass mock; + late MockedClass mock; var isNsmForwarding = assessNsmForwarding(); diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart index 8f32a7074..7941e5a67 100644 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ b/pkgs/mockito/test/deprecated_apis/mockito_test.dart @@ -21,13 +21,12 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class _RealClass { - _RealClass innerObj; String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithNormalArgs(int? x) => 'Real'; + String methodWithListArgs(List? x) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String methodWithNamedArgs(int x, {int? y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int? y, int? z}) => 'Real'; String methodWithObjArgs(_RealClass x) => 'Real'; Future methodReturningFuture() => Future.value('Real'); Stream methodReturningStream() => Stream.fromIterable(['Real']); @@ -71,7 +70,7 @@ String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' 'please instead use `verifyNever(...);`.)'; void main() { - _MockedClass mock; + late _MockedClass mock; setUp(() { mock = _MockedClass(); diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart index f4e6801ed..a21b995e2 100644 --- a/pkgs/mockito/test/deprecated_apis/until_called_test.dart +++ b/pkgs/mockito/test/deprecated_apis/until_called_test.dart @@ -23,19 +23,18 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class _RealClass { - _RealClass innerObj; String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - String typeParameterizedFn(List w, List x, - [List y, List z]) => + String methodWithNormalArgs(int? x) => 'Real'; + String methodWithListArgs(List? x) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String methodWithNamedArgs(int? x, {int? y}) => 'Real'; + String methodWithTwoNamedArgs(int x, {int? y, int? z}) => 'Real'; + String methodWithObjArgs(_RealClass? x) => 'Real'; + String typeParameterizedFn(List? w, List? x, + [List? y, List? z]) => 'Real'; String typeParameterizedNamedFn(List w, List x, - {List y, List z}) => + {List? y, List? z}) => 'Real'; String get getter => 'Real'; set setter(String arg) { @@ -73,7 +72,7 @@ class _RealClassController { class MockedClass extends Mock implements _RealClass {} void main() { - MockedClass mock; + late MockedClass mock; setUp(() { mock = MockedClass(); diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart index b99838893..4ae125b52 100644 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ b/pkgs/mockito/test/deprecated_apis/verify_test.dart @@ -21,14 +21,13 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class _RealClass { - _RealClass innerObj; String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; + String methodWithNormalArgs(int? x) => 'Real'; String methodWithListArgs(List x) => 'Real'; - String methodWithOptionalArg([int x]) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithOnlyNamedArgs({int y, int z}) => 'Real'; + String methodWithOptionalArg([int? x]) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String methodWithNamedArgs(int x, {int? y}) => 'Real'; + String methodWithOnlyNamedArgs({int? y, int? z}) => 'Real'; String methodWithObjArgs(_RealClass x) => 'Real'; String get getter => 'Real'; set setter(String arg) { @@ -36,7 +35,7 @@ class _RealClass { } String methodWithLongArgs(LongToString a, LongToString b, - {LongToString c, LongToString d}) => + {LongToString? c, LongToString? d}) => 'Real'; } @@ -76,7 +75,7 @@ String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' 'please instead use `verifyNever(...);`.)'; void main() { - _MockedClass mock; + late _MockedClass mock; // google3-specific: dart2js writes minified method names differently. var isDart2js = true; diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 97c8a9e78..b3e39af1b 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -15,8 +15,6 @@ import 'package:mockito/src/invocation_matcher.dart'; import 'package:test/test.dart'; -Invocation lastInvocation; - void main() { const stub = Stub(); @@ -144,16 +142,16 @@ abstract class Interface { bool get value; set value(value); void say(String text); - void eat(String food, {bool alsoDrink}); - void lie([bool facingDown]); - void fly({int miles}); + void eat(String food, {bool? alsoDrink}); + void lie([bool? facingDown]); + void fly({int? miles}); } /// An example of a class that captures Invocation objects. /// /// Any call always returns an [Invocation]. class Stub implements Interface { - static /*late*/ Invocation lastInvocation; + static late Invocation lastInvocation; const Stub(); @@ -172,7 +170,9 @@ void shouldFail(value, Matcher matcher, expected) { } on TestFailure catch (e) { final matcher = expected is String ? equalsIgnoringWhitespace(expected) - : expected is RegExp ? contains(expected) : expected; + : expected is RegExp + ? contains(expected) + : expected; expect(collapseWhitespace(e.message), matcher, reason: reason); } } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 2625e6df5..4428a2fcf 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -18,13 +18,13 @@ import 'package:test/test.dart'; import 'utils.dart'; class _RealClass { - _RealClass innerObj; + _RealClass? innerObj; String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; + String methodWithNormalArgs(int? x) => 'Real'; + String methodWithListArgs(List? x) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String methodWithNamedArgs(int? x, {int? y}) => 'Real'; + String methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; String methodWithObjArgs(_RealClass x) => 'Real'; Future methodReturningFuture() => Future.value('Real'); Stream methodReturningStream() => Stream.fromIterable(['Real']); @@ -64,7 +64,7 @@ void expectFail(String expectedMessage, void Function() expectedToFail) { } void main() { - _MockedClass mock; + late _MockedClass mock; var isNsmForwarding = assessNsmForwarding(); diff --git a/pkgs/mockito/test/nnbd_support_test.dart b/pkgs/mockito/test/nnbd_support_test.dart index faf089872..9e31129fc 100644 --- a/pkgs/mockito/test/nnbd_support_test.dart +++ b/pkgs/mockito/test/nnbd_support_test.dart @@ -16,23 +16,23 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class Foo { - String /*?*/ returnsNullableString() => 'Hello'; + String? returnsNullableString() => 'Hello'; // TODO(srawlins): When it becomes available, opt this test library into NNBD, // and make this method really return a non-nullable String. - String /*!*/ returnsNonNullableString() => 'Hello'; + String returnsNonNullableString() => 'Hello'; } class MockFoo extends Mock implements Foo { @override - String /*!*/ returnsNonNullableString() { + String returnsNonNullableString() { return super.noSuchMethod( Invocation.method(#returnsNonNullableString, []), 'Dummy') as String; } } void main() { - MockFoo mock; + late MockFoo mock; setUp(() { mock = MockFoo(); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index 4a0b03fd9..316119b23 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -18,19 +18,18 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class _RealClass { - _RealClass innerObj; String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int y, int z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - String typeParameterizedFn(List w, List x, - [List y, List z]) => + String methodWithNormalArgs(int? x) => 'Real'; + String methodWithListArgs(List? x) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String methodWithNamedArgs(int? x, {int? y}) => 'Real'; + String methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; + String methodWithObjArgs(_RealClass? x) => 'Real'; + String typeParameterizedFn(List? w, List? x, + [List? y, List? z]) => 'Real'; - String typeParameterizedNamedFn(List w, List x, - {List y, List z}) => + String typeParameterizedNamedFn(List? w, List? x, + {List? y, List? z}) => 'Real'; String get getter => 'Real'; set setter(String arg) { @@ -68,7 +67,7 @@ class _RealClassController { class _MockedClass extends Mock implements _RealClass {} void main() { - _MockedClass mock; + late _MockedClass mock; setUp(() { mock = _MockedClass(); diff --git a/pkgs/mockito/test/utils.dart b/pkgs/mockito/test/utils.dart index 86cb4ecf0..9d71f6fc5 100644 --- a/pkgs/mockito/test/utils.dart +++ b/pkgs/mockito/test/utils.dart @@ -1,7 +1,7 @@ import 'package:mockito/mockito.dart'; abstract class NsmForwardingSignal { - void fn([int a]); + void fn([int? a]); } class MockNsmForwardingSignal extends Mock implements NsmForwardingSignal {} diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 62814ed27..10e7852c7 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -18,22 +18,21 @@ import 'package:test/test.dart'; import 'utils.dart'; class _RealClass { - _RealClass innerObj; String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int x) => 'Real'; + String methodWithNormalArgs(int? x) => 'Real'; String methodWithListArgs(List x) => 'Real'; - String methodWithOptionalArg([int x]) => 'Real'; - String methodWithPositionalArgs(int x, [int y]) => 'Real'; - String methodWithNamedArgs(int x, {int y}) => 'Real'; - String methodWithOnlyNamedArgs({int y, int z}) => 'Real'; + String methodWithOptionalArg([int? x]) => 'Real'; + String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String methodWithNamedArgs(int x, {int? y}) => 'Real'; + String methodWithOnlyNamedArgs({int? y, int? z}) => 'Real'; String methodWithObjArgs(_RealClass x) => 'Real'; String get getter => 'Real'; set setter(String arg) { throw StateError('I must be mocked'); } - String methodWithLongArgs(LongToString a, LongToString b, - {LongToString c, LongToString d}) => + String methodWithLongArgs(LongToString? a, LongToString? b, + {LongToString? c, LongToString? d}) => 'Real'; } @@ -69,7 +68,7 @@ const noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' 'please instead use `verifyNever(...);`.)'; void main() { - _MockedClass mock; + late _MockedClass mock; var isNsmForwarding = assessNsmForwarding(); @@ -190,7 +189,6 @@ void main() { var badHelper = () => throw 'boo'; try { verify(mock.methodWithNamedArgs(42, y: badHelper())); - fail('verify call was expected to throw!'); } catch (_) {} // At this point, verification was interrupted, so // `_verificationInProgress` is still `true`. Calling mock methods below @@ -200,12 +198,14 @@ void main() { try { verify(mock.methodWithNamedArgs(42, y: 17)); fail('verify call was expected to throw!'); - } catch (e) { - expect(e, TypeMatcher()); + } catch (exception) { expect( - e.message, - contains('Verification appears to be in progress. ' - '2 verify calls have been stored.')); + exception, + isA().having( + (exception) => exception.message, + 'message', + contains('Verification appears to be in progress. ' + '2 verify calls have been stored.'))); } }); }); From 3849b22b69490442bd2753a59038de2dcc27bffd Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 19 Nov 2020 17:02:22 -0500 Subject: [PATCH 244/607] Migrate Mockito's annotations to null safety. PiperOrigin-RevId: 343365074 --- pkgs/mockito/lib/annotations.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 5c49c08db..53ecd454a 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - /// An annotation to direct Mockito to generate mock classes. /// /// During [code generation][NULL_SAFETY_README], Mockito will generate a @@ -71,10 +69,10 @@ class GenerateMocks { // TODO(srawlins): Document this in NULL_SAFETY_README.md. // TODO(srawlins): Add 'mixingIn'. class MockSpec { - final Symbol mockName; + final Symbol? mockName; final bool returnNullOnMissingStub; - const MockSpec({Symbol as, this.returnNullOnMissingStub = false}) + const MockSpec({Symbol? as, this.returnNullOnMissingStub = false}) : mockName = as; } From 4313b1d4e49a1f08ffcd97c0b4a9d5c51abe104e Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 20 Nov 2020 14:24:46 -0500 Subject: [PATCH 245/607] Prepare mockito's codegen tests for changes to include inherited members. Many of these codegen tests used an entire library content as their expectation. Sometimes the assertion was that a certain method or constructor _wasn't present_, which was conveyed indirectly, and vaguely. When classes will start to override inherited methods and fields, like hashCode and toString(), the generated mock classes will get bigger, and it is no longer viable to write tests like this. Since this is a huge refactoring of these tests, I'm landing this preparation change first. https://github.com/dart-lang/mockito/issues/297 PiperOrigin-RevId: 343529871 --- .../mockito/test/builder/auto_mocks_test.dart | 626 ++++++++---------- .../test/builder/custom_mocks_test.dart | 251 +++---- 2 files changed, 366 insertions(+), 511 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 06dd375c5..140671653 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -15,6 +15,8 @@ // @dart=2.9 @TestOn('vm') +import 'dart:convert' show utf8; + import 'package:build/build.dart'; import 'package:build/experiments.dart'; import 'package:build_test/build_test.dart'; @@ -69,73 +71,118 @@ MockFoo() { }'''; void main() { + InMemoryAssetWriter writer; + + /// Test [MockBuilder] in a package which has not opted into the non-nullable + /// type system. + Future testPreNonNullable(Map sourceAssets, + {Map*/ dynamic> outputs}) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 7)) + ]); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, outputs: outputs, packageConfig: packageConfig); + } + + /// Test [MockBuilder] in a package which has opted into the non-nullable type + /// system. + Future testWithNonNullable(Map sourceAssets, + {Map>*/ dynamic> outputs}) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 12)) + ]); + // TODO(srawlins): Remove enabled-experiments wrapper. + await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, outputs: outputs, packageConfig: packageConfig), + ['non-nullable'], + ); + } + + /// Test [MockBuilder] on a single source file, in a package which has opted + /// into the non-nullable type system, and with the non-nullable experiment + /// enabled. + Future expectSingleNonNullableOutput( + String sourceAssetText, + /*String|Matcher>*/ dynamic output) async { + await testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': sourceAssetText, + }, outputs: { + 'foo|test/foo_test.mocks.dart': output + }); + } + + /// Builds with [MockBuilder] in a package which has opted into the + /// non-nullable type system, and with the non-nullable experiment enabled, + /// returning the content of the generated mocks library. + Future buildWithSingleNonNullableSource( + String sourceAssetText) async { + await testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': sourceAssetText, + }); + var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + return utf8.decode(writer.assets[mocksAsset]); + } + + setUp(() { + writer = InMemoryAssetWriter(); + }); + test( 'generates a mock class but does not override methods w/ zero parameters', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { dynamic a() => 7; } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect(mocksContent, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('generates a mock class but does not override private methods', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int _b(int x) => 8; } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect(mocksContent, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('generates a mock class but does not override static methods', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - static int c(int y) => 9; + static int method1(int y) => 9; } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('method1'))); }); test('generates a mock class but does not override any extension methods', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' extension X on Foo { dynamic x(int m, String n) => n + 1; } class Foo {} - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect(mocksContent, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('overrides methods, matching required positional parameters', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m(int a) {} @@ -147,7 +194,7 @@ void main() { }); test('overrides methods, matching optional positional parameters', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m(int a, [int b, int c = 0]) {} @@ -159,7 +206,7 @@ void main() { }); test('overrides methods, matching named parameters', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m(int a, {int b, int c = 0}) {} @@ -171,7 +218,7 @@ void main() { }); test('matches parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([int a, int b = 0]) {} @@ -183,7 +230,7 @@ void main() { }); test('matches boolean literal parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([bool a = true, bool b = false]) {} @@ -195,7 +242,7 @@ void main() { }); test('matches number literal parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([int a = 0, double b = 0.5]) {} @@ -207,7 +254,7 @@ void main() { }); test('matches string literal parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([String a = 'Hello', String b = 'Hello ' r"World"]) {} @@ -220,7 +267,7 @@ void main() { }); test('matches empty collection literal parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([List a = const [], Map b = const {}]) {} @@ -233,7 +280,7 @@ void main() { }); test('matches non-empty list literal parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([List a = const [1, 2, 3]]) {} @@ -245,7 +292,7 @@ void main() { }); test('matches non-empty map literal parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([Map a = const {1: 'a', 2: 'b'}]) {} @@ -258,7 +305,7 @@ void main() { }); test('matches non-empty map literal parameter default values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([Map a = const {1: 'a', 2: 'b'}]) {} @@ -272,7 +319,7 @@ void main() { test('matches parameter default values constructed from a local class', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([Bar a = const Bar()]) {} @@ -288,7 +335,7 @@ void main() { test('matches parameter default values constructed from a Dart SDK class', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([Duration a = const Duration(days: 1)]) {} @@ -301,7 +348,7 @@ void main() { test('matches parameter default values constructed from a named constructor', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([Bar a = const Bar.named()]) {} @@ -317,7 +364,7 @@ void main() { test('matches parameter default values constructed with positional arguments', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([Bar a = const Bar(7)]) {} @@ -334,7 +381,7 @@ void main() { test('matches parameter default values constructed with named arguments', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([Bar a = const Bar(i: 7)]) {} @@ -351,7 +398,7 @@ void main() { test('matches parameter default values constructed with top-level variable', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m([int a = x]) {} @@ -365,7 +412,7 @@ void main() { test('matches parameter default values constructed with static field', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { static const x = 1; @@ -440,7 +487,7 @@ void main() { }); test('overrides async methods legally', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Future m() async => print(s); @@ -452,7 +499,7 @@ void main() { }); test('overrides async* methods legally', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Stream m() async* { yield 7; } @@ -464,7 +511,7 @@ void main() { }); test('overrides sync* methods legally', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Iterable m() sync* { yield 7; } @@ -476,7 +523,7 @@ void main() { }); test('generates mock classes from part files', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -505,7 +552,7 @@ void main() { }); test('does not crash upon finding non-library files', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent('class Foo {}'), @@ -517,7 +564,7 @@ void main() { }); test('generates multiple mock classes', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -551,7 +598,7 @@ void main() { }); test('generates mock classes from multiple annotations', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -588,7 +635,7 @@ void main() { test('generates mock classes from multiple annotations on a single element', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -623,20 +670,17 @@ void main() { }); test('generates generic mock classes', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo {} - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('generates generic mock classes with type bounds', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -670,7 +714,7 @@ void main() { }); test('writes dynamic, void w/o import prefix', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m(dynamic a, int b) {} @@ -684,7 +728,7 @@ void main() { }); test('writes type variables types w/o import prefixes', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void m(T a) {} @@ -697,44 +741,19 @@ void main() { }); test('imports libraries for external class types', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { dynamic f(List list) {} } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - MockFoo() { - _i1.throwOnMissingStub(this); - } - - dynamic f(List<_i2.Foo>? list) => - super.noSuchMethod(Invocation.method(#f, [list])); - } - '''), - }, - ); + ''')); + expect(mocksContent, contains("import 'package:foo/foo.dart' as _i2;")); + expect(mocksContent, contains('implements _i2.Foo')); + expect(mocksContent, contains('List<_i2.Foo>? list')); }); test('imports libraries for type aliases with external types', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; typedef Callback = void Function(); typedef void Callback2(); @@ -744,33 +763,16 @@ void main() { dynamic g(Callback2 c) {} dynamic h(Callback3 c) {} } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - MockFoo() { - _i1.throwOnMissingStub(this); - } - - dynamic f(_i2.Callback? c) => super.noSuchMethod(Invocation.method(#f, [c])); - dynamic g(_i2.Callback2? c) => super.noSuchMethod(Invocation.method(#g, [c])); - dynamic h(_i2.Callback3<_i2.Foo>? c) => - super.noSuchMethod(Invocation.method(#h, [c])); - } - '''), - }, - ); + ''')); + expect(mocksContent, contains("import 'package:foo/foo.dart' as _i2;")); + expect(mocksContent, contains('implements _i2.Foo')); + expect(mocksContent, contains('_i2.Callback? c')); + expect(mocksContent, contains('_i2.Callback2? c')); + expect(mocksContent, contains('_i2.Callback3<_i2.Foo>? c')); }); test('prefixes parameter type on generic function-typed parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' import 'dart:async'; class Foo { @@ -783,7 +785,7 @@ void main() { }); test('prefixes return type on generic function-typed parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' import 'dart:async'; class Foo { @@ -796,7 +798,7 @@ void main() { }); test('prefixes parameter type on function-typed parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' import 'dart:async'; class Foo { @@ -809,7 +811,7 @@ void main() { }); test('prefixes return type on function-typed parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' import 'dart:async'; class Foo { @@ -822,7 +824,7 @@ void main() { }); test('widens the type of parameters to be nullable', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(int? a, int b); @@ -836,7 +838,7 @@ void main() { test( 'widens the type of potentially non-nullable type variables to be ' 'nullable', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(int? a, T b); @@ -848,7 +850,7 @@ void main() { }); test('matches nullability of type arguments of a parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(List a, List b); @@ -862,7 +864,7 @@ void main() { test( 'matches nullability of return type of a generic function-typed ' 'parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(int? Function() a, int Function() b); @@ -876,7 +878,7 @@ void main() { test( 'matches nullability of parameter types within a generic function-typed ' 'parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(void Function(int?) a, void Function(int) b); @@ -889,7 +891,7 @@ void main() { test('matches nullability of return type of a function-typed parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(int? a(), int b()); @@ -903,7 +905,7 @@ void main() { test( 'matches nullability of parameter types within a function-typed ' 'parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(void a(int? x), void b(int x)); @@ -915,7 +917,7 @@ void main() { }); test('matches nullability of a generic parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(T? a, T b); @@ -927,7 +929,7 @@ void main() { }); test('matches nullability of a dynamic parameter', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { void m(dynamic a, int b); @@ -939,7 +941,7 @@ void main() { }); test('matches nullability of non-nullable return type', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { int m(int a); @@ -951,7 +953,7 @@ void main() { }); test('matches nullability of nullable return type', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { int? m(int a); @@ -963,7 +965,7 @@ void main() { }); test('matches nullability of return type type arguments', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { List m(int a); @@ -975,7 +977,7 @@ void main() { }); test('matches nullability of nullable type variable return type', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { T? m(int a); @@ -987,7 +989,7 @@ void main() { }); test('overrides implicit return type with dynamic', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { m(int a); @@ -999,7 +1001,7 @@ void main() { }); test('overrides abstract methods', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { dynamic f(int a); @@ -1011,88 +1013,95 @@ void main() { }); test('does not override methods with all nullable parameters', () async { - await _expectSingleNonNullableOutput( - dedent(''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - void a(int? p) {} - void b(dynamic p) {} - void c(var p) {} - void d(final p) {} - void e(int Function()? p) {} + void method1(int? p) {} } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub + ''')); + expect(mocksContent, isNot(contains('method1'))); + }); + + test('does not override methods with all nullable parameters (dynamic)', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class Foo { + void method1(dynamic p) {} } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('method1'))); + }); + + test('does not override methods with all nullable parameters (var untyped)', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class Foo { + void method1(var p) {} + } + ''')); + expect(mocksContent, isNot(contains('method1'))); + }); + + test('does not override methods with all nullable parameters (final untyped)', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class Foo { + void method1(final p) {} + } + ''')); + expect(mocksContent, isNot(contains('method1'))); + }); + + test( + 'does not override methods with all nullable parameters (function-typed)', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class Foo { + void method1(int Function()? p) {} + } + ''')); + expect(mocksContent, isNot(contains('method1'))); }); test('does not override methods with a void return type', () async { - await _expectSingleNonNullableOutput( - dedent(''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { - void m(); - } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub + void method1(); } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('method1'))); }); test('does not override methods with an implicit dynamic return type', () async { - await _expectSingleNonNullableOutput( - dedent(''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { - m(); - } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub + method1(); } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('method1'))); }); test('does not override methods with an explicit dynamic return type', () async { - await _expectSingleNonNullableOutput( - dedent(''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { - dynamic m(); - } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub + dynamic method1(); } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('method1'))); }); test('does not override methods with a nullable return type', () async { - await _expectSingleNonNullableOutput( - dedent(''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { - int? m(); + int? method1(); } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('method1'))); }); test('overrides methods with a non-nullable return type', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { int m(); @@ -1104,7 +1113,7 @@ void main() { }); test('overrides methods with a potentially non-nullable parameter', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -1122,7 +1131,7 @@ void main() { }); test('overrides generic methods', () async { - await _testWithNonNullable( + await testWithNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -1156,7 +1165,7 @@ void main() { }); test('overrides non-nullable instance getters', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { int get m => 7; @@ -1168,34 +1177,28 @@ void main() { }); test('does not override nullable instance getters', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - int? get m => 7; - } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub + int? get getter1 => 7; } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('getter1'))); }); test('overrides non-nullable instance setters', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { void set m(int a) {} } - '''), - _containsAllOf( - 'set m(int? a) => super.noSuchMethod(Invocation.setter(#m, [a]));'), - ); + ''')); + expect( + mocksContent, + contains( + 'set m(int? a) => super.noSuchMethod(Invocation.setter(#m, [a]));')); }); test('does not override nullable instance setters', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void set m(int? a) {} @@ -1210,7 +1213,7 @@ void main() { }); test('overrides non-nullable fields', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { int m; @@ -1223,7 +1226,7 @@ void main() { }); test('overrides final non-nullable fields', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { final int m; @@ -1236,52 +1239,34 @@ void main() { }); test('does not override nullable fields', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - int? m; - } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub + int? field1; } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('field1'))); }); test('does not override private fields', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - int _a; + int _field1; } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('int _field1'))); }); test('does not override static fields', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - static int b; + static int field1; } - '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - ); + ''')); + expect(mocksContent, isNot(contains('int field1'))); }); test('overrides binary operators', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { int operator +(Foo other) => 7; @@ -1293,7 +1278,7 @@ void main() { }); test('overrides index operators', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { int operator [](int x) => 7; @@ -1305,7 +1290,7 @@ void main() { }); test('overrides unary operators', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { int operator ~() => 7; @@ -1317,7 +1302,7 @@ void main() { }); test('creates dummy non-null bool return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { bool m() => false; @@ -1329,7 +1314,7 @@ void main() { }); test('creates dummy non-null double return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { double m() => 3.14; @@ -1341,7 +1326,7 @@ void main() { }); test('creates dummy non-null int return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { int m() => 7; @@ -1353,7 +1338,7 @@ void main() { }); test('creates dummy non-null String return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { String m() => "Hello"; @@ -1365,7 +1350,7 @@ void main() { }); test('creates dummy non-null List return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { List m() => [Foo()]; @@ -1377,7 +1362,7 @@ void main() { }); test('creates dummy non-null Set return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Set m() => {Foo()}; @@ -1389,7 +1374,7 @@ void main() { }); test('creates dummy non-null Map return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Map m() => {7: Foo()}; @@ -1401,7 +1386,7 @@ void main() { }); test('creates dummy non-null raw-typed return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { Map m(); @@ -1414,7 +1399,7 @@ void main() { test('creates dummy non-null return values for Futures of known core classes', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Future m() async => false; @@ -1426,7 +1411,7 @@ void main() { }); test('creates dummy non-null Stream return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { Stream m(); @@ -1438,7 +1423,7 @@ void main() { }); test('creates dummy non-null return values for unknown classes', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Bar m() => Bar('name'); @@ -1454,7 +1439,7 @@ void main() { }); test('creates dummy non-null return values for generic type', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' abstract class Foo { Bar m(); @@ -1467,7 +1452,7 @@ void main() { }); test('creates dummy non-null return values for enums', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Bar m1() => Bar('name'); @@ -1485,7 +1470,7 @@ void main() { test( 'creates a dummy non-null function-typed return value, with optional ' 'parameters', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void Function(int, [String]) m() => (int i, [String s]) {}; @@ -1499,7 +1484,7 @@ void main() { test( 'creates a dummy non-null function-typed return value, with named ' 'parameters', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { void Function(Foo, {bool b}) m() => (Foo f, {bool b}) {}; @@ -1513,7 +1498,7 @@ void main() { test( 'creates a dummy non-null function-typed return value, with non-core ' 'return type', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Foo Function() m() => () => Foo(); @@ -1526,7 +1511,7 @@ void main() { test('creates a dummy non-null generic function-typed return value', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { T? Function(T) m() => (int i, [String s]) {}; @@ -1540,7 +1525,7 @@ void main() { }); test('generates a fake class used in return values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Bar m1() => Bar('name1'); @@ -1552,7 +1537,7 @@ void main() { }); test('generates a fake generic class used in return values', () async { - await _expectSingleNonNullableOutput( + await expectSingleNonNullableOutput( dedent(r''' class Foo { Bar m1() => Bar('name1'); @@ -1565,8 +1550,7 @@ void main() { }); test('deduplicates fake classes', () async { - await _expectSingleNonNullableOutput( - dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { Bar m1() => Bar('name1'); Bar m2() => Bar('name2'); @@ -1575,26 +1559,11 @@ void main() { final String name; Bar(this.name); } - '''), - dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - class _FakeBar extends _i1.Fake implements _i2.Bar {} - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - MockFoo() { - _i1.throwOnMissingStub(this); - } - - _i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar()); - _i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar()); - } - '''), - ); + ''')); + var mocksContentLines = mocksContent.split('\n'); + // The _FakeBar class should be generated exactly once. + expect(mocksContentLines.where((line) => line.contains('class _FakeBar')), + hasLength(1)); }); test('throws when GenerateMocks is given a class multiple times', () async { @@ -2041,7 +2010,7 @@ void main() { test('given a pre-non-nullable library, does not override any members', () async { - await _testPreNonNullable( + await testPreNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -2064,65 +2033,6 @@ void main() { }); } -/// Test [MockBuilder] in a package which has not opted into the non-nullable -/// type system. -/// -/// Whether the non-nullable experiment is enabled depends on the SDK executing -/// this test, but that does not affect the opt-in state of the package under -/// test. -Future _testPreNonNullable(Map sourceAssets, - {Map*/ dynamic> outputs}) async { - var packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 7)) - ]); - await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, - outputs: outputs, packageConfig: packageConfig); -} - -/// Test [MockBuilder] in a package which has opted into the non-nullable type -/// system, and with the non-nullable experiment enabled. -Future _testWithNonNullable(Map sourceAssets, - {Map>*/ dynamic> outputs}) async { - var packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) - ]); - await withEnabledExperiments( - () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, - outputs: outputs, packageConfig: packageConfig), - ['non-nullable'], - ); -} - -/// Test [MockBuilder] on a single source file, in a package which has opted -/// into the non-nullable type system, and with the non-nullable experiment -/// enabled. -Future _expectSingleNonNullableOutput( - String sourceAssetText, - /*String|Matcher>*/ dynamic output) async { - var packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) - ]); - - await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': sourceAssetText, - }, - outputs: {'foo|test/foo_test.mocks.dart': output}, - packageConfig: packageConfig), - ['non-nullable'], - ); -} - TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 778c977d8..8eba278c7 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -15,6 +15,8 @@ // @dart=2.9 @TestOn('vm') +import 'dart:convert' show utf8; + import 'package:build/build.dart'; import 'package:build/experiments.dart'; import 'package:build_test/build_test.dart'; @@ -69,116 +71,127 @@ MockFoo() { }'''; void main() { + InMemoryAssetWriter writer; + + /// Test [MockBuilder] in a package which has not opted into the non-nullable + /// type system. + Future testPreNonNullable(Map sourceAssets, + {Map*/ dynamic> outputs}) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 7)) + ]); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + outputs: outputs, packageConfig: packageConfig); + } + + /// Builds with [MockBuilder] in a package which has opted into the + /// non-nullable type system, and with the non-nullable experiment enabled, + /// returning the content of the generated mocks library. + Future buildWithNonNullable(Map sourceAssets) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 10)) + ]); + await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, packageConfig: packageConfig), + ['non-nullable'], + ); + var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + return utf8.decode(writer.assets[mocksAsset]); + } + + setUp(() { + writer = InMemoryAssetWriter(); + }); + test('generates a generic mock class without type arguments', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - }, - ); + }); + expect(mocksContent, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('generates a generic mock class with type arguments', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks( [], customMocks: [MockSpec>(as: #MockFooOfIntBool)]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFooOfIntBool extends _i1.Mock implements _i2.Foo { - MockFooOfIntBool() { - _i1.throwOnMissingStub(this); - } - } - ''')), - }, - ); + }); + expect( + mocksContent, + contains( + 'class MockFooOfIntBool extends _i1.Mock implements _i2.Foo')); }); test('generates a generic mock class with type arguments but no name', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([], customMocks: [MockSpec>()]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - }, - ); + }); + expect(mocksContent, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('generates a generic, bounded mock class without type arguments', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), - }, - ); + }); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('generates mock classes from multiple annotations', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} class Bar {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([], customMocks: [MockSpec()]) @@ -186,38 +199,24 @@ void main() { @GenerateMocks([], customMocks: [MockSpec()]) void barTests() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - '''), - dedent(''' - class MockBar extends _i1.Mock implements _i2.Bar { - MockBar() { - _i1.throwOnMissingStub(this); - } - } - '''), - ), - }, - ); + }); + expect(mocksContent, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + expect(mocksContent, + contains('class MockBar extends _i1.Mock implements _i2.Bar')); }); test('generates mock classes from multiple annotations on a single element', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/a.dart': dedent(r''' + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/a.dart': dedent(r''' class Foo {} '''), - 'foo|lib/b.dart': dedent(r''' + 'foo|lib/b.dart': dedent(r''' class Foo {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/a.dart' as a; import 'package:foo/b.dart' as b; import 'package:mockito/annotations.dart'; @@ -225,50 +224,29 @@ void main() { @GenerateMocks([], customMocks: [MockSpec(as: #MockBFoo)]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - dedent(''' - class MockAFoo extends _i1.Mock implements _i2.Foo { - MockAFoo() { - _i1.throwOnMissingStub(this); - } - } - '''), - dedent(''' - class MockBFoo extends _i1.Mock implements _i3.Foo { - MockBFoo() { - _i1.throwOnMissingStub(this); - } - } - '''), - ), - }, - ); + }); + expect(mocksContent, + contains('class MockAFoo extends _i1.Mock implements _i2.Foo')); + expect(mocksContent, + contains('class MockBFoo extends _i1.Mock implements _i3.Foo')); }); test( 'generates a mock class which uses the old behavior of returning null on ' 'missing stubs', () async { - await _testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo, returnNullOnMissingStub: true)]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo {} - ''')), - }, - ); + }); + expect(mocksContent, isNot(contains('throwOnMissingStub'))); }); test( @@ -438,7 +416,7 @@ void main() { test('given a pre-non-nullable library, does not override any members', () async { - await _testPreNonNullable( + await testPreNonNullable( { ...annotationsAsset, ...simpleTestAsset, @@ -459,39 +437,6 @@ void main() { }); } -/// Test [MockBuilder] in a package which has not opted into the non-nullable -/// type system. -/// -/// Whether the non-nullable experiment is enabled depends on the SDK executing -/// this test, but that does not affect the opt-in state of the package under -/// test. -Future _testPreNonNullable(Map sourceAssets, - {Map*/ dynamic> outputs}) async { - var packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 7)) - ]); - await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, - outputs: outputs, packageConfig: packageConfig); -} - -/// Test [MockBuilder] in a package which has opted into the non-nullable type -/// system, and with the non-nullable experiment enabled. -Future _testWithNonNullable(Map sourceAssets, - {Map>*/ dynamic> outputs}) async { - var packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) - ]); - await withEnabledExperiments( - () async => await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, - outputs: outputs, packageConfig: packageConfig), - ['non-nullable'], - ); -} - TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); From fcbe11502e1511a563544e91f2845377f90d34c4 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 20 Nov 2020 20:23:34 -0500 Subject: [PATCH 246/607] Codegen API: Override inherited methods and fields. Users expect to be able to stub and verify inherited methods and fields. This work supports that. The tricky parts were making sure we override using substituted type parameters. When writing a MockFoo class for `class Foo extends FooBase`, we need to substitute `int` everywhere `T` is found in a signature. PiperOrigin-RevId: 343594212 --- pkgs/mockito/lib/src/builder.dart | 123 ++++-- .../mockito/test/builder/auto_mocks_test.dart | 379 ++++++++++-------- 2 files changed, 314 insertions(+), 188 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 2ad760b8c..4b96c6248 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -147,9 +147,13 @@ class _MockTargetGatherer { 'Mockito cannot mock `dynamic`'); } final type = _determineDartType(typeToMock, entryLib.typeProvider); - final mockName = 'Mock${type.element.name}'; - mockTargets - .add(_MockTarget(type, mockName, returnNullOnMissingStub: false)); + // [type] is `Foo` for generic classes. Switch to declaration, + // which will yield `Foo`. + final declarationType = + (type.element.declaration as ClassElement).thisType; + final mockName = 'Mock${declarationType.element.name}'; + mockTargets.add(_MockTarget(declarationType, mockName, + returnNullOnMissingStub: false)); } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { @@ -437,18 +441,25 @@ class _MockLibraryInfo { // may not be a way to get around this. for (var i = 0; i < type.typeArguments.length; i++) { var typeArgument = type.typeArguments[i]; - var bound = - type.element.typeParameters[i].bound ?? typeProvider.dynamicType; + // If [typeArgument] is a type parameter, this indicates that no type + // arguments were passed. This likely came from the 'classes' argument of + // GenerateMocks, and [type] is the declaration type (`Foo` vs + // `Foo`). + if (typeArgument is analyzer.TypeParameterType) return false; + // If [type] was given to @GenerateMocks as a Type, and no explicit type // argument is given, [typeArgument] is `dynamic` (_not_ the bound, as one // might think). We determine that an explicit type argument was given if // it is not `dynamic`. - // + if (typeArgument.isDynamic) continue; + // If, on the other hand, [type] was given to @GenerateMock as a type // argument to `Of()`, and no type argument is given, [typeArgument] is // the bound of the corresponding type paramter (dynamic or otherwise). We // determine that an explicit type argument was given if [typeArgument] is - // is not [bound]. + // not [bound]. + var bound = + type.element.typeParameters[i].bound ?? typeProvider.dynamicType; if (!typeArgument.isDynamic && typeArgument != bound) return true; } return false; @@ -504,32 +515,82 @@ class _MockLibraryInfo { if (!sourceLibIsNonNullable) { return; } - for (final field in classToMock.fields) { - if (field.isPrivate || field.isStatic) { - continue; - } - final getter = field.getter; - if (getter != null && _returnTypeIsNonNullable(getter)) { - cBuilder.methods.add( - Method((mBuilder) => _buildOverridingGetter(mBuilder, getter))); - } - final setter = field.setter; - if (setter != null && _hasNonNullableParameter(setter)) { - cBuilder.methods.add( - Method((mBuilder) => _buildOverridingSetter(mBuilder, setter))); - } + cBuilder.methods.addAll(fieldOverrides(typeToMock, {})); + cBuilder.methods.addAll(methodOverrides(typeToMock, {})); + }); + } + + /// Yields all of the field overrides required for [type]. + /// + /// This includes fields of supertypes and mixed in types. [overriddenMethods] + /// is used to track which fields have already been yielded. + /// + /// Only public instance fields which have either a potentially non-nullable + /// return type (for getters) or a parameter with a potentially non-nullable + /// type (for setters) are yielded. + Iterable fieldOverrides( + analyzer.InterfaceType type, Set overriddenFields) sync* { + for (final accessor in type.accessors) { + if (accessor.isPrivate || accessor.isStatic) { + continue; } - for (final method in classToMock.methods) { - if (method.isPrivate || method.isStatic) { - continue; - } - if (_returnTypeIsNonNullable(method) || - _hasNonNullableParameter(method)) { - cBuilder.methods.add(Method((mBuilder) => - _buildOverridingMethod(mBuilder, method, className: className))); - } + if (overriddenFields.contains(accessor)) { + continue; } - }); + if (accessor.isGetter != null && _returnTypeIsNonNullable(accessor)) { + yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); + } + if (accessor.isSetter != null && _hasNonNullableParameter(accessor)) { + yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); + } + } + if (type.mixins != null) { + for (var mixin in type.mixins) { + yield* fieldOverrides(mixin, overriddenFields); + } + } + if (type.superclass != null) { + yield* fieldOverrides(type.superclass, overriddenFields); + } + } + + /// Yields all of the method overrides required for [type]. + /// + /// This includes methods of supertypes and mixed in types. + /// [overriddenMethods] is used to track which methods have already been + /// yielded. + /// + /// Only public instance methods which have either a potentially non-nullable + /// return type or a parameter with a potentially non-nullable type are + /// yielded. + Iterable methodOverrides( + analyzer.InterfaceType type, Set overriddenMethods) sync* { + for (final method in type.methods) { + if (method.isPrivate || method.isStatic) { + continue; + } + if (overriddenMethods.contains(method)) { + continue; + } + var methodName = method.name; + overriddenMethods.add(methodName); + if (methodName == 'noSuchMethod') { + continue; + } + if (_returnTypeIsNonNullable(method) || + _hasNonNullableParameter(method)) { + yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method, + className: type.getDisplayString(withNullability: true))); + } + } + if (type.mixins != null) { + for (var mixin in type.mixins) { + yield* methodOverrides(mixin, overriddenMethods); + } + } + if (type.superclass != null) { + yield* methodOverrides(type.superclass, overriddenMethods); + } } /// The default behavior of mocks is to return null for unstubbed methods. To diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 140671653..cbf8b9be3 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -65,11 +65,6 @@ void main() {} ''' }; -const _constructorWithThrowOnMissingStub = ''' -MockFoo() { - _i1.throwOnMissingStub(this); - }'''; - void main() { InMemoryAssetWriter writer; @@ -104,6 +99,26 @@ void main() { ); } + /// Builds with [MockBuilder] in a package which has opted into the + /// non-nullable type system, returning the content of the generated mocks + /// library. + Future buildWithNonNullable(Map sourceAssets) async { + var packageConfig = PackageConfig([ + Package('foo', Uri.file('/foo/'), + packageUriRoot: Uri.file('/foo/lib/'), + languageVersion: LanguageVersion(2, 12)) + ]); + // TODO(srawlins): Remove enabled-experiments wrapper. + await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, packageConfig: packageConfig), + ['non-nullable'], + ); + var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + return utf8.decode(writer.assets[mocksAsset]); + } + /// Test [MockBuilder] on a single source file, in a package which has opted /// into the non-nullable type system, and with the non-nullable experiment /// enabled. @@ -120,8 +135,8 @@ void main() { } /// Builds with [MockBuilder] in a package which has opted into the - /// non-nullable type system, and with the non-nullable experiment enabled, - /// returning the content of the generated mocks library. + /// non-nullable type system, returning the content of the generated mocks + /// library. Future buildWithSingleNonNullableSource( String sourceAssetText) async { await testWithNonNullable({ @@ -142,22 +157,24 @@ void main() { () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - dynamic a() => 7; + dynamic method1() => 7; } ''')); expect(mocksContent, contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + expect(mocksContent, isNot(contains('method1'))); }); test('generates a mock class but does not override private methods', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - int _b(int x) => 8; + int _method1(int x) => 8; } ''')); expect(mocksContent, contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + expect(mocksContent, isNot(contains('method1'))); }); test('generates a mock class but does not override static methods', () async { @@ -522,33 +539,104 @@ void main() { ); }); + test('overrides methods of super classes', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class FooBase { + void m(int a) {} + } + class Foo extends FooBase {} + '''), + _containsAllOf('void m(int? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('overrides methods of generic super classes, substituting types', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + class FooBase { + void m(T a) {} + } + class Foo extends FooBase {} + '''), + _containsAllOf('void m(int? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('overrides methods of mixed in classes, substituting types', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Mixin { + void m(T a) {} + } + class Foo with Mixin {} + '''), + _containsAllOf('void m(int? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test( + 'overrides methods of indirect generic super classes, substituting types', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + class FooBase2 { + void m(T a) {} + } + class FooBase1 extends FooBase2 {} + class Foo extends FooBase2 {} + '''), + _containsAllOf('void m(int? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + + test('does not override methods of generic super classes using void', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class FooBase { + T method1() {} + } + class Foo extends FooBase {} + ''')); + expect(mocksContent, isNot(contains('method1'))); + }); + + test('overrides methods of generic super classes (type variable)', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class FooBase { + void m(T a) {} + } + class Foo extends FooBase {} + '''), + _containsAllOf( + 'void m(T? a) =>', 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + test('generates mock classes from part files', () async { - await testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; part 'part.dart'; ''', - 'foo|test/part.dart': ''' + 'foo|test/part.dart': ''' @GenerateMocks([Foo]) void fooTests() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - '''), - ), - }, - ); + }); + expect(mocksOutput, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); }); test('does not crash upon finding non-library files', () async { @@ -564,48 +652,33 @@ void main() { }); test('generates multiple mock classes', () async { - await testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} class Bar {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([Foo, Bar]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - '''), - dedent(''' - class MockBar extends _i1.Mock implements _i2.Bar { - MockBar() { - _i1.throwOnMissingStub(this); - } - } - '''), - ), - }, - ); + }); + expect(mocksOutput, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + expect(mocksOutput, + contains('class MockBar extends _i1.Mock implements _i2.Bar')); }); test('generates mock classes from multiple annotations', () async { - await testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} class Bar {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([Foo]) @@ -613,60 +686,33 @@ void main() { @GenerateMocks([Bar]) void barTests() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - '''), - dedent(''' - class MockBar extends _i1.Mock implements _i2.Bar { - MockBar() { - _i1.throwOnMissingStub(this); - } - } - '''), - ), - }, - ); + }); + expect(mocksOutput, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + expect(mocksOutput, + contains('class MockBar extends _i1.Mock implements _i2.Bar')); }); test('generates mock classes from multiple annotations on a single element', () async { - await testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} class Bar {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([Foo]) @GenerateMocks([Bar]) void barTests() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - '''), - dedent(''' - class MockBar extends _i1.Mock implements _i2.Bar { - MockBar() { - _i1.throwOnMissingStub(this); - } - } - '''), - ), - }, - ); + }); + expect(mocksOutput, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + expect(mocksOutput, + contains('class MockBar extends _i1.Mock implements _i2.Bar')); }); test('generates generic mock classes', () async { @@ -680,37 +726,25 @@ void main() { }); test('generates generic mock classes with type bounds', () async { - await testWithNonNullable( - { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' class Foo {} class Bar {} '''), - 'foo|test/foo_test.dart': ''' + 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([Foo, Bar]) void main() {} ''' - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - '''), - dedent(''' - class MockBar extends _i1.Mock implements _i2.Bar { - MockBar() { - _i1.throwOnMissingStub(this); - } - } - '''), - ), - }, - ); + }); + expect(mocksOutput, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + expect( + mocksOutput, + contains('class MockBar extends _i1.Mock ' + 'implements _i2.Bar')); }); test('writes dynamic, void w/o import prefix', () async { @@ -1051,6 +1085,16 @@ void main() { expect(mocksContent, isNot(contains('method1'))); }); + test('does not override methods with all nullable parameters (type variable)', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class Foo { + void method1(T? p) {} + } + ''')); + expect(mocksContent, isNot(contains('method1'))); + }); + test( 'does not override methods with all nullable parameters (function-typed)', () async { @@ -1100,6 +1144,16 @@ void main() { expect(mocksContent, isNot(contains('method1'))); }); + test('does not override methods with a nullable return type (type variable)', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + abstract class Foo { + T? method1(); + } + ''')); + expect(mocksContent, isNot(contains('method1'))); + }); + test('overrides methods with a non-nullable return type', () async { await expectSingleNonNullableOutput( dedent(r''' @@ -1119,49 +1173,26 @@ void main() { ...simpleTestAsset, 'foo|lib/foo.dart': dedent(r''' class Foo { - void a(T m) {} + void m(T a) {} } '''), }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'void a(T? m) => super.noSuchMethod(Invocation.method(#a, [m]));'), + 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), }, ); }); test('overrides generic methods', () async { - await testWithNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { dynamic f(int a) {} dynamic g(int a) {} } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': dedent(r''' - import 'package:mockito/mockito.dart' as _i1; - import 'package:foo/foo.dart' as _i2; - - /// A class which mocks [Foo]. - /// - /// See the documentation for Mockito's code generation for more information. - class MockFoo extends _i1.Mock implements _i2.Foo { - MockFoo() { - _i1.throwOnMissingStub(this); - } - - dynamic f(int? a) => super.noSuchMethod(Invocation.method(#f, [a])); - dynamic g(int? a) => - super.noSuchMethod(Invocation.method(#g, [a])); - } - '''), - }, - ); + ''')); + expect(mocksContent, contains('dynamic f(int? a) =>')); + expect(mocksContent, contains('dynamic g(int? a) =>')); }); test('overrides non-nullable instance getters', () async { @@ -1185,6 +1216,19 @@ void main() { expect(mocksContent, isNot(contains('getter1'))); }); + test('overrides inherited non-nullable instance getters', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class FooBase { + int get m => 7; + } + class Foo extends FooBase {} + '''), + _containsAllOf( + 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);'), + ); + }); + test('overrides non-nullable instance setters', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { @@ -1193,31 +1237,52 @@ void main() { ''')); expect( mocksContent, - contains( - 'set m(int? a) => super.noSuchMethod(Invocation.setter(#m, [a]));')); + contains('set m(int? a) => ' + 'super.noSuchMethod(Invocation.setter(#m, [a]));')); }); test('does not override nullable instance setters', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class Foo { + void set setter1(int? a) {} + } + ''')); + expect(mocksContent, isNot(contains('setter1'))); + }); + + test('overrides inherited non-nullable instance setters', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class FooBase { + void set m(int a) {} + } + class Foo extends FooBase {} + ''')); + expect( + mocksContent, + contains('set m(int? a) => ' + 'super.noSuchMethod(Invocation.setter(#m, [a]));')); + }); + + test('overrides non-nullable fields', () async { await expectSingleNonNullableOutput( dedent(r''' class Foo { - void set m(int? a) {} + int m; } '''), - _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')), + _containsAllOf( + 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);', + 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), ); }); - test('overrides non-nullable fields', () async { + test('overrides inherited non-nullable fields', () async { await expectSingleNonNullableOutput( dedent(r''' - class Foo { + class FooBase { int m; } + class Foo extends FooBase {} '''), _containsAllOf( 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);', From 86242b59e158d3b9bffa3c4baa9cf0acdf4d6a1c Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 23 Nov 2020 16:01:50 -0500 Subject: [PATCH 247/607] Bump dependencies to null safe pre-release versions PiperOrigin-RevId: 343912069 --- pkgs/mockito/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 33d142012..113768d97 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -11,9 +11,9 @@ dependencies: analyzer: '>=0.39.15 <0.41.0' build: ^1.3.0 code_builder: ^3.4.0 - collection: ^1.1.0 + collection: ^1.15.0-nullsafety.5 dart_style: ^1.3.6 - matcher: ^0.12.3 + matcher: ^0.12.10-nullsafety.3 meta: ^1.3.0-nullsafety source_gen: ^0.9.6 test_api: ^0.2.19-nullsafety @@ -25,4 +25,4 @@ dev_dependencies: http: ^0.12.0 package_config: ^1.9.3 pedantic: ^1.10.0-nullsafety - test: ^1.5.1 + test: ^1.16.0-nullsafety.12 From 9d2975b7cb04d45374d2deabc4c3616a841c66a4 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 24 Nov 2020 12:07:33 -0500 Subject: [PATCH 248/607] Remove `withExperiments` from codegen tests. This was required before Dart SDK versions 2.12.0-0. Since Mockito now requires 2.12.0-0 or later, the non-nullable experiment does not need to be explicitly enabled. PiperOrigin-RevId: 344070734 --- .../mockito/test/builder/auto_mocks_test.dart | 49 ++++++------------- .../test/builder/custom_mocks_test.dart | 17 ++----- 2 files changed, 21 insertions(+), 45 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index cbf8b9be3..0cb26a516 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -18,7 +18,6 @@ import 'dart:convert' show utf8; import 'package:build/build.dart'; -import 'package:build/experiments.dart'; import 'package:build_test/build_test.dart'; import 'package:meta/meta.dart'; import 'package:mockito/src/builder.dart'; @@ -68,8 +67,7 @@ void main() {} void main() { InMemoryAssetWriter writer; - /// Test [MockBuilder] in a package which has not opted into the non-nullable - /// type system. + /// Test [MockBuilder] in a package which has not opted into null safety. Future testPreNonNullable(Map sourceAssets, {Map*/ dynamic> outputs}) async { var packageConfig = PackageConfig([ @@ -81,8 +79,7 @@ void main() { writer: writer, outputs: outputs, packageConfig: packageConfig); } - /// Test [MockBuilder] in a package which has opted into the non-nullable type - /// system. + /// Test [MockBuilder] in a package which has opted into null safety. Future testWithNonNullable(Map sourceAssets, {Map>*/ dynamic> outputs}) async { var packageConfig = PackageConfig([ @@ -90,38 +87,27 @@ void main() { packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 12)) ]); - // TODO(srawlins): Remove enabled-experiments wrapper. - await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), sourceAssets, - writer: writer, outputs: outputs, packageConfig: packageConfig), - ['non-nullable'], - ); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, outputs: outputs, packageConfig: packageConfig); } - /// Builds with [MockBuilder] in a package which has opted into the - /// non-nullable type system, returning the content of the generated mocks - /// library. + /// Builds with [MockBuilder] in a package which has opted into null safety, + /// returning the content of the generated mocks library. Future buildWithNonNullable(Map sourceAssets) async { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 12)) ]); - // TODO(srawlins): Remove enabled-experiments wrapper. - await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), sourceAssets, - writer: writer, packageConfig: packageConfig), - ['non-nullable'], - ); + + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, packageConfig: packageConfig); var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]); } /// Test [MockBuilder] on a single source file, in a package which has opted - /// into the non-nullable type system, and with the non-nullable experiment - /// enabled. + /// into null safety, and with the non-nullable experiment enabled. Future expectSingleNonNullableOutput( String sourceAssetText, /*String|Matcher>*/ dynamic output) async { @@ -2101,24 +2087,21 @@ void main() { TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); -/// Expect that [testBuilder], given [assets], throws an -/// [InvalidMockitoAnnotationException] with a message containing [message]. +/// Expect that [testBuilder], given [assets], in a package which has opted into +/// null safety, throws an [InvalidMockitoAnnotationException] with a message +/// containing [message]. void _expectBuilderThrows( {@required Map assets, @required dynamic /*String|Matcher>*/ message}) { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) + languageVersion: LanguageVersion(2, 12)) ]); expect( - () async => await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), assets, - packageConfig: packageConfig), - ['non-nullable'], - ), + () async => await testBuilder(buildMocks(BuilderOptions({})), assets, + packageConfig: packageConfig), throwsA(TypeMatcher() .having((e) => e.message, 'message', message))); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 8eba278c7..1b1c670e7 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -18,7 +18,6 @@ import 'dart:convert' show utf8; import 'package:build/build.dart'; -import 'package:build/experiments.dart'; import 'package:build_test/build_test.dart'; import 'package:meta/meta.dart'; import 'package:mockito/src/builder.dart'; @@ -73,8 +72,7 @@ MockFoo() { void main() { InMemoryAssetWriter writer; - /// Test [MockBuilder] in a package which has not opted into the non-nullable - /// type system. + /// Test [MockBuilder] in a package which has not opted into null safety. Future testPreNonNullable(Map sourceAssets, {Map*/ dynamic> outputs}) async { var packageConfig = PackageConfig([ @@ -86,21 +84,16 @@ void main() { outputs: outputs, packageConfig: packageConfig); } - /// Builds with [MockBuilder] in a package which has opted into the - /// non-nullable type system, and with the non-nullable experiment enabled, + /// Builds with [MockBuilder] in a package which has opted into null safety, /// returning the content of the generated mocks library. Future buildWithNonNullable(Map sourceAssets) async { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 10)) + languageVersion: LanguageVersion(2, 12)) ]); - await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), sourceAssets, - writer: writer, packageConfig: packageConfig), - ['non-nullable'], - ); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, packageConfig: packageConfig); var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]); } From 061fdd6cbad455ec72408862492681213ceb025c Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 24 Nov 2020 12:09:07 -0500 Subject: [PATCH 249/607] Bump mockito to 5.0.0-nullsafety.0 PiperOrigin-RevId: 344070991 --- pkgs/mockito/CHANGELOG.md | 4 +++- pkgs/mockito/pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index dd1ced662..e50698ced 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,5 +1,7 @@ -## 5.0.0-dev +## 5.0.0-nullsafety.0 +* Migrate the core libraries and tests to null safety. The builder at + `lib/src/builder.dart` opts out of null safety. * Add `http` back to `dev_dependencies`. It's used by the example. ## 4.1.3 diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 113768d97..8da65c07f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,11 +1,11 @@ name: mockito -version: 5.0.0-nullsafety.0-dev +version: 5.0.0-nullsafety.0 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dependencies: analyzer: '>=0.39.15 <0.41.0' From 862fffb333fcd3b03d65e8d8dfe6808213c98abc Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 24 Nov 2020 13:54:31 -0500 Subject: [PATCH 250/607] Fix totally broken null safe example code. This should be functionally a no-op. Most example method parameters are made nullable (so as to be compatible with the existing mockito API), as well as method return types. PiperOrigin-RevId: 344091524 --- pkgs/mockito/example/example.dart | 12 ++++++------ pkgs/mockito/example/iss/iss.dart | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 3a708b561..9e63a1173 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -5,12 +5,12 @@ import 'package:test/test.dart'; // Real class class Cat { - String sound() => 'Meow'; - bool eatFood(String food, {bool hungry}) => true; + String? sound() => 'Meow'; + bool? eatFood(String? food, {bool? hungry}) => true; Future chew() async => print('Chewing...'); - int walk(List places) => 7; + int? walk(List? places) => 7; void sleep() {} - void hunt(String place, String prey) {} + void hunt(String? place, String? prey) {} int lives = 9; } @@ -20,14 +20,14 @@ class MockCat extends Mock implements Cat {} // Fake class class FakeCat extends Fake implements Cat { @override - bool eatFood(String food, {bool hungry}) { + bool? eatFood(String? food, {bool? hungry}) { print('Fake eat $food'); return true; } } void main() { - Cat cat; + late Cat cat; setUp(() { // Create mock object. diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index 86e95d516..be8878a54 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -22,21 +22,21 @@ import 'package:http/http.dart'; class IssLocator { final Client client; - /*late*/ Point _position; - Future _ongoingRequest; + late Point _position; + Future? _ongoingRequest; IssLocator(this.client); Point get currentPosition => _position; /// Returns the current GPS position in [latitude, longitude] format. - Future update() async { + Future update() async { _ongoingRequest ??= _doUpdate(); await _ongoingRequest; _ongoingRequest = null; } - Future _doUpdate() async { + Future _doUpdate() async { // Returns the point on the earth directly under the space station // at this moment. var rs = await client.get('http://api.open-notify.org/iss-now.json'); @@ -51,9 +51,8 @@ class IssLocator { class IssSpotter { final IssLocator locator; final Point observer; - final String label; - IssSpotter(this.locator, this.observer, {this.label}); + IssSpotter(this.locator, this.observer); // The ISS is defined to be visible if the distance from the observer to // the point on the earth directly under the space station is less than 80km. From 16bfd07244a0e3c7a6177bc3950adebf2090eedf Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 2 Dec 2020 11:45:21 -0500 Subject: [PATCH 251/607] Fix a few issues for upcoming roll to GitHub and publish: * Opt `bin/codegen.dart` and `test/all.dart` out of null safety since they import opted out (legacy) libraries. This avoids import_of_legacy_library_into_null_safe errors. * Ignore deprecated Fake from the test package; mockito intends to show this copy until bumping its test dependency. * Throw an Object in `thenThrow` to avoid throw_of_invalid_type error. PiperOrigin-RevId: 345240533 --- pkgs/mockito/bin/codegen.dart | 2 ++ pkgs/mockito/lib/mockito.dart | 1 + pkgs/mockito/lib/src/mock.dart | 3 ++- pkgs/mockito/test/all.dart | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/bin/codegen.dart b/pkgs/mockito/bin/codegen.dart index e216c351e..998362493 100644 --- a/pkgs/mockito/bin/codegen.dart +++ b/pkgs/mockito/bin/codegen.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + import 'package:build/build.dart'; import 'package:mockito/src/builder.dart' as b; diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 7399084f3..dad8d7e29 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore: deprecated_member_use export 'package:test_api/fake.dart' show Fake; export 'src/mock.dart' diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 247f44f88..22ac67ee0 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -17,6 +17,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; +// ignore: deprecated_member_use import 'package:test_api/fake.dart'; // ignore: deprecated_member_use import 'package:test_api/test_api.dart'; @@ -380,7 +381,7 @@ class PostExpectation { } /// Store an exception to throw when this method stub is called. - void thenThrow(throwable) { + void thenThrow(Object throwable) { return _completeWhen((Invocation _) { throw throwable; }); diff --git a/pkgs/mockito/test/all.dart b/pkgs/mockito/test/all.dart index 5fcc7b4cf..9af848fad 100644 --- a/pkgs/mockito/test/all.dart +++ b/pkgs/mockito/test/all.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + // This file explicitly does _not_ end in `_test.dart`, so that it is not picked // up by `pub run test`. It is here for coveralls. From f22b766f1e91eb4f790795f6643b95fba3369016 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 4 Dec 2020 19:31:37 -0500 Subject: [PATCH 252/607] Remove assessNsmForwarding testing util. This utility (which assesed whether the Dart runtime performed "noSuchMethod forwarding") has not been needed since the stable release of Dart 2.0. See the CHANGELOG entry for the commit that introduced it: https://github.com/dart-lang/mockito/commit/de7e20efc4a0932a5ab61e53b9da4d45b94481bc#diff-e3add028a0a83d042c0fbf2778df8b172734500140479f80f48354c8373894f9 Nothing needed in CHANGELOG. PiperOrigin-RevId: 345780596 --- pkgs/mockito/test/capture_test.dart | 7 +------ .../test/deprecated_apis/capture_test.dart | 7 +------ pkgs/mockito/test/mockito_test.dart | 8 ------- pkgs/mockito/test/utils.dart | 21 ------------------- pkgs/mockito/test/verify_test.dart | 14 +++---------- 5 files changed, 5 insertions(+), 52 deletions(-) delete mode 100644 pkgs/mockito/test/utils.dart diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index c4423b2de..84dd073e3 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -15,8 +15,6 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; -import 'utils.dart'; - class _RealClass { String methodWithNormalArgs(int? x) => 'Real'; String methodWithListArgs(List? x) => 'Real'; @@ -32,8 +30,6 @@ class _MockedClass extends Mock implements _RealClass {} void main() { late _MockedClass mock; - var isNsmForwarding = assessNsmForwarding(); - setUp(() { mock = _MockedClass(); }); @@ -83,11 +79,10 @@ void main() { test('should capture with matching arguments', () { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); - var expectedCaptures = isNsmForwarding ? [1, null, 2, 3] : [2, 3]; expect( verify(mock.methodWithPositionalArgs(captureAny, captureAny)) .captured, - equals(expectedCaptures)); + equals([1, null, 2, 3])); }); test('should capture multiple invocations', () { diff --git a/pkgs/mockito/test/deprecated_apis/capture_test.dart b/pkgs/mockito/test/deprecated_apis/capture_test.dart index dfd357609..0f76ca0cb 100644 --- a/pkgs/mockito/test/deprecated_apis/capture_test.dart +++ b/pkgs/mockito/test/deprecated_apis/capture_test.dart @@ -20,8 +20,6 @@ library mockito.test.deprecated_apis.capture_test; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; -import '../utils.dart'; - class _RealClass { String methodWithNormalArgs(int? x) => 'Real'; String methodWithListArgs(List? x) => 'Real'; @@ -36,8 +34,6 @@ class MockedClass extends Mock implements _RealClass {} void main() { late MockedClass mock; - var isNsmForwarding = assessNsmForwarding(); - setUp(() { mock = MockedClass(); }); @@ -85,12 +81,11 @@ void main() { test('should capture with matching arguments', () { mock.methodWithPositionalArgs(1); mock.methodWithPositionalArgs(2, 3); - var expectedCaptures = isNsmForwarding ? [1, null, 2, 3] : [2, 3]; expect( verify(mock.methodWithPositionalArgs( typed(captureAny), typed(captureAny))) .captured, - equals(expectedCaptures)); + equals([1, null, 2, 3])); }); test('should capture multiple invocations', () { diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 4428a2fcf..f63ff9542 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -15,8 +15,6 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; -import 'utils.dart'; - class _RealClass { _RealClass? innerObj; String methodWithoutArgs() => 'Real'; @@ -66,8 +64,6 @@ void expectFail(String expectedMessage, void Function() expectedToFail) { void main() { late _MockedClass mock; - var isNsmForwarding = assessNsmForwarding(); - setUp(() { mock = _MockedClass(); }); @@ -149,11 +145,7 @@ void main() { .thenReturn('x y'); when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) .thenReturn('x z'); - if (isNsmForwarding) { expect(mock.methodWithTwoNamedArgs(42), 'x z'); - } else { - expect(mock.methodWithTwoNamedArgs(42), isNull); - } expect(mock.methodWithTwoNamedArgs(42, y: 18), equals('x y')); expect(mock.methodWithTwoNamedArgs(42, z: 17), equals('x z')); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); diff --git a/pkgs/mockito/test/utils.dart b/pkgs/mockito/test/utils.dart deleted file mode 100644 index 9d71f6fc5..000000000 --- a/pkgs/mockito/test/utils.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:mockito/mockito.dart'; - -abstract class NsmForwardingSignal { - void fn([int? a]); -} - -class MockNsmForwardingSignal extends Mock implements NsmForwardingSignal {} - -bool assessNsmForwarding() { - var signal = MockNsmForwardingSignal(); - signal.fn(); - try { - verify(signal.fn(any)); - return true; - } catch (_) { - // The verify failed, because the default value of 7 was not passed to - // noSuchMethod. - verify(signal.fn()); - return false; - } -} diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 10e7852c7..212c9a471 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -15,8 +15,6 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; -import 'utils.dart'; - class _RealClass { String methodWithoutArgs() => 'Real'; String methodWithNormalArgs(int? x) => 'Real'; @@ -70,8 +68,6 @@ const noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' void main() { late _MockedClass mock; - var isNsmForwarding = assessNsmForwarding(); - setUp(() { mock = _MockedClass(); }); @@ -230,9 +226,8 @@ void main() { test('and there is one unmatched call without args', () { mock.methodWithOptionalArg(); - var nsmForwardedArgs = isNsmForwarding ? 'null' : ''; expectFail( - 'No matching calls. All calls: _MockedClass.methodWithOptionalArg($nsmForwardedArgs)\n' + 'No matching calls. All calls: _MockedClass.methodWithOptionalArg(null)\n' '$noMatchingCallsFooter', () { verify(mock.methodWithOptionalArg(43)); }); @@ -252,10 +247,9 @@ void main() { test('and unmatched calls have only named args', () { mock.methodWithOnlyNamedArgs(y: 1); - var nsmForwardedArgs = isNsmForwarding ? '{y: 1, z: null}' : '{y: 1}'; expectFail( 'No matching calls. All calls: ' - '_MockedClass.methodWithOnlyNamedArgs($nsmForwardedArgs)\n' + '_MockedClass.methodWithOnlyNamedArgs({y: 1, z: null})\n' '$noMatchingCallsFooter', () { verify(mock.methodWithOnlyNamedArgs()); }); @@ -501,8 +495,6 @@ void main() { mock.methodWithLongArgs(null, null, c: LongToString([5, 6], {5: 'g', 6: 'h'}, 'i'), d: LongToString([7, 8], {7: 'j', 8: 'k'}, 'l')); - var nsmForwardedNamedArgs = - isNsmForwarding ? '>, {c: null, d: null}),' : '>),'; expectFail( 'No matching calls. All calls: ' '_MockedClass.methodWithLongArgs(\n' @@ -515,7 +507,7 @@ void main() { ' aList: [4, 5]\n' ' aMap: {3: d, 4: e}\n' ' aString: f\n' - ' $nsmForwardedNamedArgs\n' + ' >, {c: null, d: null}),\n' '_MockedClass.methodWithLongArgs(null, null, {\n' ' c: LongToString<\n' ' aList: [5, 6]\n' From 6a23eafa269e6a06e6249ebd907214172e828843 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 7 Dec 2020 16:32:52 -0500 Subject: [PATCH 253/607] Remove deprecated APIs `typed`, `typedArgThat`, and `typedCaptureThat`. These have been deprecated for over 3 years. It is time to remove them in this major release. PiperOrigin-RevId: 346168211 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/mockito.dart | 5 - pkgs/mockito/lib/src/mock.dart | 11 - .../test/deprecated_apis/capture_test.dart | 98 --------- .../test/deprecated_apis/mockito_test.dart | 151 ------------- .../deprecated_apis/until_called_test.dart | 200 ------------------ .../test/deprecated_apis/verify_test.dart | 144 ------------- 7 files changed, 1 insertion(+), 609 deletions(-) delete mode 100644 pkgs/mockito/test/deprecated_apis/capture_test.dart delete mode 100644 pkgs/mockito/test/deprecated_apis/mockito_test.dart delete mode 100644 pkgs/mockito/test/deprecated_apis/until_called_test.dart delete mode 100644 pkgs/mockito/test/deprecated_apis/verify_test.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index e50698ced..3b80be952 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,7 @@ * Migrate the core libraries and tests to null safety. The builder at `lib/src/builder.dart` opts out of null safety. * Add `http` back to `dev_dependencies`. It's used by the example. +* Remove deprecated `typed`, `typedArgThat`, and `typedCaptureThat` APIs. ## 4.1.3 diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index dad8d7e29..7aa8ecd97 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -41,11 +41,6 @@ export 'src/mock.dart' VerificationResult, Verification, - // -- deprecated - typed, // ignore: deprecated_member_use_from_same_package - typedArgThat, // ignore: deprecated_member_use_from_same_package - typedCaptureThat, // ignore: deprecated_member_use_from_same_package - // -- misc throwOnMissingStub, clearInteractions, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 22ac67ee0..00b0cfd3d 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -692,17 +692,6 @@ Null captureThat(Matcher matcher, {String? named}) => _registerMatcher(matcher, true, named: named, argumentMatcher: 'captureThat'); -@Deprecated('ArgMatchers no longer need to be wrapped in Mockito 3.0') -Null typed(ArgMatcher? matcher, {String? named}) => null; - -@Deprecated('Replace with `argThat`') -Null typedArgThat(Matcher matcher, {String? named}) => - argThat(matcher, named: named); - -@Deprecated('Replace with `captureThat`') -Null typedCaptureThat(Matcher matcher, {String? named}) => - captureThat(matcher, named: named); - /// Registers [matcher] into the stored arguments collections. /// /// Creates an [ArgMatcher] with [matcher] and [capture], then if [named] is diff --git a/pkgs/mockito/test/deprecated_apis/capture_test.dart b/pkgs/mockito/test/deprecated_apis/capture_test.dart deleted file mode 100644 index 0f76ca0cb..000000000 --- a/pkgs/mockito/test/deprecated_apis/capture_test.dart +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// ignore_for_file: strong_mode_implicit_dynamic_function - -@deprecated -library mockito.test.deprecated_apis.capture_test; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class _RealClass { - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List? x) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - set setter(String arg) { - throw StateError('I must be mocked'); - } -} - -class MockedClass extends Mock implements _RealClass {} - -void main() { - late MockedClass mock; - - setUp(() { - mock = MockedClass(); - }); - - tearDown(() { - // In some of the tests that expect an Error to be thrown, Mockito's - // global state can become invalid. Reset it. - resetMockitoState(); - }); - - group('capture', () { - test('captureAny should match anything', () { - mock.methodWithNormalArgs(42); - expect( - verify(mock.methodWithNormalArgs(typed(captureAny))).captured.single, - equals(42)); - }); - - test('captureThat should match some things', () { - mock.methodWithNormalArgs(42); - mock.methodWithNormalArgs(44); - mock.methodWithNormalArgs(43); - mock.methodWithNormalArgs(45); - expect( - verify(mock.methodWithNormalArgs(typed(captureThat(lessThan(44))))) - .captured, - equals([42, 43])); - }); - - test('should capture list arguments', () { - mock.methodWithListArgs([42]); - expect(verify(mock.methodWithListArgs(typed(captureAny))).captured.single, - equals([42])); - }); - - test('should capture multiple arguments', () { - mock.methodWithPositionalArgs(1, 2); - expect( - verify(mock.methodWithPositionalArgs( - typed(captureAny), typed(captureAny))) - .captured, - equals([1, 2])); - }); - - test('should capture with matching arguments', () { - mock.methodWithPositionalArgs(1); - mock.methodWithPositionalArgs(2, 3); - expect( - verify(mock.methodWithPositionalArgs( - typed(captureAny), typed(captureAny))) - .captured, - equals([1, null, 2, 3])); - }); - - test('should capture multiple invocations', () { - mock.methodWithNormalArgs(1); - mock.methodWithNormalArgs(2); - expect(verify(mock.methodWithNormalArgs(typed(captureAny))).captured, - equals([1, 2])); - }); - }); -} diff --git a/pkgs/mockito/test/deprecated_apis/mockito_test.dart b/pkgs/mockito/test/deprecated_apis/mockito_test.dart deleted file mode 100644 index 7941e5a67..000000000 --- a/pkgs/mockito/test/deprecated_apis/mockito_test.dart +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2016 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// ignore_for_file: strong_mode_implicit_dynamic_function - -@deprecated -library mockito.test.deprecated_apis.mockito_test; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class _RealClass { - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List? x) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - String methodWithNamedArgs(int x, {int? y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int? y, int? z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - Future methodReturningFuture() => Future.value('Real'); - Stream methodReturningStream() => Stream.fromIterable(['Real']); - String get getter => 'Real'; - set setter(String arg) { - throw StateError('I must be mocked'); - } -} - -abstract class Foo { - String bar(); -} - -abstract class AbstractFoo implements Foo { - @override - String bar() => baz(); - - String baz(); -} - -class MockFoo extends AbstractFoo with Mock {} - -class _MockedClass extends Mock implements _RealClass {} - -void expectFail(String expectedMessage, void Function() expectedToFail) { - try { - expectedToFail(); - fail('It was expected to fail!'); - } catch (e) { - if (!(e is TestFailure)) { - rethrow; - } else { - if (expectedMessage != e.message) { - throw TestFailure('Failed, but with wrong message: ${e.message}'); - } - } - } -} - -String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' - 'please instead use `verifyNever(...);`.)'; - -void main() { - late _MockedClass mock; - - setUp(() { - mock = _MockedClass(); - }); - - tearDown(() { - // In some of the tests that expect an Error to be thrown, Mockito's - // global state can become invalid. Reset it. - resetMockitoState(); - }); - - group('when()', () { - test('should mock method with argument matcher', () { - when(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))) - .thenReturn('A lot!'); - expect(mock.methodWithNormalArgs(100), isNull); - expect(mock.methodWithNormalArgs(101), equals('A lot!')); - }); - - test('should mock method with any argument matcher', () { - when(mock.methodWithNormalArgs(typed(any))).thenReturn('A lot!'); - expect(mock.methodWithNormalArgs(100), equals('A lot!')); - expect(mock.methodWithNormalArgs(101), equals('A lot!')); - }); - - test('should mock method with any list argument matcher', () { - when(mock.methodWithListArgs(typed(any))).thenReturn('A lot!'); - expect(mock.methodWithListArgs([42]), equals('A lot!')); - expect(mock.methodWithListArgs([43]), equals('A lot!')); - }); - - test('should mock method with mix of argument matchers and real things', - () { - when(mock.methodWithPositionalArgs(typed(argThat(greaterThan(100))), 17)) - .thenReturn('A lot with 17'); - expect(mock.methodWithPositionalArgs(100, 17), isNull); - expect(mock.methodWithPositionalArgs(101, 18), isNull); - expect(mock.methodWithPositionalArgs(101, 17), equals('A lot with 17')); - }); - - //no need to mock setter, except if we will have spies later... - test('should mock method with thrown result', () { - when(mock.methodWithNormalArgs(typed(any))).thenThrow(StateError('Boo')); - expect(() => mock.methodWithNormalArgs(42), throwsStateError); - }); - - test('should mock method with calculated result', () { - when(mock.methodWithNormalArgs(typed(any))).thenAnswer( - (Invocation inv) => inv.positionalArguments[0].toString()); - expect(mock.methodWithNormalArgs(43), equals('43')); - expect(mock.methodWithNormalArgs(42), equals('42')); - }); - - test('should mock method with calculated result', () { - when(mock.methodWithNormalArgs(typed(argThat(equals(43))))) - .thenReturn('43'); - when(mock.methodWithNormalArgs(typed(argThat(equals(42))))) - .thenReturn('42'); - expect(mock.methodWithNormalArgs(43), equals('43')); - }); - - test('should mock hashCode', () { - named(mock, hashCode: 42); - expect(mock.hashCode, equals(42)); - }); - - test('should have toString as name when it is not mocked', () { - named(mock, name: 'Cat'); - expect(mock.toString(), equals('Cat')); - }); - - test('should mock equals between mocks when givenHashCode is equals', () { - var anotherMock = named(_MockedClass(), hashCode: 42); - named(mock, hashCode: 42); - expect(mock == anotherMock, isTrue); - }); - }); -} diff --git a/pkgs/mockito/test/deprecated_apis/until_called_test.dart b/pkgs/mockito/test/deprecated_apis/until_called_test.dart deleted file mode 100644 index a21b995e2..000000000 --- a/pkgs/mockito/test/deprecated_apis/until_called_test.dart +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2016 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// ignore_for_file: strong_mode_implicit_dynamic_function - -@deprecated -library mockito.test.deprecated_apis.until_called_test; - -import 'dart:async'; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class _RealClass { - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List? x) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - String methodWithNamedArgs(int? x, {int? y}) => 'Real'; - String methodWithTwoNamedArgs(int x, {int? y, int? z}) => 'Real'; - String methodWithObjArgs(_RealClass? x) => 'Real'; - String typeParameterizedFn(List? w, List? x, - [List? y, List? z]) => - 'Real'; - String typeParameterizedNamedFn(List w, List x, - {List? y, List? z}) => - 'Real'; - String get getter => 'Real'; - set setter(String arg) { - throw StateError('I must be mocked'); - } -} - -class CallMethodsEvent {} - -/// Listens on a stream and upon any event calls all methods in [_RealClass]. -class _RealClassController { - final _RealClass _realClass; - - _RealClassController( - this._realClass, StreamController streamController) { - streamController.stream.listen(_callAllMethods); - } - - Future _callAllMethods(_) async { - _realClass - ..methodWithoutArgs() - ..methodWithNormalArgs(1) - ..methodWithListArgs([1, 2]) - ..methodWithPositionalArgs(1, 2) - ..methodWithNamedArgs(1, y: 2) - ..methodWithTwoNamedArgs(1, y: 2, z: 3) - ..methodWithObjArgs(_RealClass()) - ..typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]) - ..typeParameterizedNamedFn([1, 2], [3, 4], y: [5, 6], z: [7, 8]) - ..getter - ..setter = 'A'; - } -} - -class MockedClass extends Mock implements _RealClass {} - -void main() { - late MockedClass mock; - - setUp(() { - mock = MockedClass(); - }); - - tearDown(() { - // In some of the tests that expect an Error to be thrown, Mockito's - // global state can become invalid. Reset it. - resetMockitoState(); - }); - - group('untilCalled', () { - var streamController = StreamController.broadcast(); - - group('on methods already called', () { - test('waits for method with normal args', () async { - mock.methodWithNormalArgs(1); - - await untilCalled(mock.methodWithNormalArgs(typed(any))); - - verify(mock.methodWithNormalArgs(typed(any))).called(1); - }); - - test('waits for method with list args', () async { - mock.methodWithListArgs([1]); - - await untilCalled(mock.methodWithListArgs(typed(any))); - - verify(mock.methodWithListArgs(typed(any))).called(1); - }); - - test('waits for method with positional args', () async { - mock.methodWithPositionalArgs(1, 2); - - await untilCalled( - mock.methodWithPositionalArgs(typed(any), typed(any))); - - verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); - }); - - test('waits for method with named args', () async { - mock.methodWithNamedArgs(1, y: 2); - - await untilCalled(mock.methodWithNamedArgs(any, y: anyNamed('y'))); - - verify(mock.methodWithNamedArgs(any, y: anyNamed('y'))).called(1); - }); - - test('waits for method with obj args', () async { - mock.methodWithObjArgs(_RealClass()); - - await untilCalled(mock.methodWithObjArgs(typed(any))); - - verify(mock.methodWithObjArgs(typed(any))).called(1); - }); - - test('waits for function with positional parameters', () async { - mock.typeParameterizedFn([1, 2], [3, 4], [5, 6], [7, 8]); - - await untilCalled(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); - - verify(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))) - .called(1); - }); - }); - - group('on methods not yet called', () { - setUp(() { - _RealClassController(mock, streamController); - }); - - test('waits for method with normal args', () async { - streamController.add(CallMethodsEvent()); - verifyNever(mock.methodWithNormalArgs(typed(any))); - - await untilCalled(mock.methodWithNormalArgs(typed(any))); - - verify(mock.methodWithNormalArgs(typed(any))).called(1); - }); - - test('waits for method with list args', () async { - streamController.add(CallMethodsEvent()); - verifyNever(mock.methodWithListArgs(typed(any))); - - await untilCalled(mock.methodWithListArgs(typed(any))); - - verify(mock.methodWithListArgs(typed(any))).called(1); - }); - - test('waits for method with positional args', () async { - streamController.add(CallMethodsEvent()); - verifyNever(mock.methodWithPositionalArgs(typed(any), typed(any))); - - await untilCalled( - mock.methodWithPositionalArgs(typed(any), typed(any))); - - verify(mock.methodWithPositionalArgs(typed(any), typed(any))).called(1); - }); - - test('waits for method with obj args', () async { - streamController.add(CallMethodsEvent()); - verifyNever(mock.methodWithObjArgs(typed(any))); - - await untilCalled(mock.methodWithObjArgs(typed(any))); - - verify(mock.methodWithObjArgs(typed(any))).called(1); - }); - - test('waits for function with positional parameters', () async { - streamController.add(CallMethodsEvent()); - verifyNever(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); - - await untilCalled(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))); - - verify(mock.typeParameterizedFn( - typed(any), typed(any), typed(any), typed(any))) - .called(1); - }); - }); - }); -} diff --git a/pkgs/mockito/test/deprecated_apis/verify_test.dart b/pkgs/mockito/test/deprecated_apis/verify_test.dart deleted file mode 100644 index 4ae125b52..000000000 --- a/pkgs/mockito/test/deprecated_apis/verify_test.dart +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2018 Dart Mockito authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// ignore_for_file: strong_mode_implicit_dynamic_function - -@deprecated -library mockito.test.deprecated_apis.verify_test; - -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class _RealClass { - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithOptionalArg([int? x]) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - String methodWithNamedArgs(int x, {int? y}) => 'Real'; - String methodWithOnlyNamedArgs({int? y, int? z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - String get getter => 'Real'; - set setter(String arg) { - throw StateError('I must be mocked'); - } - - String methodWithLongArgs(LongToString a, LongToString b, - {LongToString? c, LongToString? d}) => - 'Real'; -} - -class LongToString { - final List aList; - final Map aMap; - final String aString; - - LongToString(this.aList, this.aMap, this.aString); - - @override - String toString() => 'LongToString<\n' - ' aList: $aList\n' - ' aMap: $aMap\n' - ' aString: $aString\n' - '>'; -} - -class _MockedClass extends Mock implements _RealClass {} - -void expectFail(String expectedMessage, void Function() expectedToFail) { - try { - expectedToFail(); - fail('It was expected to fail!'); - } catch (e) { - if (!(e is TestFailure)) { - rethrow; - } else { - if (expectedMessage != e.message) { - throw TestFailure('Failed, but with wrong message: ${e.message}'); - } - } - } -} - -String noMatchingCallsFooter = '(If you called `verify(...).called(0);`, ' - 'please instead use `verifyNever(...);`.)'; - -void main() { - late _MockedClass mock; - - // google3-specific: dart2js writes minified method names differently. - var isDart2js = true; - // asserts are not run in dart2js. - assert(() { - isDart2js = false; - return true; - }()); - // END google3-specific. - - setUp(() { - mock = _MockedClass(); - }); - - tearDown(() { - // In some of the tests that expect an Error to be thrown, Mockito's - // global state can become invalid. Reset it. - resetMockitoState(); - }); - - group('verify', () { - test('should mock method with argument matcher', () { - mock.methodWithNormalArgs(100); - expectFail( - 'No matching calls. All calls: ' - '_MockedClass.methodWithNormalArgs(100)\n' - '$noMatchingCallsFooter', () { - verify(mock.methodWithNormalArgs(typed(argThat(greaterThan(100))))); - }); - verify( - mock.methodWithNormalArgs(typed(argThat(greaterThanOrEqualTo(100))))); - }); - - test('should mock method with mix of argument matchers and real things', - () { - mock.methodWithPositionalArgs(100, 17); - expectFail( - 'No matching calls. All calls: ' - '_MockedClass.methodWithPositionalArgs(100, 17)\n' - '$noMatchingCallsFooter', () { - verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThanOrEqualTo(100))), 18)); - }); - expectFail( - 'No matching calls. All calls: ' - '_MockedClass.methodWithPositionalArgs(100, 17)\n' - '$noMatchingCallsFooter', () { - verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThan(100))), 17)); - }); - verify(mock.methodWithPositionalArgs( - typed(argThat(greaterThanOrEqualTo(100))), 17)); - }); - - test('should mock method with mock args', () { - var m1 = named(_MockedClass(), name: 'm1'); - mock.methodWithObjArgs(m1); - expectFail( - 'No matching calls. All calls: _MockedClass.methodWithObjArgs(m1)\n' - '$noMatchingCallsFooter', () { - verify(mock.methodWithObjArgs(_MockedClass())); - }); - verify(mock.methodWithObjArgs(m1)); - }, skip: isDart2js); // google3-specific skip. - }); -} From 66895474c01ea61355332191ec21ebff70273453 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 9 Dec 2020 17:55:45 -0500 Subject: [PATCH 254/607] Fix null safe runtime errors in tests. PiperOrigin-RevId: 346643960 --- pkgs/mockito/test/capture_test.dart | 8 ++--- .../mockito/test/invocation_matcher_test.dart | 2 +- pkgs/mockito/test/mockito_test.dart | 30 +++++++++---------- pkgs/mockito/test/until_called_test.dart | 20 ++++++------- pkgs/mockito/test/verify_test.dart | 22 +++++++------- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/pkgs/mockito/test/capture_test.dart b/pkgs/mockito/test/capture_test.dart index 84dd073e3..5545e72a0 100644 --- a/pkgs/mockito/test/capture_test.dart +++ b/pkgs/mockito/test/capture_test.dart @@ -16,10 +16,10 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class _RealClass { - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List? x) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - String methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; + String? methodWithNormalArgs(int? x) => 'Real'; + String? methodWithListArgs(List? x) => 'Real'; + String? methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String? methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; set setter(String? arg) { throw StateError('I must be mocked'); } diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index b3e39af1b..b2b440fce 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -139,7 +139,7 @@ void main() { } abstract class Interface { - bool get value; + bool? get value; set value(value); void say(String text); void eat(String food, {bool? alsoDrink}); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index f63ff9542..6ad2d7053 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -17,27 +17,27 @@ import 'package:test/test.dart'; class _RealClass { _RealClass? innerObj; - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List? x) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - String methodWithNamedArgs(int? x, {int? y}) => 'Real'; - String methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - Future methodReturningFuture() => Future.value('Real'); - Stream methodReturningStream() => Stream.fromIterable(['Real']); - String get getter => 'Real'; + String? methodWithoutArgs() => 'Real'; + String? methodWithNormalArgs(int? x) => 'Real'; + String? methodWithListArgs(List? x) => 'Real'; + String? methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String? methodWithNamedArgs(int? x, {int? y}) => 'Real'; + String? methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; + String? methodWithObjArgs(_RealClass x) => 'Real'; + Future? methodReturningFuture() => Future.value('Real'); + Stream? methodReturningStream() => Stream.fromIterable(['Real']); + String? get getter => 'Real'; } abstract class _Foo { - String bar(); + String? bar(); } abstract class _AbstractFoo implements _Foo { @override - String bar() => baz(); + String? bar() => baz(); - String baz(); + String? baz(); String quux() => 'Real'; } @@ -145,7 +145,7 @@ void main() { .thenReturn('x y'); when(mock.methodWithTwoNamedArgs(any, z: anyNamed('z'))) .thenReturn('x z'); - expect(mock.methodWithTwoNamedArgs(42), 'x z'); + expect(mock.methodWithTwoNamedArgs(42), 'x z'); expect(mock.methodWithTwoNamedArgs(42, y: 18), equals('x y')); expect(mock.methodWithTwoNamedArgs(42, z: 17), equals('x z')); expect(mock.methodWithTwoNamedArgs(42, y: 18, z: 17), isNull); @@ -249,7 +249,7 @@ void main() { when(mock.methodReturningStream()) .thenAnswer((_) => Stream.fromIterable(['stub'])); - expect(await mock.methodReturningStream().toList(), ['stub']); + expect(await mock.methodReturningStream()?.toList(), ['stub']); }); test('should throw if named matcher is passed as the wrong name', () { diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index 316119b23..c35a65fce 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -18,20 +18,20 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; class _RealClass { - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List? x) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - String methodWithNamedArgs(int? x, {int? y}) => 'Real'; - String methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; - String methodWithObjArgs(_RealClass? x) => 'Real'; - String typeParameterizedFn(List? w, List? x, + String? methodWithoutArgs() => 'Real'; + String? methodWithNormalArgs(int? x) => 'Real'; + String? methodWithListArgs(List? x) => 'Real'; + String? methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String? methodWithNamedArgs(int? x, {int? y}) => 'Real'; + String? methodWithTwoNamedArgs(int? x, {int? y, int? z}) => 'Real'; + String? methodWithObjArgs(_RealClass? x) => 'Real'; + String? typeParameterizedFn(List? w, List? x, [List? y, List? z]) => 'Real'; - String typeParameterizedNamedFn(List? w, List? x, + String? typeParameterizedNamedFn(List? w, List? x, {List? y, List? z}) => 'Real'; - String get getter => 'Real'; + String? get getter => 'Real'; set setter(String arg) { throw StateError('I must be mocked'); } diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 212c9a471..b4425180b 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -15,21 +15,23 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; +/// Every method on this class has a nullable return type and nullable parameter +/// types. This allows each method to be verified without manual overriding or +/// code-generation. class _RealClass { - String methodWithoutArgs() => 'Real'; - String methodWithNormalArgs(int? x) => 'Real'; - String methodWithListArgs(List x) => 'Real'; - String methodWithOptionalArg([int? x]) => 'Real'; - String methodWithPositionalArgs(int? x, [int? y]) => 'Real'; - String methodWithNamedArgs(int x, {int? y}) => 'Real'; - String methodWithOnlyNamedArgs({int? y, int? z}) => 'Real'; - String methodWithObjArgs(_RealClass x) => 'Real'; - String get getter => 'Real'; + String? methodWithoutArgs() => 'Real'; + String? methodWithNormalArgs(int? x) => 'Real'; + String? methodWithListArgs(List? x) => 'Real'; + String? methodWithOptionalArg([int? x]) => 'Real'; + String? methodWithPositionalArgs(int? x, [int? y]) => 'Real'; + String? methodWithNamedArgs(int x, {int? y}) => 'Real'; + String? methodWithOnlyNamedArgs({int? y = 0, int? z}) => 'Real'; + String? get getter => 'Real'; set setter(String arg) { throw StateError('I must be mocked'); } - String methodWithLongArgs(LongToString? a, LongToString? b, + String? methodWithLongArgs(LongToString? a, LongToString? b, {LongToString? c, LongToString? d}) => 'Real'; } From cb6fd64cce40a50cda69d1a8af9bda51155991e0 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 15 Dec 2020 20:28:18 -0500 Subject: [PATCH 255/607] Support analyzer 0.41.0 PiperOrigin-RevId: 347725645 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8da65c07f..5b8f9554a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=0.39.15 <0.41.0' + analyzer: '>=0.39.15 <0.42.0' build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.15.0-nullsafety.5 From 9e6842b9d7d9e47ed99da880e8912132115304ce Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 18 Dec 2020 04:11:49 -0500 Subject: [PATCH 256/607] Support Mockito generator for immutable classes Previously, the analyzer would complain when generating mocks for immutable classes because the generated mocks are mutable and classes that implement an immutable class must themselves be immutable. We suppress this warning by adding the //ignore: must_be_immutable analyzer annotation to the generated mock. PiperOrigin-RevId: 348161693 --- pkgs/mockito/lib/src/builder.dart | 4 ++++ .../mockito/test/builder/auto_mocks_test.dart | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 4b96c6248..a4bb03399 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -468,6 +468,7 @@ class _MockLibraryInfo { Class _buildMockClass(_MockTarget mockTarget) { final typeToMock = mockTarget.classType; final classToMock = mockTarget.classElement; + final classIsImmutable = classToMock.metadata.any((it) => it.isImmutable); final className = classToMock.name; return Class((cBuilder) { @@ -478,6 +479,9 @@ class _MockLibraryInfo { ..docs.add('///') ..docs.add('/// See the documentation for Mockito\'s code generation ' 'for more information.'); + if (classIsImmutable) { + cBuilder..docs.add('// ignore: must_be_immutable'); + } // For each type parameter on [classToMock], the Mock class needs a type // parameter with same type variables, and a mirrored type argument for // the "implements" clause. diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 0cb26a516..81a276933 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -55,6 +55,15 @@ class Mock {} ''' }; +const metaAssets = { + 'meta|lib/meta.dart': ''' +library meta; +class _Immutable { + const _Immutable(); +} +const immutable = _Immutable(); +'''}; + const simpleTestAsset = { 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; @@ -112,6 +121,7 @@ void main() { String sourceAssetText, /*String|Matcher>*/ dynamic output) async { await testWithNonNullable({ + ...metaAssets, ...annotationsAsset, ...simpleTestAsset, 'foo|lib/foo.dart': sourceAssetText, @@ -2082,6 +2092,20 @@ void main() { }, ); }); + + test('adds ignore: must_be_immutable analyzer comment if mocked class is ' + 'immutable', () async { + await expectSingleNonNullableOutput( + dedent(r''' + import 'package:meta/meta.dart'; + @immutable + class Foo { + void foo(); + } + '''), + _containsAllOf('// ignore: must_be_immutable\nclass MockFoo'), + ); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From 8dbd79b85ba06491bae920e76a501cad3929963a Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 21 Dec 2020 18:09:34 -0500 Subject: [PATCH 257/607] Do not compile example or tests with DDC using null safety yet. The example app, `example/iss`, uses package:http, which is not migrated yet. This prevents a crash in DDC, as in this travis task: https://travis-ci.org/github/dart-lang/mockito/jobs/750256243 PiperOrigin-RevId: 348534584 --- pkgs/mockito/example/iss/iss.dart | 6 ++++-- pkgs/mockito/example/iss/iss_test.dart | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index be8878a54..bc5c73a98 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + import 'dart:async'; import 'dart:convert'; import 'dart:math'; @@ -22,8 +24,8 @@ import 'package:http/http.dart'; class IssLocator { final Client client; - late Point _position; - Future? _ongoingRequest; + /*late*/ Point _position; + Future /*?*/ _ongoingRequest; IssLocator(this.client); diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index dc4f3cd86..b96d52848 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// @dart=2.9 + import 'dart:math'; import 'package:mockito/mockito.dart'; From cf7f5f143033e68f0adeb1212ff3801630df9114 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 21 Dec 2020 18:18:02 -0500 Subject: [PATCH 258/607] Reformat auto_mocks_test PiperOrigin-RevId: 348535917 --- pkgs/mockito/test/builder/auto_mocks_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 81a276933..2caf2703c 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -62,7 +62,8 @@ class _Immutable { const _Immutable(); } const immutable = _Immutable(); -'''}; +''' +}; const simpleTestAsset = { 'foo|test/foo_test.dart': ''' @@ -2093,7 +2094,8 @@ void main() { ); }); - test('adds ignore: must_be_immutable analyzer comment if mocked class is ' + test( + 'adds ignore: must_be_immutable analyzer comment if mocked class is ' 'immutable', () async { await expectSingleNonNullableOutput( dedent(r''' From eca6c9eaedcdb54da22f417c5092db8444d2971b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 29 Dec 2020 16:46:22 -0800 Subject: [PATCH 259/607] Remove dart2js travis tasks --- pkgs/mockito/.travis.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml index fde1da4e6..d38251c98 100644 --- a/pkgs/mockito/.travis.yml +++ b/pkgs/mockito/.travis.yml @@ -31,10 +31,6 @@ jobs: # script: ./tool/travis.sh dartdevc_test # dart: 2.9.0 #- stage: testing - # name: "2.9.0 dart2js test" - # script: ./tool/travis.sh dart2js_test - # dart: 2.9.0 - #- stage: testing # name: "2.9.0 code coverage" # script: ./tool/travis.sh coverage # dart: 2.9.0 @@ -59,10 +55,6 @@ jobs: name: "dev DDC test" script: ./tool/travis.sh dartdevc_test dart: dev - - stage: testing - name: "dev dart2js test" - script: ./tool/travis.sh dart2js_test - dart: dev - stage: testing name: "dev code coverage" script: ./tool/travis.sh coverage From 90544a55836da13fc7fa3ac5edd568db964b138e Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 22 Dec 2020 20:13:30 -0500 Subject: [PATCH 260/607] Add tests for the manual mocking route. This route is spelled out in NULL_SAFETY_README but tests for the ideas were missing. This includes tests that show TypeErrors are thrown when methods with non-nullable parameters or return types are not manually mocked. The BUILD file needed amending because the builder tests cannot run with sound null safety. PiperOrigin-RevId: 348716933 --- pkgs/mockito/test/manual_mocks_test.dart | 214 +++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 pkgs/mockito/test/manual_mocks_test.dart diff --git a/pkgs/mockito/test/manual_mocks_test.dart b/pkgs/mockito/test/manual_mocks_test.dart new file mode 100644 index 000000000..bcfce08f5 --- /dev/null +++ b/pkgs/mockito/test/manual_mocks_test.dart @@ -0,0 +1,214 @@ +// Copyright 2020 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +class RealClass { + // Non-nullable parameters + String? nonNullableParam(int x) => 'Real'; + String? nonNullableParam2(int x) => 'Real'; + String? nonNullableParam3(T x) => 'Real'; + String? operator +(int x) => 'Real'; + + // Non-nullable return types + int nonNullableReturn(int? x) => 0; + T nonNullableReturn2(T t) => t; + Future nonNullableFutureReturn(int? x) => Future.value(0); + int get getter => 0; + + // Methods which are not manually mocked in `MockedClass` + String? notMockedNonNullableParam(int x) => 'Real'; + int notMockedNonNullableReturn() => 0; +} + +class MockedClass extends Mock implements RealClass { + @override + String? nonNullableParam(int? x) => + super.noSuchMethod(Invocation.method(#nonNullableParam, [x])); + + @override + String? nonNullableParam2(int? x) => super + .noSuchMethod(Invocation.genericMethod(#nonNullableParam2, [T], [x])); + + @override + String? nonNullableParam3(T? x) => super + .noSuchMethod(Invocation.genericMethod(#nonNullableParam3, [T], [x])); + + @override + String? operator +(int? x) => super.noSuchMethod(Invocation.method(#+, [x])); + + @override + int nonNullableReturn(int? x) => + super.noSuchMethod(Invocation.method(#nonNullableReturn, [x]), 1); + + // A generic return type is very tricky to work with in a manually mocked + // method. What value can be passed as the second argument to + // `super.noSuchMethod` which will always act as a non-nullable T? We + // "require" a named parameter, `sentinal` as this value. The named parameter + // is optional, so that the override is still legal. + @override + T nonNullableReturn2(T? x, {T? sentinal}) => super + .noSuchMethod(Invocation.method(#nonNullableReturn2, [x]), sentinal!); + + @override + Future nonNullableFutureReturn(int? x) => super.noSuchMethod( + Invocation.method(#nonNullableFutureReturn, [x]), Future.value(1)); + + @override + int get getter => super.noSuchMethod(Invocation.getter(#getter), 1); +} + +void main() { + late MockedClass mock; + + setUp(() { + mock = MockedClass(); + }); + + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + + group('when()', () { + test( + 'cannot operate on method with non-nullable params without a manual ' + 'mock', () { + // Normally this use of `any` would be a static error. To push forward to + // reveal the runtime error, we cast as dynamic. + expect( + () => when(mock.notMockedNonNullableParam(any as dynamic)) + .thenReturn('Mock'), + throwsA(TypeMatcher())); + }); + + test( + 'cannot operate on method with non-nullable return type without a ' + 'manual mock', () { + expect(() => when(mock.notMockedNonNullableReturn()).thenReturn(7), + throwsA(TypeMatcher())); + }); + + test('should mock method with non-nullable params', () { + when(mock.nonNullableParam(42)).thenReturn('Mock'); + expect(mock.nonNullableParam(43), isNull); + expect(mock.nonNullableParam(42), equals('Mock')); + }); + + test( + 'should mock method with non-nullable params with "any" argument ' + 'matcher', () { + when(mock.nonNullableParam(any)).thenReturn('Mock'); + expect(mock.nonNullableParam(100), equals('Mock')); + expect(mock.nonNullableParam(101), equals('Mock')); + }); + + test( + 'should mock generic method with non-nullable params with "any" ' + 'argument matcher', () { + when(mock.nonNullableParam2(any)).thenReturn('Mock'); + expect(mock.nonNullableParam2(100), equals('Mock')); + expect(mock.nonNullableParam2(101), equals('Mock')); + }); + + test( + 'should mock generic method with non-nullable generic params with ' + '"any" argument matcher', () { + when(mock.nonNullableParam3(any)).thenReturn('Mock'); + expect(mock.nonNullableParam3(100), equals('Mock')); + expect(mock.nonNullableParam3(101), equals('Mock')); + }); + + test('should mock operator with non-nullable param', () { + when(mock + any).thenReturn('Mock'); + expect(mock + 42, equals('Mock')); + }); + + test('should mock method with non-nullable return type', () { + when(mock.nonNullableReturn(42)).thenReturn(7); + expect(mock.nonNullableReturn(42), equals(7)); + }); + + test('should mock generic method with non-nullable return type', () { + when(mock.nonNullableReturn2(42, sentinal: 99)).thenReturn(7); + expect(mock.nonNullableReturn2(42, sentinal: 99), equals(7)); + }); + + test('should mock method with non-nullable Future return type', () async { + when(mock.nonNullableFutureReturn(42)).thenAnswer((_) async => 7); + expect(await mock.nonNullableFutureReturn(42), equals(7)); + }); + + test('should mock getter', () { + when(mock.getter).thenReturn(7); + expect(mock.getter, equals(7)); + }); + }); + + group('real calls', () { + test( + 'should throw a TypeError on a call to a function with a non-nullable ' + 'return type without a matching stub', () { + expect( + () => mock.nonNullableReturn(43), throwsA(TypeMatcher())); + }); + + test( + 'should throw a NoSuchMethodError on a call without a matching stub, ' + 'using `throwOnMissingStub` behavior', () { + throwOnMissingStub(mock); + expect(() => mock.nonNullableReturn(43), + throwsA(TypeMatcher())); + }); + }); + + group('verify()', () { + test('should verify method with non-nullable params', () { + mock.nonNullableParam(42); + verify(mock.nonNullableParam(42)).called(1); + }); + + test( + 'should verify method with non-nullable params with "any" argument ' + 'matcher', () { + mock.nonNullableParam(42); + verify(mock.nonNullableParam(any)).called(1); + }); + + test('should verify method with non-nullable return type', () { + when(mock.nonNullableReturn(42)).thenReturn(7); + mock.nonNullableReturn(42); + verify(mock.nonNullableReturn(42)).called(1); + }); + }); + + group('verifyNever()', () { + test('should verify method with non-nullable params', () { + mock.nonNullableParam(42); + verifyNever(mock.nonNullableParam(43)); + }); + + test( + 'should verify method with non-nullable params with "any" argument ' + 'matcher', () { + verifyNever(mock.nonNullableParam(any)); + }); + + test('should verify method with non-nullable return type', () { + verifyNever(mock.nonNullableReturn(42)); + }); + }); +} From 6e552c3bc296dec6c4b10f2b39a899d48121f6be Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 28 Dec 2020 16:31:28 -0500 Subject: [PATCH 261/607] Do not generated stubs for methods from Object. The Mock class itself overrides hashCode, operator ==, and toString(), and uses some of these methods, so they should not be stubbed on any class which extends Mock. (This is the `if (type.superclass != null)` => `if (!type.superclass.isDartCoreObject)` changes.) While testing these fixes, I encountered an issue with relative imports from a non-lib/ directory, and fixed that to use `resolver.assetIdForElement(element)`. However, assetIdForElement returns a Future, which doesn't play well deep in all of the Builder code. So I extracted out the code that resolves asset IDs into a function, _resolveAssetUris, and an Element Visitor, _TypeVisitor. This code gathers all of the types referenced by any public APIs which need to be mocked, and resolves all of the AssetIds, and then passes a mapping to _MockLibraryInfo. I also added end-to-end tests to test the runtime behavior of generated mocks. I need to add _many_ more tests in this vein. PiperOrigin-RevId: 349324517 --- pkgs/mockito/CHANGELOG.md | 6 + pkgs/mockito/build.yaml | 7 + pkgs/mockito/lib/src/builder.dart | 216 ++++++++++++++++-- pkgs/mockito/pubspec.yaml | 3 +- .../mockito/test/builder/auto_mocks_test.dart | 162 +++++++++++++ pkgs/mockito/test/end2end/foo.dart | 5 + .../test/end2end/generated_mocks_test.dart | 31 +++ 7 files changed, 404 insertions(+), 26 deletions(-) create mode 100644 pkgs/mockito/test/end2end/foo.dart create mode 100644 pkgs/mockito/test/end2end/generated_mocks_test.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 3b80be952..1fa26ebec 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.0-nullsafety.1 + +* Fix an issue with generated mocks overriding methods from Object, such as + `operator ==` ([#306](https://github.com/dart-lang/mockito/issues/306)). +* Fix an issue with relative imports in generated mocks. + ## 5.0.0-nullsafety.0 * Migrate the core libraries and tests to null safety. The builder at diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index 3945a1470..47eebd83d 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -1,3 +1,10 @@ +targets: + $default: + builders: + mockito|mockBuilder: + generate_for: + - test/testing/*.dart + builders: mockBuilder: import: "package:mockito/src/builder.dart" diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a4bb03399..22c0e5f65 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -19,10 +19,12 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/dart/element/type_system.dart'; +import 'package:analyzer/dart/element/visitor.dart'; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'package:meta/meta.dart'; +import 'package:path/path.dart' as p; import 'package:source_gen/source_gen.dart'; /// For a source Dart library, generate the mocks referenced therein. @@ -49,11 +51,13 @@ class MockBuilder implements Builder { final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); final mockTargetGatherer = _MockTargetGatherer(entryLib); + var entryAssetId = await buildStep.resolver.assetIdForElement(entryLib); + final assetUris = await _resolveAssetUris( + buildStep.resolver, mockTargetGatherer._mockTargets, entryAssetId.path); + final mockLibrary = Library((b) { var mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets, - sourceLibIsNonNullable: sourceLibIsNonNullable, - typeProvider: entryLib.typeProvider, - typeSystem: entryLib.typeSystem); + assetUris: assetUris, entryLib: entryLib); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); @@ -71,12 +75,160 @@ class MockBuilder implements Builder { await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); } + Future> _resolveAssetUris(Resolver resolver, + List<_MockTarget> mockTargets, String entryAssetPath) async { + final typeVisitor = _TypeVisitor(); + final seenTypes = {}; + + void addTypesFrom(analyzer.InterfaceType type) { + // Prevent infinite recursion. + if (seenTypes.contains(type)) { + return; + } + seenTypes.add(type); + type.element.accept(typeVisitor); + // For a type like `Foo`, add the `Bar`. + (type.typeArguments ?? []) + .whereType() + .forEach(addTypesFrom); + // For a type like `Foo extends Bar`, add the `Baz`. + for (var supertype in type.allSupertypes) { + addTypesFrom(supertype); + } + } + + for (var mockTarget in mockTargets) { + addTypesFrom(mockTarget.classType); + } + + final typeUris = {}; + + for (var element in typeVisitor._elements) { + if (element.library.isInSdk) { + typeUris[element] = element.library.source.uri.toString(); + continue; + } + + try { + var typeAssetId = await resolver.assetIdForElement(element); + + if (typeAssetId.path.startsWith('lib/')) { + typeUris[element] = typeAssetId.uri.toString(); + } else { + typeUris[element] = + p.relative(typeAssetId.path, from: p.dirname(entryAssetPath)); + } + } on UnresolvableAssetException { + // Asset may be in a summary. + typeUris[element] = element.library.source.uri.toString(); + continue; + } + } + + return typeUris; + } + @override final buildExtensions = const { '.dart': ['.mocks.dart'] }; } +/// An [Element] visitor which collects the elements of all of the +/// [analyzer.InterfaceType]s which it encounters. +class _TypeVisitor extends RecursiveElementVisitor { + final _elements = {}; + + @override + void visitClassElement(ClassElement element) { + _elements.add(element); + super.visitClassElement(element); + } + + @override + void visitFieldElement(FieldElement element) { + _addType(element.type); + super.visitFieldElement(element); + } + + @override + void visitMethodElement(MethodElement element) { + _addType(element.returnType); + super.visitMethodElement(element); + } + + @override + void visitParameterElement(ParameterElement element) { + _addType(element.type); + if (element.hasDefaultValue) { + _addTypesFromConstant(element.computeConstantValue()); + } + super.visitParameterElement(element); + } + + @override + void visitTypeParameterElement(TypeParameterElement element) { + _addType(element.bound); + super.visitTypeParameterElement(element); + } + + void _addType(analyzer.DartType type) { + if (type == null) return; + + if (type is analyzer.InterfaceType) { + _elements.add(type.element); + (type.typeArguments ?? []).forEach(_addType); + } else if (type is analyzer.FunctionType) { + _addType(type.returnType); + var element = type.element; + if (element != null) { + if (element is FunctionTypeAliasElement) { + _elements.add(element); + } else { + _elements.add(element.enclosingElement as FunctionTypeAliasElement); + } + } + } + } + + void _addTypesFromConstant(DartObject object) { + final constant = ConstantReader(object); + if (constant.isNull || + constant.isBool || + constant.isInt || + constant.isDouble || + constant.isString || + constant.isType) { + // No types to add from a literal. + return; + } else if (constant.isList) { + for (var element in constant.listValue) { + _addTypesFromConstant(element); + } + } else if (constant.isSet) { + for (var element in constant.setValue) { + _addTypesFromConstant(element); + } + } else if (constant.isMap) { + for (var pair in constant.mapValue.entries) { + _addTypesFromConstant(pair.key); + _addTypesFromConstant(pair.value); + } + } else { + // If [constant] is not null, a literal, or a type, then it must be an + // object constructed with `const`. Revive it. + var revivable = constant.revive(); + for (var argument in revivable.positionalArguments) { + _addTypesFromConstant(argument); + } + for (var pair in revivable.namedArguments.entries) { + _addTypesFromConstant(pair.value); + } + _addType(object.type); + } + } +} + class _MockTarget { /// The class to be mocked. final analyzer.InterfaceType classType; @@ -424,9 +576,19 @@ class _MockLibraryInfo { /// fake classes are added to the generated library. final fakedClassElements = []; + /// A mapping of each necessary [Element] to a URI from which it can be + /// imported. + /// + /// This mapping is generated eagerly so as to avoid any asynchronous + /// Asset-resolving while building the mock library. + final Map assetUris; + /// Build mock classes for [mockTargets]. _MockLibraryInfo(Iterable<_MockTarget> mockTargets, - {this.sourceLibIsNonNullable, this.typeProvider, this.typeSystem}) { + {this.assetUris, LibraryElement entryLib}) + : sourceLibIsNonNullable = entryLib.isNonNullableByDefault, + typeProvider = entryLib.typeProvider, + typeSystem = entryLib.typeSystem { for (final mockTarget in mockTargets) { mockClasses.add(_buildMockClass(mockTarget)); } @@ -507,7 +669,7 @@ class _MockLibraryInfo { cBuilder.implements.add(TypeReference((b) { b ..symbol = classToMock.name - ..url = _typeImport(mockTarget.classType) + ..url = _typeImport(mockTarget.classElement) ..types.addAll(typeArguments); })); if (!mockTarget.returnNullOnMissingStub) { @@ -526,7 +688,7 @@ class _MockLibraryInfo { /// Yields all of the field overrides required for [type]. /// - /// This includes fields of supertypes and mixed in types. [overriddenMethods] + /// This includes fields of supertypes and mixed in types. [overriddenFields] /// is used to track which fields have already been yielded. /// /// Only public instance fields which have either a potentially non-nullable @@ -553,7 +715,7 @@ class _MockLibraryInfo { yield* fieldOverrides(mixin, overriddenFields); } } - if (type.superclass != null) { + if (!type.superclass.isDartCoreObject) { yield* fieldOverrides(type.superclass, overriddenFields); } } @@ -592,7 +754,7 @@ class _MockLibraryInfo { yield* methodOverrides(mixin, overriddenMethods); } } - if (type.superclass != null) { + if (!type.superclass.isDartCoreObject) { yield* methodOverrides(type.superclass, overriddenMethods); } } @@ -794,7 +956,7 @@ class _MockLibraryInfo { cBuilder.implements.add(TypeReference((b) { b ..symbol = elementToFake.name - ..url = _typeImport(dartType) + ..url = _typeImport(elementToFake) ..types.addAll(typeParameters); })); })); @@ -892,7 +1054,8 @@ class _MockLibraryInfo { } if (revivable.source.fragment.isEmpty) { // We can create this invocation by referring to a const field. - return refer(revivable.accessor, _typeImport(object.type)); + return refer(revivable.accessor, + _typeImport(object.type.element as TypeDefiningElement)); } final name = revivable.source.fragment; @@ -904,7 +1067,8 @@ class _MockLibraryInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final type = refer(name, _typeImport(object.type)); + final type = + refer(name, _typeImport(object.type.element as TypeDefiningElement)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( revivable.accessor, @@ -1002,7 +1166,7 @@ class _MockLibraryInfo { b ..symbol = type.element.name ..isNullable = forceNullable || typeSystem.isPotentiallyNullable(type) - ..url = _typeImport(type) + ..url = _typeImport(type.element) ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { @@ -1027,16 +1191,16 @@ class _MockLibraryInfo { }); } return TypeReference((b) { - Element typedef; + TypeDefiningElement typedef; if (element is FunctionTypeAliasElement) { typedef = element; } else { - typedef = element.enclosingElement; + typedef = element.enclosingElement as FunctionTypeAliasElement; } b ..symbol = typedef.name - ..url = _typeImport(type) + ..url = _typeImport(typedef) ..isNullable = forceNullable || typeSystem.isNullable(type); for (var typeArgument in type.typeArguments) { b.types.add(_typeReference(typeArgument)); @@ -1051,25 +1215,27 @@ class _MockLibraryInfo { } else { return refer( type.getDisplayString(withNullability: false), - _typeImport(type), + _typeImport(type.element as TypeDefiningElement), ); } } - /// Returns the import URL for [type]. + /// Returns the import URL for [element]. /// /// For some types, like `dynamic` and type variables, this may return null. - String _typeImport(analyzer.DartType type) { + String _typeImport(TypeDefiningElement element) { // For type variables, no import needed. - if (type is analyzer.TypeParameterType) return null; - - var library = type.element?.library; + if (element is TypeParameterElement) return null; // For types like `dynamic`, return null; no import needed. - if (library == null) return null; - // TODO(srawlins): See what other code generators do here to guarantee sane - // URIs. - return library.source.uri.toString(); + if (element?.library == null) return null; + + assert( + assetUris.containsKey(element), + () => + 'An element, "${element}", is missing from the asset URI mapping'); + + return assetUris[element]; } } diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 5b8f9554a..88b4c30dd 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.0 +version: 5.0.0-nullsafety.1 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -24,5 +24,6 @@ dev_dependencies: build_web_compilers: '>=1.0.0 <3.0.0' http: ^0.12.0 package_config: ^1.9.3 + path: ^1.8.0-nullsafety.3 pedantic: ^1.10.0-nullsafety test: ^1.16.0-nullsafety.12 diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 2caf2703c..3d8930c31 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -783,6 +783,168 @@ void main() { expect(mocksContent, contains('List<_i2.Foo>? list')); }); + test( + 'imports libraries for external class types found in a method return ' + 'type', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + Future f() async {} + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('_i3.Future f()')); + }); + + test('imports libraries for external class types found in a type argument', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + List f() => []; + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('List<_i3.Future> f()')); + }); + + test( + 'imports libraries for external class types found in the return type of ' + 'a function-typed parameter', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + void f(Future a()) {} + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('f(_i3.Future Function()? a)')); + }); + + test( + 'imports libraries for external class types found in a parameter type of ' + 'a function-typed parameter', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + void f(void a(Future b)) {} + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('f(void Function(_i3.Future)? a)')); + }); + + test( + 'imports libraries for external class types found in a function-typed ' + 'parameter', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + void f(Future a()) {} + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('f(_i3.Future Function()? a)')); + }); + + test( + 'imports libraries for external class types found in a FunctionType ' + 'parameter', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + void f(Future Function() a) {} + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('f(_i3.Future Function()? a)')); + }); + + test( + 'imports libraries for external class types found nested in a ' + 'function-typed parameter', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + void f(void a(Future b)) {} + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('f(void Function(_i3.Future)? a)')); + }); + + test( + 'imports libraries for external class types found in the bound of a ' + 'type parameter of a method', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + class Foo { + void f(T a) {} + } + ''')); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('f>(T? a)')); + }); + + test( + 'imports libraries for external class types found in the default value ' + 'of a parameter', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:convert'; + class Foo { + void f([Object a = utf8]) {} + } + ''')); + expect(mocksContent, contains("import 'dart:convert' as _i3;")); + expect(mocksContent, contains('f([Object? a = const _i3.Utf8Codec()])')); + }); + + test( + 'imports libraries for external class types found in an inherited method', + () async { + await testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'bar.dart'; + class Foo extends Bar {} + ''', + 'foo|lib/bar.dart': ''' + import 'dart:async'; + class Bar { + m(Future a) {} + } + ''', + }); + var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + var mocksContent = utf8.decode(writer.assets[mocksAsset]); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('m(_i3.Future? a)')); + }); + + test( + 'imports libraries for external class types found in an inherited method' + 'via a generic instantiation', () async { + await testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'dart:async'; + import 'bar.dart'; + class Foo extends Bar> {} + ''', + 'foo|lib/bar.dart': ''' + class Bar { + m(T a) {} + } + ''', + }); + var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + var mocksContent = utf8.decode(writer.assets[mocksAsset]); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('m(_i3.Future? a)')); + }); + test('imports libraries for type aliases with external types', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart new file mode 100644 index 000000000..8f69d54ba --- /dev/null +++ b/pkgs/mockito/test/end2end/foo.dart @@ -0,0 +1,5 @@ +class Foo { + String m(Bar bar) => 'result'; +} + +class Bar {} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart new file mode 100644 index 000000000..62f16d3ac --- /dev/null +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -0,0 +1,31 @@ +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +import 'foo.dart'; +import 'generated_mocks_test.mocks.dart'; + +@GenerateMocks([ + Foo, + Bar +], customMocks: [ + MockSpec(as: #MockFooReturningNull, returnNullOnMissingStub: true), + MockSpec(as: #MockBarReturningNull, returnNullOnMissingStub: true), +]) +void main() { + test('a generated mock can be used as a stub argument', () { + var foo = MockFoo(); + var bar = MockBar(); + when(foo.m(bar)).thenReturn('mocked result'); + expect(foo.m(bar), equals('mocked result')); + }); + + test( + 'a generated mock which returns null on missing stubs can be used as a ' + 'stub argument', () { + var foo = MockFooReturningNull(); + var bar = MockBarReturningNull(); + when(foo.m(bar)).thenReturn('mocked result'); + expect(foo.m(bar), equals('mocked result')); + }); +} From 23a94d916bdaf5577b1c1c231136e5dda17961fa Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 28 Dec 2020 19:44:55 -0500 Subject: [PATCH 262/607] Fix testing directory in build.yaml. PiperOrigin-RevId: 349347746 --- pkgs/mockito/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index 47eebd83d..2120fc9fb 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -3,7 +3,7 @@ targets: builders: mockito|mockBuilder: generate_for: - - test/testing/*.dart + - test/end2end/*.dart builders: mockBuilder: From f3220794b64d0c6e660625c7e5b6aeda3b358b4f Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 29 Dec 2020 18:08:09 -0500 Subject: [PATCH 263/607] Insert casts from the `super.noSuchMethod` result for any codebase which uses `implicit-dynamic: false`. PiperOrigin-RevId: 349474887 --- pkgs/mockito/lib/src/builder.dart | 10 ++- .../mockito/test/builder/auto_mocks_test.dart | 70 +++++++++---------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 22c0e5f65..35247f7e6 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -836,10 +836,16 @@ class _MockLibraryInfo { final dummyReturnValue = _dummyValue(method.returnType); noSuchMethodArgs.add(dummyReturnValue); } - final returnNoSuchMethod = + var superNoSuchMethod = refer('super').property('noSuchMethod').call(noSuchMethodArgs); + if (!method.returnType.isVoid && !method.returnType.isDynamic) { + superNoSuchMethod = + superNoSuchMethod.asA(_typeReference(method.returnType)); + } - builder.body = returnNoSuchMethod.code; + builder + ..lambda = true + ..body = superNoSuchMethod.code; } Expression _dummyValue(analyzer.DartType type) { diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 3d8930c31..8029f14b5 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -508,7 +508,7 @@ void main() { } '''), _containsAllOf('_i3.Future m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), Future.value(null));'), + '(super.noSuchMethod(Invocation.method(#m, []), Future.value(null))'), ); }); @@ -520,7 +520,7 @@ void main() { } '''), _containsAllOf('_i3.Stream m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), Stream.empty());'), + '(super.noSuchMethod(Invocation.method(#m, []), Stream.empty())'), ); }); @@ -532,7 +532,7 @@ void main() { } '''), _containsAllOf('Iterable m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), []);'), + '(super.noSuchMethod(Invocation.method(#m, []), [])'), ); }); @@ -1141,7 +1141,7 @@ void main() { } '''), _containsAllOf( - 'int m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]), 0);'), + 'int m(int? a) => (super.noSuchMethod(Invocation.method(#m, [a]), 0)'), ); }); @@ -1153,7 +1153,7 @@ void main() { } '''), _containsAllOf( - 'int? m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), + 'int? m(int? a) => (super.noSuchMethod(Invocation.method(#m, [a]))'), ); }); @@ -1165,7 +1165,7 @@ void main() { } '''), _containsAllOf('List m(int? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]), []);'), + '(super.noSuchMethod(Invocation.method(#m, [a]), [])'), ); }); @@ -1177,7 +1177,7 @@ void main() { } '''), _containsAllOf( - 'T? m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), + 'T? m(int? a) => (super.noSuchMethod(Invocation.method(#m, [a]))'), ); }); @@ -1188,8 +1188,8 @@ void main() { m(int a); } '''), - _containsAllOf( - 'dynamic m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf('dynamic m(int? a) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), ); }); @@ -1321,7 +1321,7 @@ void main() { } '''), _containsAllOf( - 'int m() => super.noSuchMethod(Invocation.method(#m, []), 0);'), + 'int m() => (super.noSuchMethod(Invocation.method(#m, []), 0)'), ); }); @@ -1497,7 +1497,7 @@ void main() { } '''), _containsAllOf('int operator +(_i2.Foo? other) =>', - 'super.noSuchMethod(Invocation.method(#+, [other]), 0);'), + '(super.noSuchMethod(Invocation.method(#+, [other]), 0)'), ); }); @@ -1508,8 +1508,8 @@ void main() { int operator [](int x) => 7; } '''), - _containsAllOf( - 'int operator [](int? x) => super.noSuchMethod(Invocation.method(#[], [x]), 0);'), + _containsAllOf('int operator [](int? x) =>', + '(super.noSuchMethod(Invocation.method(#[], [x]), 0)'), ); }); @@ -1521,7 +1521,7 @@ void main() { } '''), _containsAllOf( - 'int operator ~() => super.noSuchMethod(Invocation.method(#~, []), 0);'), + 'int operator ~() => (super.noSuchMethod(Invocation.method(#~, []), 0)'), ); }); @@ -1533,7 +1533,7 @@ void main() { } '''), _containsAllOf( - 'bool m() => super.noSuchMethod(Invocation.method(#m, []), false);'), + 'bool m() => (super.noSuchMethod(Invocation.method(#m, []), false)'), ); }); @@ -1545,7 +1545,7 @@ void main() { } '''), _containsAllOf( - 'double m() => super.noSuchMethod(Invocation.method(#m, []), 0.0);'), + 'double m() => (super.noSuchMethod(Invocation.method(#m, []), 0.0)'), ); }); @@ -1557,7 +1557,7 @@ void main() { } '''), _containsAllOf( - 'int m() => super.noSuchMethod(Invocation.method(#m, []), 0);'), + 'int m() => (super.noSuchMethod(Invocation.method(#m, []), 0)'), ); }); @@ -1569,7 +1569,7 @@ void main() { } '''), _containsAllOf( - "String m() => super.noSuchMethod(Invocation.method(#m, []), '');"), + "String m() => (super.noSuchMethod(Invocation.method(#m, []), '')"), ); }); @@ -1581,7 +1581,7 @@ void main() { } '''), _containsAllOf('List<_i2.Foo> m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>[]);'), + '(super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>[])'), ); }); @@ -1593,7 +1593,7 @@ void main() { } '''), _containsAllOf('Set<_i2.Foo> m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>{});'), + '(super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>{})'), ); }); @@ -1605,7 +1605,7 @@ void main() { } '''), _containsAllOf('Map m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), {});'), + '(super.noSuchMethod(Invocation.method(#m, []), {})'), ); }); @@ -1617,7 +1617,7 @@ void main() { } '''), _containsAllOf('Map m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), {});'), + '(super.noSuchMethod(Invocation.method(#m, []), {})'), ); }); @@ -1630,7 +1630,7 @@ void main() { } '''), _containsAllOf('_i3.Future m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), Future.value(false));'), + '(super.noSuchMethod(Invocation.method(#m, []), Future.value(false))'), ); }); @@ -1642,7 +1642,7 @@ void main() { } '''), _containsAllOf('Stream m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), Stream.empty());'), + '(super.noSuchMethod(Invocation.method(#m, []), Stream.empty())'), ); }); @@ -1657,8 +1657,8 @@ void main() { Bar(this.name); } '''), - _containsAllOf( - '_i2.Bar m() => super.noSuchMethod(Invocation.method(#m, []), _FakeBar());'), + _containsAllOf('_i2.Bar m() =>', + '(super.noSuchMethod(Invocation.method(#m, []), _FakeBar())'), ); }); @@ -1671,7 +1671,7 @@ void main() { class Bar {} '''), _containsAllOf('Bar m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), _FakeBar());'), + 'super.noSuchMethod(Invocation.method(#m, []), _FakeBar())'), ); }); @@ -1686,8 +1686,8 @@ void main() { two, } '''), - _containsAllOf( - '_i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _i2.Bar.one);'), + _containsAllOf('_i2.Bar m1() =>', + '(super.noSuchMethod(Invocation.method(#m1, []), _i2.Bar.one)'), ); }); @@ -1700,8 +1700,8 @@ void main() { void Function(int, [String]) m() => (int i, [String s]) {}; } '''), - _containsAllOf('void Function(int, [String]) m() => super', - '.noSuchMethod(Invocation.method(#m, []), (int __p0, [String __p1]) {});'), + _containsAllOf('void Function(int, [String]) m() => (super', + '.noSuchMethod(Invocation.method(#m, []), (int __p0, [String __p1]) {})'), ); }); @@ -1714,8 +1714,8 @@ void main() { void Function(Foo, {bool b}) m() => (Foo f, {bool b}) {}; } '''), - _containsAllOf('void Function(_i2.Foo, {bool b}) m() => super', - '.noSuchMethod(Invocation.method(#m, []), (_i2.Foo __p0, {bool b}) {});'), + _containsAllOf('void Function(_i2.Foo, {bool b}) m() => (super', + '.noSuchMethod(Invocation.method(#m, []), (_i2.Foo __p0, {bool b}) {})'), ); }); @@ -1729,7 +1729,7 @@ void main() { } '''), _containsAllOf('_i2.Foo Function() m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), () => _FakeFoo());'), + 'super.noSuchMethod(Invocation.method(#m, []), () => _FakeFoo())'), ); }); @@ -1744,7 +1744,7 @@ void main() { // TODO(srawlins): This output is invalid: `T __p0` is out of the scope // where T is defined. _containsAllOf('T? Function(T) m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), (T __p0) => null);'), + 'super.noSuchMethod(Invocation.method(#m, []), (T __p0) => null)'), ); }); From e8890308baf507542beaba89f82c21c33dcd4a4f Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 29 Dec 2020 18:37:48 -0500 Subject: [PATCH 264/607] Update travis to use build_runner in the VM tests, and only analyze lib/ Since there are now files being generated (by build_runner) in test/end2end, we cannot analyze it (on Travis) before building. Trust the analyzer results from Google-internal. PiperOrigin-RevId: 349478269 --- pkgs/mockito/tool/travis.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 36e52164c..0ffe0783c 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -31,13 +31,13 @@ while (( "$#" )); do ;; dartanalyzer) echo echo -e '\033[1mTASK: dartanalyzer\033[22m' - echo -e 'dartanalyzer --fatal-warnings .' - dartanalyzer --fatal-warnings . || EXIT_CODE=$? + echo -e 'dartanalyzer --fatal-warnings lib' + dartanalyzer --fatal-warnings lib || EXIT_CODE=$? ;; vm_test) echo echo -e '\033[1mTASK: vm_test\033[22m' - echo -e 'pub run test -p vm' - pub run test -p vm || EXIT_CODE=$? + echo -e 'pub run build_runner test -p vm' + pub run build_runner test -p vm || EXIT_CODE=$? ;; dartdevc_build) echo echo -e '\033[1mTASK: build\033[22m' From c552edbadc785d576c03eb125bd0c14cacabd3a0 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 29 Dec 2020 19:11:54 -0500 Subject: [PATCH 265/607] Fix Travis VM test task - missed the `--` PiperOrigin-RevId: 349482389 --- pkgs/mockito/tool/travis.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 0ffe0783c..a45c8b728 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -36,8 +36,8 @@ while (( "$#" )); do ;; vm_test) echo echo -e '\033[1mTASK: vm_test\033[22m' - echo -e 'pub run build_runner test -p vm' - pub run build_runner test -p vm || EXIT_CODE=$? + echo -e 'pub run build_runner test -- -p vm' + pub run build_runner test -- -p vm || EXIT_CODE=$? ;; dartdevc_build) echo echo -e '\033[1mTASK: build\033[22m' From a25705fe9975ab6da1cd7ec5650df84a296b90ef Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 29 Dec 2020 19:34:05 -0500 Subject: [PATCH 266/607] Remove dart2js from travis tasks. * We test this internally in Google. * Using build_runner would make it the same command? Something wasn't running in production mode... PiperOrigin-RevId: 349484860 --- pkgs/mockito/tool/travis.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index a45c8b728..9a4cb7251 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -49,11 +49,6 @@ while (( "$#" )); do echo -e 'pub run build_runner test -- -p chrome' xvfb-run pub run build_runner test -- -p chrome || EXIT_CODE=$? ;; - dart2js_test) echo - echo -e '\033[1mTASK: dart2js_test\033[22m' - echo -e 'pub run test -p chrome' - xvfb-run pub run test -p chrome || EXIT_CODE=$? - ;; coverage) echo echo -e '\033[1mTASK: coverage\033[22m' if [ "$REPO_TOKEN" ]; then From 6d9ce855597abb2b4c2e33e8df5599ac453f0684 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 30 Dec 2020 14:06:12 -0500 Subject: [PATCH 267/607] Move 'path' from dev dependencies to regular dependencies, for new use in builder. PiperOrigin-RevId: 349578592 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 88b4c30dd..fe800a89f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: dart_style: ^1.3.6 matcher: ^0.12.10-nullsafety.3 meta: ^1.3.0-nullsafety + path: ^1.8.0-nullsafety.3 source_gen: ^0.9.6 test_api: ^0.2.19-nullsafety @@ -24,6 +25,5 @@ dev_dependencies: build_web_compilers: '>=1.0.0 <3.0.0' http: ^0.12.0 package_config: ^1.9.3 - path: ^1.8.0-nullsafety.3 pedantic: ^1.10.0-nullsafety test: ^1.16.0-nullsafety.12 From 7c6bc315e2fdeddc119bd2f27eae30d8c457bd1d Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 5 Jan 2021 10:41:29 -0500 Subject: [PATCH 268/607] Fix `implicit-casts: false` errors. PiperOrigin-RevId: 350137943 --- pkgs/mockito/test/manual_mocks_test.dart | 31 ++++++++++++++---------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/pkgs/mockito/test/manual_mocks_test.dart b/pkgs/mockito/test/manual_mocks_test.dart index bcfce08f5..110251c3c 100644 --- a/pkgs/mockito/test/manual_mocks_test.dart +++ b/pkgs/mockito/test/manual_mocks_test.dart @@ -36,22 +36,25 @@ class RealClass { class MockedClass extends Mock implements RealClass { @override String? nonNullableParam(int? x) => - super.noSuchMethod(Invocation.method(#nonNullableParam, [x])); + super.noSuchMethod(Invocation.method(#nonNullableParam, [x])) as String?; @override - String? nonNullableParam2(int? x) => super - .noSuchMethod(Invocation.genericMethod(#nonNullableParam2, [T], [x])); + String? nonNullableParam2(int? x) => + super.noSuchMethod(Invocation.genericMethod(#nonNullableParam2, [T], [x])) + as String?; @override - String? nonNullableParam3(T? x) => super - .noSuchMethod(Invocation.genericMethod(#nonNullableParam3, [T], [x])); + String? nonNullableParam3(T? x) => + super.noSuchMethod(Invocation.genericMethod(#nonNullableParam3, [T], [x])) + as String?; @override - String? operator +(int? x) => super.noSuchMethod(Invocation.method(#+, [x])); + String? operator +(int? x) => + super.noSuchMethod(Invocation.method(#+, [x])) as String?; @override int nonNullableReturn(int? x) => - super.noSuchMethod(Invocation.method(#nonNullableReturn, [x]), 1); + super.noSuchMethod(Invocation.method(#nonNullableReturn, [x]), 1) as int; // A generic return type is very tricky to work with in a manually mocked // method. What value can be passed as the second argument to @@ -59,15 +62,17 @@ class MockedClass extends Mock implements RealClass { // "require" a named parameter, `sentinal` as this value. The named parameter // is optional, so that the override is still legal. @override - T nonNullableReturn2(T? x, {T? sentinal}) => super - .noSuchMethod(Invocation.method(#nonNullableReturn2, [x]), sentinal!); + T nonNullableReturn2(T? x, {T? sentinal}) => + super.noSuchMethod(Invocation.method(#nonNullableReturn2, [x]), sentinal!) + as T; @override Future nonNullableFutureReturn(int? x) => super.noSuchMethod( - Invocation.method(#nonNullableFutureReturn, [x]), Future.value(1)); + Invocation.method(#nonNullableFutureReturn, [x]), Future.value(1)) + as Future; @override - int get getter => super.noSuchMethod(Invocation.getter(#getter), 1); + int get getter => super.noSuchMethod(Invocation.getter(#getter), 1) as int; } void main() { @@ -88,9 +93,9 @@ void main() { 'cannot operate on method with non-nullable params without a manual ' 'mock', () { // Normally this use of `any` would be a static error. To push forward to - // reveal the runtime error, we cast as dynamic. + // reveal the runtime error, we cast as int. expect( - () => when(mock.notMockedNonNullableParam(any as dynamic)) + () => when(mock.notMockedNonNullableParam(any as int)) .thenReturn('Mock'), throwsA(TypeMatcher())); }); From 6e596f2553fd196e24fe8891e3e02c21b7a72d37 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 5 Jan 2021 12:09:30 -0500 Subject: [PATCH 269/607] Clean up generated code to have few static analysis violations: * Add `@override` to overridden methods. * Ignore `unnecessary_parenthesis` across generated libraries, as code_builder's `asA` API adds defensive parentheses unconditionally. Fixes https://github.com/dart-lang/mockito/issues/311 PiperOrigin-RevId: 350152370 --- pkgs/mockito/lib/src/builder.dart | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 35247f7e6..ee1e84e97 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -55,18 +55,22 @@ class MockBuilder implements Builder { final assetUris = await _resolveAssetUris( buildStep.resolver, mockTargetGatherer._mockTargets, entryAssetId.path); - final mockLibrary = Library((b) { - var mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets, - assetUris: assetUris, entryLib: entryLib); - b.body.addAll(mockLibraryInfo.fakeClasses); - b.body.addAll(mockLibraryInfo.mockClasses); - }); + final mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets, + assetUris: assetUris, entryLib: entryLib); - if (mockLibrary.body.isEmpty) { + if (mockLibraryInfo.fakeClasses.isEmpty && + mockLibraryInfo.mockClasses.isEmpty) { // Nothing to mock here! return; } + final mockLibrary = Library((b) { + // The code_builder `asA` API unconditionally adds defensive parentheses. + b.body.add(Code('\n\n// ignore_for_file: unnecessary_parenthesis\n\n')); + b.body.addAll(mockLibraryInfo.fakeClasses); + b.body.addAll(mockLibraryInfo.mockClasses); + }); + final emitter = DartEmitter.scoped(useNullSafetySyntax: sourceLibIsNonNullable); final mockLibraryContent = @@ -795,6 +799,7 @@ class _MockLibraryInfo { if (method.isOperator) name = 'operator$name'; builder ..name = name + ..annotations.addAll([refer('override')]) ..returns = _typeReference(method.returnType); if (method.typeParameters != null) { From 3c069091f6b5d62e7b09dc403b44788746ae782b Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 7 Jan 2021 14:32:10 -0500 Subject: [PATCH 270/607] Fix import URI for elements in part files. Fixes https://github.com/dart-lang/mockito/issues/310 PiperOrigin-RevId: 350604317 --- pkgs/mockito/lib/src/builder.dart | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index ee1e84e97..145a041c2 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -114,7 +114,7 @@ class MockBuilder implements Builder { } try { - var typeAssetId = await resolver.assetIdForElement(element); + var typeAssetId = await resolver.assetIdForElement(element.library); if (typeAssetId.path.startsWith('lib/')) { typeUris[element] = typeAssetId.uri.toString(); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 8029f14b5..c37a1d932 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -783,6 +783,29 @@ void main() { expect(mocksContent, contains('List<_i2.Foo>? list')); }); + test('imports libraries for external class types declared in parts', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + part 'foo_part.dart'; + '''), + 'foo|lib/foo_part.dart': dedent(r''' + part of 'foo.dart'; + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void fooTests() {} + ''' + }); + expect(mocksContent, contains("import 'package:foo/foo.dart' as _i2;")); + expect(mocksContent, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + }); + test( 'imports libraries for external class types found in a method return ' 'type', () async { From 768c8bf40b52898159f1bf127f0363ed192a7b7e Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 11 Jan 2021 13:08:26 -0500 Subject: [PATCH 271/607] Bump to 5.0.0-nullsafety.2 PiperOrigin-RevId: 351178884 --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 1fa26ebec..89f3b61f7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.0.0-nullsafety.2 + +* Fix issue with generated code which references a class declared in a part + ([#310](https://github.com/dart-lang/mockito/issues/310)). + ## 5.0.0-nullsafety.1 * Fix an issue with generated mocks overriding methods from Object, such as diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index fe800a89f..4d45cd972 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.1 +version: 5.0.0-nullsafety.2 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito From 8186fe4cba1505989ebc6522bcfb2ff431b29ec3 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 13 Jan 2021 12:32:25 -0500 Subject: [PATCH 272/607] Ignore two more lint rules in generated code which we cannot fix yet today. PiperOrigin-RevId: 351603126 --- pkgs/mockito/lib/src/builder.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 145a041c2..06d590b96 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -65,6 +65,10 @@ class MockBuilder implements Builder { } final mockLibrary = Library((b) { + // We don't properly prefix imported class names in doc comments. + b.body.add(Code('\n\n// ignore_for_file: comment_references\n\n')); + // code_builder does not sort import directives. + b.body.add(Code('\n\n// ignore_for_file: directives_ordering\n\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. b.body.add(Code('\n\n// ignore_for_file: unnecessary_parenthesis\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); @@ -641,6 +645,8 @@ class _MockLibraryInfo { cBuilder ..name = mockTarget.mockName ..extend = refer('Mock', 'package:mockito/mockito.dart') + // TODO(srawlins): Refer to [classToMock] properly, which will yield the + // appropriate import prefix. ..docs.add('/// A class which mocks [$className].') ..docs.add('///') ..docs.add('/// See the documentation for Mockito\'s code generation ' From cdc29676a4b4f09c63e7fc429fce2caf9f7275a7 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 13 Jan 2021 17:51:11 -0500 Subject: [PATCH 273/607] Make casts from dynamic explicit in generated getters. Bump to 5.0.0-nullsafety.3 PiperOrigin-RevId: 351670376 --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/lib/src/builder.dart | 10 ++++++++-- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/builder/auto_mocks_test.dart | 10 +++++----- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 89f3b61f7..8e2af95cf 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.0.0-nullsafety.3 + +* Improve static analysis of generated code. +* Make implicit casts from dynamic in getters explicit. + ## 5.0.0-nullsafety.2 * Fix issue with generated code which references a class declared in a part diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 06d590b96..d0a29674b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1114,10 +1114,16 @@ class _MockLibraryInfo { refer('#${getter.displayName}'), ]); final noSuchMethodArgs = [invocation, _dummyValue(getter.returnType)]; - final returnNoSuchMethod = + var superNoSuchMethod = refer('super').property('noSuchMethod').call(noSuchMethodArgs); + if (!getter.returnType.isVoid && !getter.returnType.isDynamic) { + superNoSuchMethod = + superNoSuchMethod.asA(_typeReference(getter.returnType)); + } - builder.body = returnNoSuchMethod.code; + builder + ..lambda = true + ..body = superNoSuchMethod.code; } /// Build a setter which overrides [setter], widening the single parameter diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 4d45cd972..3edb762bb 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.2 +version: 5.0.0-nullsafety.3 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index c37a1d932..bfc995362 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1385,7 +1385,7 @@ void main() { } '''), _containsAllOf( - 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);'), + 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)'), ); }); @@ -1407,7 +1407,7 @@ void main() { class Foo extends FooBase {} '''), _containsAllOf( - 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);'), + 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)'), ); }); @@ -1453,7 +1453,7 @@ void main() { } '''), _containsAllOf( - 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);', + 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)', 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), ); }); @@ -1467,7 +1467,7 @@ void main() { class Foo extends FooBase {} '''), _containsAllOf( - 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);', + 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)', 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), ); }); @@ -1481,7 +1481,7 @@ void main() { } '''), _containsAllOf( - 'int get m => super.noSuchMethod(Invocation.getter(#m), 0);'), + 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)'), ); }); From 318c179e7b867adb817ac7cfa3ba97f199ab1385 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 13 Jan 2021 18:15:47 -0500 Subject: [PATCH 274/607] Order directives in generated mock libraries. PiperOrigin-RevId: 351675087 --- pkgs/mockito/lib/src/builder.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index d0a29674b..eeb28c882 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -67,16 +67,14 @@ class MockBuilder implements Builder { final mockLibrary = Library((b) { // We don't properly prefix imported class names in doc comments. b.body.add(Code('\n\n// ignore_for_file: comment_references\n\n')); - // code_builder does not sort import directives. - b.body.add(Code('\n\n// ignore_for_file: directives_ordering\n\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. b.body.add(Code('\n\n// ignore_for_file: unnecessary_parenthesis\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); - final emitter = - DartEmitter.scoped(useNullSafetySyntax: sourceLibIsNonNullable); + final emitter = DartEmitter.scoped( + orderDirectives: true, useNullSafetySyntax: sourceLibIsNonNullable); final mockLibraryContent = DartFormatter().format(mockLibrary.accept(emitter).toString()); From a863542cce6c406877320beda9a7179bdbe28369 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 14 Jan 2021 12:29:05 -0800 Subject: [PATCH 275/607] Annotate overridden getters and setters with `@override` (dart-lang/mockito#318) Also bump to 5.0.0-nullsafety.4 PiperOrigin-RevId: 351789062 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/builder.dart | 2 ++ pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 8e2af95cf..fc3f15f65 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.0-nullsafety.4 + +* Annotate overridden getters and setters with `@override`. + ## 5.0.0-nullsafety.3 * Improve static analysis of generated code. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index eeb28c882..5de09bf29 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1105,6 +1105,7 @@ class _MockLibraryInfo { MethodBuilder builder, PropertyAccessorElement getter) { builder ..name = getter.displayName + ..annotations.addAll([refer('override')]) ..type = MethodType.getter ..returns = _typeReference(getter.returnType); @@ -1132,6 +1133,7 @@ class _MockLibraryInfo { MethodBuilder builder, PropertyAccessorElement setter) { builder ..name = setter.displayName + ..annotations.addAll([refer('override')]) ..type = MethodType.setter; final invocationPositionalArgs = []; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 3edb762bb..cf24e01d9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.3 +version: 5.0.0-nullsafety.4 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito From 53cb76f9e8b1e0548fe10470792a08879049ed91 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 22 Jan 2021 10:57:07 -0800 Subject: [PATCH 276/607] GitHub Sync (dart-lang/mockito#320) * Fix setter invocation in generated mocks Unlike method invocation, `Invocation.setter` expects a single argument, not a list of positional arguments. As a result, mock cannot match arguments without this fix, because instead of `null` it gets `[null]`: http://sponge2/bbccac1-5c7a-4762-a43d-4093c48e4916 PiperOrigin-RevId: 352531206 * Bump to 5.0.0-nullsafety.5 PiperOrigin-RevId: 353023446 * Fix mockito codegen when a class extends another class with the same method. PiperOrigin-RevId: 353238314 Co-authored-by: iinozemtsev Co-authored-by: davidmorgan --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/builder.dart | 6 +++--- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/builder/auto_mocks_test.dart | 8 ++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index fc3f15f65..21fcb8c74 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.0-nullsafety.5 + +* Fix `noSuchMethod` invocation of setters in generated mocks. + ## 5.0.0-nullsafety.4 * Annotate overridden getters and setters with `@override`. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 5de09bf29..b08d282ef 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -743,10 +743,10 @@ class _MockLibraryInfo { if (method.isPrivate || method.isStatic) { continue; } - if (overriddenMethods.contains(method)) { + var methodName = method.name; + if (overriddenMethods.contains(methodName)) { continue; } - var methodName = method.name; overriddenMethods.add(methodName); if (methodName == 'noSuchMethod') { continue; @@ -1150,7 +1150,7 @@ class _MockLibraryInfo { final invocation = refer('Invocation').property('setter').call([ refer('#${setter.displayName}'), - literalList(invocationPositionalArgs), + invocationPositionalArgs.single, ]); final returnNoSuchMethod = refer('super').property('noSuchMethod').call([invocation]); diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index cf24e01d9..9d2905846 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.4 +version: 5.0.0-nullsafety.5 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index bfc995362..8cc49fc5b 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1420,7 +1420,7 @@ void main() { expect( mocksContent, contains('set m(int? a) => ' - 'super.noSuchMethod(Invocation.setter(#m, [a]));')); + 'super.noSuchMethod(Invocation.setter(#m, a));')); }); test('does not override nullable instance setters', () async { @@ -1442,7 +1442,7 @@ void main() { expect( mocksContent, contains('set m(int? a) => ' - 'super.noSuchMethod(Invocation.setter(#m, [a]));')); + 'super.noSuchMethod(Invocation.setter(#m, a));')); }); test('overrides non-nullable fields', () async { @@ -1454,7 +1454,7 @@ void main() { '''), _containsAllOf( 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)', - 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), + 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, _m));'), ); }); @@ -1468,7 +1468,7 @@ void main() { '''), _containsAllOf( 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)', - 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, [_m]));'), + 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, _m));'), ); }); From 13b697996ea06deb437dfc287f7cbb6a4274c78d Mon Sep 17 00:00:00 2001 From: Alexander Thomas Date: Tue, 2 Feb 2021 16:49:17 +0100 Subject: [PATCH 277/607] Migrate to GitHub Actions (dart-lang/mockito#328) * Migrate to GitHub Actions * Delete .travis.yml * Use the same less strict analysis options * Replace Travis badge * Remove --coverage * Delete dart_test.yaml --- .../.github/workflows/test-package.yml | 67 +++++++++++++++++ pkgs/mockito/.travis.yml | 71 ------------------- pkgs/mockito/README.md | 2 +- pkgs/mockito/dart_test.yaml | 5 -- 4 files changed, 68 insertions(+), 77 deletions(-) create mode 100644 pkgs/mockito/.github/workflows/test-package.yml delete mode 100644 pkgs/mockito/.travis.yml delete mode 100644 pkgs/mockito/dart_test.yaml diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml new file mode 100644 index 000000000..3706bf0c1 --- /dev/null +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -0,0 +1,67 @@ +name: Dart CI + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze lib + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart run build_runner test -- --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run DDC build + run: dart run build_runner build --fail-on-severe + if: always() && steps.install.outcome == 'success' + - name: Run DDC tests + run: dart run build_runner test -- --platform chrome + if: always() && steps.install.outcome == 'success' diff --git a/pkgs/mockito/.travis.yml b/pkgs/mockito/.travis.yml deleted file mode 100644 index d38251c98..000000000 --- a/pkgs/mockito/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ -language: dart - -# Build stages: https://docs.travis-ci.com/user/build-stages/. -stages: -- presubmit -- build -- testing - -# 1. Run dartfmt, dartanalyzer, pub run test (VM). -# 2. Then run a build. -# 3. Then run tests compiled via dartdevc and dart2js. -jobs: - include: - # mockito tests cannot run on stable until stable is >= 2.10.0, as "2.10.0" - # is the version required by analyzer. See - # https://github.com/dart-lang/build/issues/2685. - #- stage: presubmit - # name: "2.9.0 analyzer" - # script: ./tool/travis.sh dartanalyzer - # dart: 2.9.0 - #- stage: presubmit - # name: "2.9.0 vm test" - # script: ./tool/travis.sh vm_test - # dart: 2.9.0 - #- stage: build - # name: "2.9.0 DDC build" - # script: ./tool/travis.sh dartdevc_build - # dart: 2.9.0 - #- stage: testing - # name: "2.9.0 DDC test" - # script: ./tool/travis.sh dartdevc_test - # dart: 2.9.0 - #- stage: testing - # name: "2.9.0 code coverage" - # script: ./tool/travis.sh coverage - # dart: 2.9.0 - - - stage: presubmit - name: "dev dartfmt" - script: ./tool/travis.sh dartfmt - dart: dev - - stage: presubmit - name: "dev analyzer" - script: ./tool/travis.sh dartanalyzer - dart: dev - - stage: presubmit - name: "dev vm test" - script: ./tool/travis.sh vm_test - dart: dev - - stage: build - name: "dev DDC build" - script: ./tool/travis.sh dartdevc_build - dart: dev - - stage: testing - name: "dev DDC test" - script: ./tool/travis.sh dartdevc_test - dart: dev - - stage: testing - name: "dev code coverage" - script: ./tool/travis.sh coverage - dart: dev - -# Only building master means that we don't run two builds for each pull request. -branches: - only: [master] - -# Incremental pub cache and builds. -cache: - directories: - - $HOME/.pub-cache - - .dart_tool diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a6c8a3809..b068e7d49 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -3,7 +3,7 @@ Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). [![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito) -[![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) +[![Build Status](https://github.com/dart-lang/mockito/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/mockito/actions?query=workflow%3A"Dart+CI"+branch%3Amaster) ## Let's create mocks diff --git a/pkgs/mockito/dart_test.yaml b/pkgs/mockito/dart_test.yaml deleted file mode 100644 index 0a3cb47e0..000000000 --- a/pkgs/mockito/dart_test.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# TODO(https://github.com/dart-lang/test/issues/772): Headless chrome timeout. -override_platforms: - chrome: - settings: - headless: false From 7781b592f4782c2879bb7a0b0a7fbb00e4914d25 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 25 Jan 2021 17:22:00 -0500 Subject: [PATCH 278/607] Fix method parameters with function-reference default values. PiperOrigin-RevId: 353729792 --- .../.github/workflows/test-package.yml | 67 ------------------- pkgs/mockito/README.md | 2 +- pkgs/mockito/dart_test.yaml | 5 ++ pkgs/mockito/lib/src/builder.dart | 28 +++++--- .../mockito/test/builder/auto_mocks_test.dart | 15 +++++ 5 files changed, 38 insertions(+), 79 deletions(-) delete mode 100644 pkgs/mockito/.github/workflows/test-package.yml create mode 100644 pkgs/mockito/dart_test.yaml diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml deleted file mode 100644 index 3706bf0c1..000000000 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Dart CI - -on: - # Run on PRs and pushes to the default branch. - push: - branches: [ master ] - pull_request: - branches: [ master ] - schedule: - - cron: "0 0 * * 0" - -env: - PUB_ENVIRONMENT: bot.github - -jobs: - # Check code formatting and static analysis on a single OS (linux) - # against Dart dev. - analyze: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - sdk: [dev] - steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 - with: - sdk: ${{ matrix.sdk }} - - id: install - name: Install dependencies - run: dart pub get - - name: Check formatting - run: dart format --output=none --set-exit-if-changed . - if: always() && steps.install.outcome == 'success' - - name: Analyze code - run: dart analyze lib - if: always() && steps.install.outcome == 'success' - - # Run tests on a matrix consisting of two dimensions: - # 1. OS: ubuntu-latest, (macos-latest, windows-latest) - # 2. release channel: dev - test: - needs: analyze - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - # Add macos-latest and/or windows-latest if relevant for this package. - os: [ubuntu-latest] - sdk: [dev] - steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 - with: - sdk: ${{ matrix.sdk }} - - id: install - name: Install dependencies - run: dart pub get - - name: Run VM tests - run: dart run build_runner test -- --platform vm - if: always() && steps.install.outcome == 'success' - - name: Run DDC build - run: dart run build_runner build --fail-on-severe - if: always() && steps.install.outcome == 'success' - - name: Run DDC tests - run: dart run build_runner test -- --platform chrome - if: always() && steps.install.outcome == 'success' diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index b068e7d49..a6c8a3809 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -3,7 +3,7 @@ Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). [![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito) -[![Build Status](https://github.com/dart-lang/mockito/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/mockito/actions?query=workflow%3A"Dart+CI"+branch%3Amaster) +[![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) ## Let's create mocks diff --git a/pkgs/mockito/dart_test.yaml b/pkgs/mockito/dart_test.yaml new file mode 100644 index 000000000..0a3cb47e0 --- /dev/null +++ b/pkgs/mockito/dart_test.yaml @@ -0,0 +1,5 @@ +# TODO(https://github.com/dart-lang/test/issues/772): Headless chrome timeout. +override_platforms: + chrome: + settings: + headless: false diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b08d282ef..157e95cbd 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -107,7 +107,7 @@ class MockBuilder implements Builder { addTypesFrom(mockTarget.classType); } - final typeUris = {}; + final typeUris = {}; for (var element in typeVisitor._elements) { if (element.library.isInSdk) { @@ -143,7 +143,7 @@ class MockBuilder implements Builder { /// An [Element] visitor which collects the elements of all of the /// [analyzer.InterfaceType]s which it encounters. class _TypeVisitor extends RecursiveElementVisitor { - final _elements = {}; + final _elements = {}; @override void visitClassElement(ClassElement element) { @@ -220,6 +220,8 @@ class _TypeVisitor extends RecursiveElementVisitor { _addTypesFromConstant(pair.key); _addTypesFromConstant(pair.value); } + } else if (object.toFunctionValue() != null) { + _elements.add(object.toFunctionValue()); } else { // If [constant] is not null, a literal, or a type, then it must be an // object constructed with `const`. Revive it. @@ -1067,10 +1069,15 @@ class _MockLibraryInfo { throw _ReviveException( 'default value has a private type: $privateReference.'); } - if (revivable.source.fragment.isEmpty) { - // We can create this invocation by referring to a const field. - return refer(revivable.accessor, - _typeImport(object.type.element as TypeDefiningElement)); + if (object.toFunctionValue() != null) { + // A top-level function, like `void f() {}` must be referenced by its + // identifier, rather than a revived value. + var element = object.toFunctionValue(); + return refer(revivable.accessor, _typeImport(element)); + } else if (revivable.source.fragment.isEmpty) { + // We can create this invocation by referring to a const field or + // top-level variable. + return refer(revivable.accessor, _typeImport(object.type.element)); } final name = revivable.source.fragment; @@ -1082,8 +1089,7 @@ class _MockLibraryInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final type = - refer(name, _typeImport(object.type.element as TypeDefiningElement)); + final type = refer(name, _typeImport(object.type.element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( revivable.accessor, @@ -1214,7 +1220,7 @@ class _MockLibraryInfo { }); } return TypeReference((b) { - TypeDefiningElement typedef; + Element typedef; if (element is FunctionTypeAliasElement) { typedef = element; } else { @@ -1238,7 +1244,7 @@ class _MockLibraryInfo { } else { return refer( type.getDisplayString(withNullability: false), - _typeImport(type.element as TypeDefiningElement), + _typeImport(type.element), ); } } @@ -1246,7 +1252,7 @@ class _MockLibraryInfo { /// Returns the import URL for [element]. /// /// For some types, like `dynamic` and type variables, this may return null. - String _typeImport(TypeDefiningElement element) { + String _typeImport(Element element) { // For type variables, no import needed. if (element is TypeParameterElement) return null; diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 8cc49fc5b..a29f1aa87 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -424,6 +424,21 @@ void main() { ); }); + test('matches parameter default values constructed with top-level function', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + typedef Callback = void Function(); + void defaultCallback() {} + class Foo { + void m([Callback a = defaultCallback]) {} + } + '''), + _containsAllOf('void m([_i2.Callback? a = _i2.defaultCallback]) =>', + 'super.noSuchMethod(Invocation.method(#m, [a]));'), + ); + }); + test('matches parameter default values constructed with static field', () async { await expectSingleNonNullableOutput( From 0ea3499523dfd667594eca091c6890311ef07e98 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 25 Jan 2021 17:27:01 -0500 Subject: [PATCH 279/607] Add many tests for runtime behavior of generated mocks: * methods of generated mocks can be stubbed * methods of generated mocks can be given argument matchers, like `any` * methods of generated mocks throw if no stub is found * methods of generated mocks use correct default values Also run all tests with sound null safety. PiperOrigin-RevId: 353730873 --- pkgs/mockito/test/end2end/foo.dart | 13 +- .../test/end2end/generated_mocks_test.dart | 133 ++++++++++++++++-- pkgs/mockito/test/mockito_test.dart | 2 +- 3 files changed, 137 insertions(+), 11 deletions(-) diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 8f69d54ba..9e6d08ffc 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -1,5 +1,14 @@ -class Foo { - String m(Bar bar) => 'result'; +class Foo { + String positionalParameter(int x) => 'Real'; + String namedParameter({required int x}) => 'Real'; + String get getter => 'Real'; + int operator +(int arg) => arg + 1; + String parameterWithDefault([int x = 0]) => 'Real'; + String? nullableMethod(int x) => 'Real'; + String? get nullableGetter => 'Real'; + String methodWithBarArg(Bar bar) => 'result'; } +class FooSub extends Foo {} + class Bar {} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 62f16d3ac..942d8b12f 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -7,25 +7,142 @@ import 'generated_mocks_test.mocks.dart'; @GenerateMocks([ Foo, + FooSub, Bar ], customMocks: [ - MockSpec(as: #MockFooReturningNull, returnNullOnMissingStub: true), - MockSpec(as: #MockBarReturningNull, returnNullOnMissingStub: true), + MockSpec(as: #MockFooRelaxed, returnNullOnMissingStub: true), + MockSpec(as: #MockBarRelaxed, returnNullOnMissingStub: true), ]) void main() { + group('for a generated mock,', () { + late MockFoo foo; + late FooSub fooSub; + + setUp(() { + foo = MockFoo(); + fooSub = MockFooSub(); + }); + + test('a method with a positional parameter can be stubbed', () { + when(foo.positionalParameter(42)).thenReturn('Stubbed'); + expect(foo.positionalParameter(42), equals('Stubbed')); + }); + + test('a method with a named parameter can be stubbed', () { + when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); + expect(foo.namedParameter(x: 42), equals('Stubbed')); + }); + + test('a getter can be stubbed', () { + when(foo.getter).thenReturn('Stubbed'); + expect(foo.getter, equals('Stubbed')); + }); + + test('an operator can be stubbed', () { + when(foo + 1).thenReturn(0); + expect(foo + 1, equals(0)); + }); + + test('a method with a parameter with a default value can be stubbed', () { + when(foo.parameterWithDefault(42)).thenReturn('Stubbed'); + expect(foo.parameterWithDefault(42), equals('Stubbed')); + + when(foo.parameterWithDefault()).thenReturn('Default'); + expect(foo.parameterWithDefault(), equals('Default')); + }); + + test('an inherited method can be stubbed', () { + when(fooSub.positionalParameter(42)).thenReturn('Stubbed'); + expect(fooSub.positionalParameter(42), equals('Stubbed')); + }); + + test( + 'a method with a non-nullable positional parameter accepts an argument ' + 'matcher while stubbing', () { + when(foo.positionalParameter(any)).thenReturn('Stubbed'); + expect(foo.positionalParameter(42), equals('Stubbed')); + }); + + test( + 'a method with a non-nullable named parameter accepts an argument ' + 'matcher while stubbing', () { + when(foo.namedParameter(x: anyNamed('x'))).thenReturn('Stubbed'); + expect(foo.namedParameter(x: 42), equals('Stubbed')); + }); + + test( + 'a method with a non-nullable parameter accepts an argument matcher ' + 'while verifying', () { + when(foo.positionalParameter(any)).thenReturn('Stubbed'); + foo.positionalParameter(42); + expect(() => verify(foo.positionalParameter(any)), returnsNormally); + }); + + test('a method with a non-nullable parameter can capture an argument', () { + when(foo.positionalParameter(any)).thenReturn('Stubbed'); + foo.positionalParameter(42); + var captured = verify(foo.positionalParameter(captureAny)).captured; + expect(captured[0], equals(42)); + }); + + test('an unstubbed method throws', () { + when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); + expect( + () => foo.namedParameter(x: 43), + throwsNoSuchMethodError, + ); + }); + + test('an unstubbed getter throws', () { + expect( + () => foo.getter, + throwsNoSuchMethodError, + ); + }); + }); + + group('for a generated mock using returnNullOnMissingStub,', () { + late Foo foo; + + setUp(() { + foo = MockFooRelaxed(); + }); + + test('an unstubbed method returning a non-nullable type throws a TypeError', + () { + when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); + expect( + () => foo.namedParameter(x: 43), throwsA(TypeMatcher())); + }); + + test('an unstubbed getter returning a non-nullable type throws a TypeError', + () { + expect(() => foo.getter, throwsA(TypeMatcher())); + }); + + test('an unstubbed method returning a nullable type returns null', () { + when(foo.nullableMethod(42)).thenReturn('Stubbed'); + expect(foo.nullableMethod(43), isNull); + }); + + test('an unstubbed getter returning a nullable type returns null', () { + expect(foo.nullableGetter, isNull); + }); + }); + test('a generated mock can be used as a stub argument', () { var foo = MockFoo(); var bar = MockBar(); - when(foo.m(bar)).thenReturn('mocked result'); - expect(foo.m(bar), equals('mocked result')); + when(foo.methodWithBarArg(bar)).thenReturn('mocked result'); + expect(foo.methodWithBarArg(bar), equals('mocked result')); }); test( 'a generated mock which returns null on missing stubs can be used as a ' 'stub argument', () { - var foo = MockFooReturningNull(); - var bar = MockBarReturningNull(); - when(foo.m(bar)).thenReturn('mocked result'); - expect(foo.m(bar), equals('mocked result')); + var foo = MockFooRelaxed(); + var bar = MockBarRelaxed(); + when(foo.methodWithBarArg(bar)).thenReturn('mocked result'); + expect(foo.methodWithBarArg(bar), equals('mocked result')); }); } diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 6ad2d7053..76786520f 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -272,7 +272,7 @@ void main() { throwOnMissingStub(mock); when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); expect( - () => (mock).methodWithoutArgs(), + () => mock.methodWithoutArgs(), throwsNoSuchMethodError, ); }); From c7cf175234f58f0d8e1020a643a2845713baa58f Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 25 Jan 2021 18:03:33 -0500 Subject: [PATCH 280/607] Bump to 5.0.0-nullsafety.6 PiperOrigin-RevId: 353738730 --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 21fcb8c74..bf111f6ce 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.0.0-nullsafety.6 + +* Fix generation of method with a parameter with a default value which includes + a top-level function. + ## 5.0.0-nullsafety.5 * Fix `noSuchMethod` invocation of setters in generated mocks. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 9d2905846..b32f51623 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.5 +version: 5.0.0-nullsafety.6 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito From 5ec667e0297c28c74a3c1e0eb8a1295f1a3d43f9 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 28 Jan 2021 09:19:56 -0500 Subject: [PATCH 281/607] Add a header to generated mocks with Mockito version Fixes dart-lang/mockito#322 PiperOrigin-RevId: 354299281 --- pkgs/mockito/lib/src/builder.dart | 17 +++++++++++++---- pkgs/mockito/lib/src/version.dart | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 pkgs/mockito/lib/src/version.dart diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 157e95cbd..e24bd358b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -24,6 +24,7 @@ import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'package:meta/meta.dart'; +import 'package:mockito/src/version.dart'; import 'package:path/path.dart' as p; import 'package:source_gen/source_gen.dart'; @@ -65,18 +66,26 @@ class MockBuilder implements Builder { } final mockLibrary = Library((b) { + // These comments are added after import directives; leading newlines + // are necessary. // We don't properly prefix imported class names in doc comments. - b.body.add(Code('\n\n// ignore_for_file: comment_references\n\n')); + b.body.add(Code('\n\n// ignore_for_file: comment_references\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. - b.body.add(Code('\n\n// ignore_for_file: unnecessary_parenthesis\n\n')); + b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); final emitter = DartEmitter.scoped( orderDirectives: true, useNullSafetySyntax: sourceLibIsNonNullable); - final mockLibraryContent = - DartFormatter().format(mockLibrary.accept(emitter).toString()); + final rawOutput = mockLibrary.accept(emitter).toString(); + final mockLibraryContent = DartFormatter().format(''' +// Mocks generated by Mockito $packageVersion from annotations +// in ${entryLib.definingCompilationUnit.source.uri.path}. +// Do not manually edit this file. + +$rawOutput +'''); await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); } diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart new file mode 100644 index 000000000..fd3e9bd6b --- /dev/null +++ b/pkgs/mockito/lib/src/version.dart @@ -0,0 +1 @@ +const packageVersion = '5.0.0-nullsafety.6'; From f3027b9242d808591e5adf7c8fbb5837b912cf03 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 28 Jan 2021 22:51:55 -0500 Subject: [PATCH 282/607] Fix an error in mockito/example/iss PiperOrigin-RevId: 354451904 --- pkgs/mockito/example/iss/iss.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index bc5c73a98..2185ae7f3 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -41,7 +41,8 @@ class IssLocator { Future _doUpdate() async { // Returns the point on the earth directly under the space station // at this moment. - var rs = await client.get('http://api.open-notify.org/iss-now.json'); + var uri = Uri.parse('http://api.open-notify.org/iss-now.json'); + var rs = await client.get(uri); var data = jsonDecode(rs.body); var latitude = double.parse(data['iss_position']['latitude'] as String); var longitude = double.parse(data['iss_position']['longitude'] as String); From 380991710df941abc525d0d66aff524e27f662b0 Mon Sep 17 00:00:00 2001 From: tijoforyou Date: Fri, 29 Jan 2021 02:51:07 -0500 Subject: [PATCH 283/607] Clean up violations of Dart lint "unnecessary_brace_in_string_interps". AVOID using curly braces in interpolation when not needed. [Effective Dart guideline](http://go/effective-dart/usage#avoid-using-curly-braces-in-interpolation-when-not-needed) [Lint documentation](https://dart-lang.github.io/linter/lints/unnecessary_brace_in_string_interps) [Dart lints discussion](http://g/dart-lints/kJEpoAEIk0k/ZePUtBM1AgAJ) More information: http://go/dart-trivial-lint-cleanup-lsc Tested: TAP --sample ran all affected tests and none failed http://test/OCL:354472163:BASE:354441477:1611902860918:75335a98 PiperOrigin-RevId: 354478324 --- pkgs/mockito/lib/src/builder.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index e24bd358b..e9276de36 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1268,10 +1268,8 @@ class _MockLibraryInfo { // For types like `dynamic`, return null; no import needed. if (element?.library == null) return null; - assert( - assetUris.containsKey(element), - () => - 'An element, "${element}", is missing from the asset URI mapping'); + assert(assetUris.containsKey(element), + () => 'An element, "$element", is missing from the asset URI mapping'); return assetUris[element]; } From 72687d75803d5a13cc9c11cc9b867b92075842c7 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 29 Jan 2021 16:40:17 -0500 Subject: [PATCH 284/607] Migrate the example code to null safety. This required bumping some dev_dependencies to null safe versions. PiperOrigin-RevId: 354601469 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/example/iss/iss.dart | 6 ++---- pkgs/mockito/example/iss/iss_test.dart | 2 -- pkgs/mockito/pubspec.yaml | 4 ++-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index bf111f6ce..ed145ca24 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,7 @@ * Fix generation of method with a parameter with a default value which includes a top-level function. +* Migrate example code to null safety. ## 5.0.0-nullsafety.5 diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index 2185ae7f3..abf647dc8 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - import 'dart:async'; import 'dart:convert'; import 'dart:math'; @@ -24,8 +22,8 @@ import 'package:http/http.dart'; class IssLocator { final Client client; - /*late*/ Point _position; - Future /*?*/ _ongoingRequest; + late Point _position; + Future? _ongoingRequest; IssLocator(this.client); diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index b96d52848..dc4f3cd86 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - import 'dart:math'; import 'package:mockito/mockito.dart'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b32f51623..a96fbcdcd 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -23,7 +23,7 @@ dev_dependencies: build_runner: ^1.0.0 build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' - http: ^0.12.0 - package_config: ^1.9.3 + http: ^0.13.0-nullsafety.0 + package_config: ^2.0.0-nullsafety.0 pedantic: ^1.10.0-nullsafety test: ^1.16.0-nullsafety.12 From 6d3768fa2c289edf63d20d414e17e3f52666e99d Mon Sep 17 00:00:00 2001 From: tijoforyou Date: Mon, 1 Feb 2021 06:11:10 -0500 Subject: [PATCH 285/607] Clean up violations of Dart lint "avoid_single_cascade_in_expression_statements". AVOID single cascade in expression statements. [Lint documentation](https://dart-lang.github.io/linter/lints/avoid_single_cascade_in_expression_statements) [Dart lints discussion](http://g/dart-lints/Pn95rk2Uj28/HTz34tc9BgAJ) More information: http://go/dart-trivial-lint-cleanup-lsc Tested: tap_presubmit: http://test/OCL:354475262:BASE:354467909:1611926611261:809355d6 Sample tests for this CL passed, but some tests failed during the TGP run. Test failures are believed to be unrelated to this CL PiperOrigin-RevId: 354902189 --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index e9276de36..622b1bb98 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -661,7 +661,7 @@ class _MockLibraryInfo { ..docs.add('/// See the documentation for Mockito\'s code generation ' 'for more information.'); if (classIsImmutable) { - cBuilder..docs.add('// ignore: must_be_immutable'); + cBuilder.docs.add('// ignore: must_be_immutable'); } // For each type parameter on [classToMock], the Mock class needs a type // parameter with same type variables, and a mirrored type argument for From ad69d11a9c9d1388481871712bad98780fa3429c Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 1 Feb 2021 14:08:49 -0500 Subject: [PATCH 286/607] Change the error which is thrown for an unstubbed method call. 'NoSuchMethodError' did not really help the user understand what they may have done wrong, or what action they could take. This new Error helps out. Current thrown error and stack trace: 00:15 +0 -3: navigation exits [E] NoSuchMethodError: 'goTo' method not found Receiver: Instance of 'Object' Arguments: [Instance of 'Ad'] dart:sdk_internal noSuchMethod package:mockito/src/mock.dart 149:22 [_noSuchMethod] package:mockito/src/mock.dart 143:45 noSuchMethod navigation_test.mocks.dart 48:13 goTo New error thrown: 00:16 +0 -3: navigation exits [E] MissingStubError: 'goTo' No stub was found which matches the arguments of this method call: goTo(/aw/ads) Add a stub for this method using Mockito's 'when' API, or generate the mock for MockPlaceController with 'returnNullOnMissingStub: true'. dart:sdk_internal throw_ package:mockito/src/mock.dart 149:7 [_noSuchMethod] package:mockito/src/mock.dart 143:45 noSuchMethod navigation_test.mocks.dart 48:13 goTo PiperOrigin-RevId: 354980818 --- pkgs/mockito/CHANGELOG.md | 2 + pkgs/mockito/lib/mockito.dart | 3 +- pkgs/mockito/lib/src/mock.dart | 121 ++++++++++-------- .../test/end2end/generated_mocks_test.dart | 6 +- pkgs/mockito/test/manual_mocks_test.dart | 2 +- pkgs/mockito/test/mockito_test.dart | 2 +- 6 files changed, 81 insertions(+), 55 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ed145ca24..2c26fc232 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,8 @@ * Fix generation of method with a parameter with a default value which includes a top-level function. * Migrate example code to null safety. +* **Breaking change:** Change the error which is thrown if a method is called + and no method stub was found, from NoSuchMethodError to MissingStubError. ## 5.0.0-nullsafety.5 diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 7aa8ecd97..17454b358 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -47,4 +47,5 @@ export 'src/mock.dart' reset, resetMockitoState, logInvocations, - untilCalled; + untilCalled, + MissingStubError; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 00b0cfd3d..45ea812a4 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -146,7 +146,7 @@ class Mock { } dynamic _noSuchMethod(Invocation invocation) => - const Object().noSuchMethod(invocation); + throw MissingStubError(invocation, this); @override int get hashCode => _givenHashCode ?? 0; @@ -176,6 +176,23 @@ class Mock { _realCallsToString(_realCalls.where((call) => !call.verified)); } +/// An error which is thrown when no stub is found which matches the arguments +/// of a real method call on a mock object. +class MissingStubError extends Error { + final Invocation invocation; + final Object receiver; + + MissingStubError(this.invocation, this.receiver); + + @override + String toString() => + "MissingStubError: '${_symbolToString(invocation.memberName)}'\n" + 'No stub was found which matches the arguments of this method call:\n' + '${invocation.toPrettyString()}\n\n' + "Add a stub for this method using Mockito's 'when' API, or generate the " + "mock for ${receiver.runtimeType} with 'returnNullOnMissingStub: true'."; +} + typedef _ReturnsCannedResponse = CallPair Function(); // When using an [ArgMatcher], we transform our invocation to have knowledge of @@ -517,59 +534,14 @@ class RealCall { @override String toString() { - var argString = ''; - var args = invocation.positionalArguments.map((v) => '$v'); - if (args.any((arg) => arg.contains('\n'))) { - // As one or more arg contains newlines, put each on its own line, and - // indent each, for better readability. - argString += '\n' + - args - .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')) - .join(',\n'); - } else { - // A compact String should be perfect. - argString += args.join(', '); - } - if (invocation.namedArguments.isNotEmpty) { - if (argString.isNotEmpty) argString += ', '; - var namedArgs = invocation.namedArguments.keys.map((key) => - '${_symbolToString(key)}: ${invocation.namedArguments[key]}'); - if (namedArgs.any((arg) => arg.contains('\n'))) { - // As one or more arg contains newlines, put each on its own line, and - // indent each, for better readability. - namedArgs = namedArgs - .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')); - argString += '{\n${namedArgs.join(',\n')}}'; - } else { - // A compact String should be perfect. - argString += '{${namedArgs.join(', ')}}'; - } - } - - var method = _symbolToString(invocation.memberName); - if (invocation.isMethod) { - method = '$method($argString)'; - } else if (invocation.isGetter) { - method = '$method'; - } else if (invocation.isSetter) { - method = '$method=$argString'; - } else { - throw StateError('Invocation should be getter, setter or a method call.'); - } - var verifiedText = verified ? '[VERIFIED] ' : ''; - return '$verifiedText$mock.$method'; + return '$verifiedText$mock.${invocation.toPrettyString()}'; } - - // This used to use MirrorSystem, which cleans up the Symbol() wrapper. - // Since this toString method is just used in Mockito's own tests, it's not - // a big deal to massage the toString a bit. - // - // Input: Symbol("someMethodName") - static String _symbolToString(Symbol symbol) => - symbol.toString().split('"')[1]; } +// Converts a [Symbol] to a meaningful [String]. +String _symbolToString(Symbol symbol) => symbol.toString().split('"')[1]; + class _WhenCall { final Mock mock; final Invocation whenInvocation; @@ -1083,3 +1055,52 @@ void resetMockitoState() { _storedArgs.clear(); _storedNamedArgs.clear(); } + +extension on Invocation { + /// Returns a pretty String representing a method (or getter or setter) call + /// including its arguments, separating elements with newlines when it should + /// improve readability. + String toPrettyString() { + String argString; + var args = positionalArguments.map((v) => '$v'); + if (args.any((arg) => arg.contains('\n'))) { + // As one or more arg contains newlines, put each on its own line, and + // indent each, for better readability. + argString = '\n' + + args + .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')) + .join(',\n'); + } else { + // A compact String should be perfect. + argString = args.join(', '); + } + if (namedArguments.isNotEmpty) { + if (argString.isNotEmpty) argString += ', '; + var namedArgs = namedArguments.keys + .map((key) => '${_symbolToString(key)}: ${namedArguments[key]}'); + if (namedArgs.any((arg) => arg.contains('\n'))) { + // As one or more arg contains newlines, put each on its own line, and + // indent each, for better readability. + namedArgs = namedArgs + .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')); + argString += '{\n${namedArgs.join(',\n')}}'; + } else { + // A compact String should be perfect. + argString += '{${namedArgs.join(', ')}}'; + } + } + + var method = _symbolToString(memberName); + if (isMethod) { + method = '$method($argString)'; + } else if (isGetter) { + method = '$method'; + } else if (isSetter) { + method = '$method=$argString'; + } else { + throw StateError('Invocation should be getter, setter or a method call.'); + } + + return method; + } +} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 942d8b12f..c74c31e13 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -89,14 +89,16 @@ void main() { when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); expect( () => foo.namedParameter(x: 43), - throwsNoSuchMethodError, + throwsA(TypeMatcher().having((e) => e.toString(), + 'toString()', contains('namedParameter({x: 43})'))), ); }); test('an unstubbed getter throws', () { expect( () => foo.getter, - throwsNoSuchMethodError, + throwsA(TypeMatcher() + .having((e) => e.toString(), 'toString()', contains('getter'))), ); }); }); diff --git a/pkgs/mockito/test/manual_mocks_test.dart b/pkgs/mockito/test/manual_mocks_test.dart index 110251c3c..5d145bbf4 100644 --- a/pkgs/mockito/test/manual_mocks_test.dart +++ b/pkgs/mockito/test/manual_mocks_test.dart @@ -176,7 +176,7 @@ void main() { 'using `throwOnMissingStub` behavior', () { throwOnMissingStub(mock); expect(() => mock.nonNullableReturn(43), - throwsA(TypeMatcher())); + throwsA(TypeMatcher())); }); }); diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 76786520f..7085cad93 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -273,7 +273,7 @@ void main() { when(mock.methodWithNormalArgs(42)).thenReturn('Ultimate Answer'); expect( () => mock.methodWithoutArgs(), - throwsNoSuchMethodError, + throwsA(TypeMatcher()), ); }); From 6bd3d0bf6388a574ad91c31a131ad338b7a4b948 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 3 Feb 2021 14:17:21 -0500 Subject: [PATCH 287/607] Allow real method calls for unstubbed methods if they return void or Future. This requires a breaking change, changing Mock.noSuchMethod's optional second parameter to two optional named parameters. Any code which calls noSuchMethod with a second positional parameter, or which overrides Mock.noSuchMethod, will need to change. Users have found the throw-if-unstubbed setting to be too constrained for setters, other methods that return void, and methods which return a (non-nullable) Future. The method calls should be allowed, as stubbing is probably meaningless. Fixes https://github.com/dart-lang/mockito/issues/305 PiperOrigin-RevId: 355438518 --- pkgs/mockito/CHANGELOG.md | 6 + pkgs/mockito/lib/src/builder.dart | 29 +- pkgs/mockito/lib/src/mock.dart | 18 +- .../mockito/test/builder/auto_mocks_test.dart | 343 +++++++++++------- pkgs/mockito/test/end2end/foo.dart | 2 + .../test/end2end/generated_mocks_test.dart | 9 + pkgs/mockito/test/manual_mocks_test.dart | 16 +- pkgs/mockito/test/nnbd_support_test.dart | 4 +- 8 files changed, 273 insertions(+), 154 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 2c26fc232..273596978 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -5,6 +5,12 @@ * Migrate example code to null safety. * **Breaking change:** Change the error which is thrown if a method is called and no method stub was found, from NoSuchMethodError to MissingStubError. +* **Breaking change**: `Mock.noSuchMethod`'s optional positional parameter, + "returnValue" is changed to a named parameter, and a second named parameter is + added. Any manual mocks which call `Mock.noSuchMethod` with a second + positional argument will need to instead use the named parameter. +* Allow real calls to mock methods which return `void` (like setters) or + `Future`, even if unstubbed. ## 5.0.0-nullsafety.5 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 622b1bb98..0342bbec1 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -851,13 +851,23 @@ class _MockLibraryInfo { literalList(invocationPositionalArgs), if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs), ]); - final noSuchMethodArgs = [invocation]; - if (_returnTypeIsNonNullable(method)) { - final dummyReturnValue = _dummyValue(method.returnType); - noSuchMethodArgs.add(dummyReturnValue); + + Expression returnValueForMissingStub; + if (method.returnType.isVoid) { + returnValueForMissingStub = refer('null'); + } else if (method.returnType == + typeProvider.futureType2(typeProvider.voidType)) { + returnValueForMissingStub = refer('Future').property('value').call([]); } + final namedArgs = { + if (_returnTypeIsNonNullable(method)) + 'returnValue': _dummyValue(method.returnType), + if (returnValueForMissingStub != null) + 'returnValueForMissingStub': returnValueForMissingStub, + }; + var superNoSuchMethod = - refer('super').property('noSuchMethod').call(noSuchMethodArgs); + refer('super').property('noSuchMethod').call([invocation], namedArgs); if (!method.returnType.isVoid && !method.returnType.isDynamic) { superNoSuchMethod = superNoSuchMethod.asA(_typeReference(method.returnType)); @@ -1127,9 +1137,9 @@ class _MockLibraryInfo { final invocation = refer('Invocation').property('getter').call([ refer('#${getter.displayName}'), ]); - final noSuchMethodArgs = [invocation, _dummyValue(getter.returnType)]; + final namedArgs = {'returnValue': _dummyValue(getter.returnType)}; var superNoSuchMethod = - refer('super').property('noSuchMethod').call(noSuchMethodArgs); + refer('super').property('noSuchMethod').call([invocation], namedArgs); if (!getter.returnType.isVoid && !getter.returnType.isDynamic) { superNoSuchMethod = superNoSuchMethod.asA(_typeReference(getter.returnType)); @@ -1167,8 +1177,9 @@ class _MockLibraryInfo { refer('#${setter.displayName}'), invocationPositionalArgs.single, ]); - final returnNoSuchMethod = - refer('super').property('noSuchMethod').call([invocation]); + final returnNoSuchMethod = refer('super') + .property('noSuchMethod') + .call([invocation], {'returnValueForMissingStub': refer('null')}); builder.body = returnNoSuchMethod.code; } diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 45ea812a4..1a4b3a37b 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -113,6 +113,11 @@ class Mock { _responses.add(cannedResponse); } + /// A sentinal value used as the default argument for noSuchMethod's + /// 'returnValueForMissingStub' parameter. + @protected + static const deferToDefaultResponse = Object(); + /// Handles method stubbing, method call verification, and real method calls. /// /// If passed, [returnValue] will be returned during method stubbing and @@ -121,7 +126,9 @@ class Mock { /// return type. @override @visibleForTesting - dynamic noSuchMethod(Invocation invocation, [Object? returnValue]) { + dynamic noSuchMethod(Invocation invocation, + {Object? returnValue, + Object? returnValueForMissingStub = deferToDefaultResponse}) { // noSuchMethod is that 'magic' that allows us to ignore implementing fields // and methods and instead define them later at compile-time per instance. invocation = _useMatchedInvocationIfSet(invocation); @@ -135,11 +142,18 @@ class Mock { _untilCall = _UntilCall(this, invocation); return returnValue; } else { + _ReturnsCannedResponse defaultResponse; + if (returnValueForMissingStub == deferToDefaultResponse) { + defaultResponse = _defaultResponse; + } else { + defaultResponse = () => + CallPair.allInvocations((_) => returnValueForMissingStub); + } _realCalls.add(RealCall(this, invocation)); _invocationStreamController.add(invocation); var cannedResponse = _responses.lastWhere( (cr) => cr.call.matches(invocation, {}), - orElse: _defaultResponse); + orElse: defaultResponse); var response = cannedResponse.response(invocation); return response; } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index a29f1aa87..7252a42c7 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -202,8 +202,8 @@ void main() { void m(int a) {} } '''), - _containsAllOf('void m(int? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -215,7 +215,7 @@ void main() { } '''), _containsAllOf('void m(int? a, [int? b, int? c = 0]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b, c]));'), + 'super.noSuchMethod(Invocation.method(#m, [a, b, c])'), ); }); @@ -227,7 +227,7 @@ void main() { } '''), _containsAllOf('void m(int? a, {int? b, int? c = 0}) =>', - 'super.noSuchMethod(Invocation.method(#m, [a], {#b: b, #c: c}));'), + 'super.noSuchMethod(Invocation.method(#m, [a], {#b: b, #c: c})'), ); }); @@ -239,7 +239,7 @@ void main() { } '''), _containsAllOf('void m([int? a, int? b = 0]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + 'super.noSuchMethod(Invocation.method(#m, [a, b])'), ); }); @@ -251,7 +251,7 @@ void main() { } '''), _containsAllOf('void m([bool? a = true, bool? b = false]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + 'super.noSuchMethod(Invocation.method(#m, [a, b])'), ); }); @@ -263,7 +263,7 @@ void main() { } '''), _containsAllOf('void m([int? a = 0, double? b = 0.5]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + 'super.noSuchMethod(Invocation.method(#m, [a, b])'), ); }); @@ -276,7 +276,7 @@ void main() { '''), _containsAllOf( "void m([String? a = r'Hello', String? b = r'Hello World']) =>", - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + 'super.noSuchMethod(Invocation.method(#m, [a, b])'), ); }); @@ -289,7 +289,7 @@ void main() { '''), _containsAllOf( 'void m([List? a = const [], Map? b = const {}]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + 'super.noSuchMethod(Invocation.method(#m, [a, b])'), ); }); @@ -301,7 +301,7 @@ void main() { } '''), _containsAllOf('void m([List? a = const [1, 2, 3]]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -314,7 +314,7 @@ void main() { '''), _containsAllOf( "void m([Map? a = const {1: r'a', 2: r'b'}]) =>", - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -327,7 +327,7 @@ void main() { '''), _containsAllOf( "void m([Map? a = const {1: r'a', 2: r'b'}]) =>", - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -343,7 +343,7 @@ void main() { } '''), _containsAllOf('void m([_i2.Bar? a = const _i2.Bar()]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -356,7 +356,7 @@ void main() { } '''), _containsAllOf('void m([Duration? a = const Duration(days: 1)]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -372,7 +372,7 @@ void main() { } '''), _containsAllOf('void m([_i2.Bar? a = const _i2.Bar.named()]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -389,7 +389,7 @@ void main() { } '''), _containsAllOf('void m([_i2.Bar? a = const _i2.Bar(7)]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -406,7 +406,7 @@ void main() { } '''), _containsAllOf('void m([_i2.Bar? a = const _i2.Bar(i: 7)]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -419,8 +419,8 @@ void main() { } const x = 1; '''), - _containsAllOf('void m([int? a = 1]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'void m([int? a = 1]) => super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -435,7 +435,7 @@ void main() { } '''), _containsAllOf('void m([_i2.Callback? a = _i2.defaultCallback]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -448,8 +448,8 @@ void main() { void m([int a = x]) {} } '''), - _containsAllOf('void m([int? a = 1]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'void m([int? a = 1]) => super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -522,8 +522,11 @@ void main() { Future m() async => print(s); } '''), - _containsAllOf('_i3.Future m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), Future.value(null))'), + _containsAllOf(dedent2(''' + _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i3.Future); + ''')), ); }); @@ -534,8 +537,10 @@ void main() { Stream m() async* { yield 7; } } '''), - _containsAllOf('_i3.Stream m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), Stream.empty())'), + _containsAllOf(dedent2(''' + _i3.Stream m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Stream.empty()) as _i3.Stream); + ''')), ); }); @@ -546,8 +551,11 @@ void main() { Iterable m() sync* { yield 7; } } '''), - _containsAllOf('Iterable m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), [])'), + _containsAllOf(dedent2(''' + Iterable m() => + (super.noSuchMethod(Invocation.method(#m, []), returnValue: []) + as Iterable); + ''')), ); }); @@ -559,8 +567,8 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf('void m(int? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -573,8 +581,8 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf('void m(int? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -586,8 +594,8 @@ void main() { } class Foo with Mixin {} '''), - _containsAllOf('void m(int? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -602,8 +610,8 @@ void main() { class FooBase1 extends FooBase2 {} class Foo extends FooBase2 {} '''), - _containsAllOf('void m(int? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'void m(int? a) =>', 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -627,7 +635,7 @@ void main() { class Foo extends FooBase {} '''), _containsAllOf( - 'void m(T? a) =>', 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'void m(T? a) =>', 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -767,8 +775,7 @@ void main() { } '''), _containsAllOf( - 'void m(dynamic a, int? b) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));', + 'void m(dynamic a, int? b) => super.noSuchMethod(Invocation.method(#m, [a, b])', ), ); }); @@ -781,7 +788,7 @@ void main() { } '''), _containsAllOf( - 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a]));', + 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a])', ), ); }); @@ -1024,7 +1031,7 @@ void main() { } '''), _containsAllOf('void m(_i2.Foo Function()? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -1037,7 +1044,7 @@ void main() { } '''), _containsAllOf('void m(void Function(_i2.Foo)? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -1050,7 +1057,7 @@ void main() { } '''), _containsAllOf('void m(_i2.Foo Function()? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + 'super.noSuchMethod(Invocation.method(#m, [a])'), ); }); @@ -1062,7 +1069,7 @@ void main() { } '''), _containsAllOf( - 'void m(int? a, int? b) => super.noSuchMethod(Invocation.method(#m, [a, b]));'), + 'void m(int? a, int? b) => super.noSuchMethod(Invocation.method(#m, [a, b])'), ); }); @@ -1076,7 +1083,7 @@ void main() { } '''), _containsAllOf( - 'void m(int? a, T? b) => super.noSuchMethod(Invocation.method(#m, [a, b]));'), + 'void m(int? a, T? b) => super.noSuchMethod(Invocation.method(#m, [a, b])'), ); }); @@ -1087,8 +1094,7 @@ void main() { void m(List a, List b); } '''), - _containsAllOf('void m(List? a, List? b) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + _containsAllOf('void m(List? a, List? b) =>'), ); }); @@ -1101,8 +1107,7 @@ void main() { void m(int? Function() a, int Function() b); } '''), - _containsAllOf('void m(int? Function()? a, int Function()? b) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + _containsAllOf('void m(int? Function()? a, int Function()? b) =>'), ); }); @@ -1115,8 +1120,8 @@ void main() { void m(void Function(int?) a, void Function(int) b); } '''), - _containsAllOf('void m(void Function(int?)? a, void Function(int)? b) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + _containsAllOf( + 'void m(void Function(int?)? a, void Function(int)? b) =>'), ); }); @@ -1128,8 +1133,7 @@ void main() { void m(int? a(), int b()); } '''), - _containsAllOf('void m(int? Function()? a, int Function()? b) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + _containsAllOf('void m(int? Function()? a, int Function()? b) =>'), ); }); @@ -1142,8 +1146,8 @@ void main() { void m(void a(int? x), void b(int x)); } '''), - _containsAllOf('void m(void Function(int?)? a, void Function(int)? b) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + _containsAllOf( + 'void m(void Function(int?)? a, void Function(int)? b) =>'), ); }); @@ -1154,8 +1158,7 @@ void main() { void m(T? a, T b); } '''), - _containsAllOf( - 'void m(T? a, T? b) => super.noSuchMethod(Invocation.method(#m, [a, b]));'), + _containsAllOf('void m(T? a, T? b) =>'), ); }); @@ -1166,8 +1169,7 @@ void main() { void m(dynamic a, int b); } '''), - _containsAllOf('void m(dynamic a, int? b) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b]));'), + _containsAllOf('void m(dynamic a, int? b) =>'), ); }); @@ -1178,8 +1180,7 @@ void main() { int m(int a); } '''), - _containsAllOf( - 'int m(int? a) => (super.noSuchMethod(Invocation.method(#m, [a]), 0)'), + _containsAllOf('int m(int? a) =>'), ); }); @@ -1190,8 +1191,7 @@ void main() { int? m(int a); } '''), - _containsAllOf( - 'int? m(int? a) => (super.noSuchMethod(Invocation.method(#m, [a]))'), + _containsAllOf('int? m(int? a) =>'), ); }); @@ -1202,8 +1202,7 @@ void main() { List m(int a); } '''), - _containsAllOf('List m(int? a) =>', - '(super.noSuchMethod(Invocation.method(#m, [a]), [])'), + _containsAllOf('List m(int? a) =>'), ); }); @@ -1214,8 +1213,7 @@ void main() { T? m(int a); } '''), - _containsAllOf( - 'T? m(int? a) => (super.noSuchMethod(Invocation.method(#m, [a]))'), + _containsAllOf('T? m(int? a) =>'), ); }); @@ -1358,8 +1356,10 @@ void main() { int m(); } '''), - _containsAllOf( - 'int m() => (super.noSuchMethod(Invocation.method(#m, []), 0)'), + _containsAllOf(dedent2(''' + int m() => + (super.noSuchMethod(Invocation.method(#m, []), returnValue: 0) as int); + ''')), ); }); @@ -1376,7 +1376,7 @@ void main() { }, outputs: { 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a]));'), + 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a])'), }, ); }); @@ -1399,8 +1399,10 @@ void main() { int get m => 7; } '''), - _containsAllOf( - 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)'), + _containsAllOf(dedent2(''' + int get m => + (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + ''')), ); }); @@ -1421,21 +1423,25 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf( - 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)'), + _containsAllOf(dedent2(''' + int get m => + (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + ''')), ); }); test('overrides non-nullable instance setters', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + await expectSingleNonNullableOutput( + dedent(''' class Foo { void set m(int a) {} } - ''')); - expect( - mocksContent, - contains('set m(int? a) => ' - 'super.noSuchMethod(Invocation.setter(#m, a));')); + '''), + _containsAllOf(dedent2(''' + set m(int? a) => super + .noSuchMethod(Invocation.setter(#m, a), returnValueForMissingStub: null); + ''')), + ); }); test('does not override nullable instance setters', () async { @@ -1448,16 +1454,18 @@ void main() { }); test('overrides inherited non-nullable instance setters', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + await expectSingleNonNullableOutput( + dedent(''' class FooBase { void set m(int a) {} } class Foo extends FooBase {} - ''')); - expect( - mocksContent, - contains('set m(int? a) => ' - 'super.noSuchMethod(Invocation.setter(#m, a));')); + '''), + _containsAllOf(dedent2(''' + set m(int? a) => super + .noSuchMethod(Invocation.setter(#m, a), returnValueForMissingStub: null); + ''')), + ); }); test('overrides non-nullable fields', () async { @@ -1467,9 +1475,13 @@ void main() { int m; } '''), - _containsAllOf( - 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)', - 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, _m));'), + _containsAllOf(dedent2(''' + int get m => + (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + '''), dedent2(''' + set m(int? _m) => super + .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); + ''')), ); }); @@ -1481,9 +1493,13 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf( - 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)', - 'set m(int? _m) => super.noSuchMethod(Invocation.setter(#m, _m));'), + _containsAllOf(dedent2(''' + int get m => + (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + '''), dedent2(''' + set m(int? _m) => super + .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); + ''')), ); }); @@ -1495,8 +1511,10 @@ void main() { Foo(this.m); } '''), - _containsAllOf( - 'int get m => (super.noSuchMethod(Invocation.getter(#m), 0)'), + _containsAllOf(dedent2(''' + int get m => + (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + ''')), ); }); @@ -1534,8 +1552,11 @@ void main() { int operator +(Foo other) => 7; } '''), - _containsAllOf('int operator +(_i2.Foo? other) =>', - '(super.noSuchMethod(Invocation.method(#+, [other]), 0)'), + _containsAllOf(dedent2(''' + int operator +(_i2.Foo? other) => + (super.noSuchMethod(Invocation.method(#+, [other]), returnValue: 0) + as int); + ''')), ); }); @@ -1546,8 +1567,10 @@ void main() { int operator [](int x) => 7; } '''), - _containsAllOf('int operator [](int? x) =>', - '(super.noSuchMethod(Invocation.method(#[], [x]), 0)'), + _containsAllOf(dedent2(''' + int operator [](int? x) => + (super.noSuchMethod(Invocation.method(#[], [x]), returnValue: 0) as int); + ''')), ); }); @@ -1558,8 +1581,10 @@ void main() { int operator ~() => 7; } '''), - _containsAllOf( - 'int operator ~() => (super.noSuchMethod(Invocation.method(#~, []), 0)'), + _containsAllOf(dedent2(''' + int operator ~() => + (super.noSuchMethod(Invocation.method(#~, []), returnValue: 0) as int); + ''')), ); }); @@ -1570,8 +1595,10 @@ void main() { bool m() => false; } '''), - _containsAllOf( - 'bool m() => (super.noSuchMethod(Invocation.method(#m, []), false)'), + _containsAllOf(dedent2(''' + bool m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: false) + as bool); + ''')), ); }); @@ -1582,8 +1609,10 @@ void main() { double m() => 3.14; } '''), - _containsAllOf( - 'double m() => (super.noSuchMethod(Invocation.method(#m, []), 0.0)'), + _containsAllOf(dedent2(''' + double m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: 0.0) + as double); + ''')), ); }); @@ -1594,8 +1623,10 @@ void main() { int m() => 7; } '''), - _containsAllOf( - 'int m() => (super.noSuchMethod(Invocation.method(#m, []), 0)'), + _containsAllOf(dedent2(''' + int m() => + (super.noSuchMethod(Invocation.method(#m, []), returnValue: 0) as int); + ''')), ); }); @@ -1606,8 +1637,10 @@ void main() { String m() => "Hello"; } '''), - _containsAllOf( - "String m() => (super.noSuchMethod(Invocation.method(#m, []), '')"), + _containsAllOf(dedent2(''' + String m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: '') + as String); + ''')), ); }); @@ -1618,8 +1651,11 @@ void main() { List m() => [Foo()]; } '''), - _containsAllOf('List<_i2.Foo> m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>[])'), + _containsAllOf(dedent2(''' + List<_i2.Foo> m() => + (super.noSuchMethod(Invocation.method(#m, []), returnValue: <_i2.Foo>[]) + as List<_i2.Foo>); + ''')), ); }); @@ -1630,8 +1666,11 @@ void main() { Set m() => {Foo()}; } '''), - _containsAllOf('Set<_i2.Foo> m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>{})'), + _containsAllOf(dedent2(''' + Set<_i2.Foo> m() => + (super.noSuchMethod(Invocation.method(#m, []), returnValue: <_i2.Foo>{}) + as Set<_i2.Foo>); + ''')), ); }); @@ -1642,8 +1681,10 @@ void main() { Map m() => {7: Foo()}; } '''), - _containsAllOf('Map m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), {})'), + _containsAllOf(dedent2(''' + Map m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: {}) as Map); + ''')), ); }); @@ -1654,8 +1695,10 @@ void main() { Map m(); } '''), - _containsAllOf('Map m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), {})'), + _containsAllOf(dedent2(''' + Map m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: {}) as Map); + ''')), ); }); @@ -1667,8 +1710,10 @@ void main() { Future m() async => false; } '''), - _containsAllOf('_i3.Future m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), Future.value(false))'), + _containsAllOf(dedent2(''' + _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Future.value(false)) as _i3.Future); + ''')), ); }); @@ -1679,8 +1724,10 @@ void main() { Stream m(); } '''), - _containsAllOf('Stream m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), Stream.empty())'), + _containsAllOf(dedent2(''' + _i3.Stream m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Stream.empty()) as _i3.Stream); + ''')), ); }); @@ -1695,8 +1742,11 @@ void main() { Bar(this.name); } '''), - _containsAllOf('_i2.Bar m() =>', - '(super.noSuchMethod(Invocation.method(#m, []), _FakeBar())'), + _containsAllOf(dedent2(''' + _i2.Bar m() => + (super.noSuchMethod(Invocation.method(#m, []), returnValue: _FakeBar()) + as _i2.Bar); + ''')), ); }); @@ -1708,8 +1758,10 @@ void main() { } class Bar {} '''), - _containsAllOf('Bar m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), _FakeBar())'), + _containsAllOf(dedent2(''' + _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: _FakeBar()) as _i2.Bar); + ''')), ); }); @@ -1724,8 +1776,11 @@ void main() { two, } '''), - _containsAllOf('_i2.Bar m1() =>', - '(super.noSuchMethod(Invocation.method(#m1, []), _i2.Bar.one)'), + _containsAllOf(dedent2(''' + _i2.Bar m1() => + (super.noSuchMethod(Invocation.method(#m1, []), returnValue: _i2.Bar.one) + as _i2.Bar); + ''')), ); }); @@ -1738,8 +1793,12 @@ void main() { void Function(int, [String]) m() => (int i, [String s]) {}; } '''), - _containsAllOf('void Function(int, [String]) m() => (super', - '.noSuchMethod(Invocation.method(#m, []), (int __p0, [String __p1]) {})'), + _containsAllOf(dedent2(''' + void Function(int, [String]) m() => (super.noSuchMethod( + Invocation.method(#m, []), + returnValue: (int __p0, [String __p1]) {}) + as void Function(int, [String])); + ''')), ); }); @@ -1752,8 +1811,12 @@ void main() { void Function(Foo, {bool b}) m() => (Foo f, {bool b}) {}; } '''), - _containsAllOf('void Function(_i2.Foo, {bool b}) m() => (super', - '.noSuchMethod(Invocation.method(#m, []), (_i2.Foo __p0, {bool b}) {})'), + _containsAllOf(dedent2(''' + void Function(_i2.Foo, {bool b}) m() => + (super.noSuchMethod(Invocation.method(#m, []), + returnValue: (_i2.Foo __p0, {bool b}) {}) + as void Function(_i2.Foo, {bool b})); + ''')), ); }); @@ -1766,8 +1829,9 @@ void main() { Foo Function() m() => () => Foo(); } '''), - _containsAllOf('_i2.Foo Function() m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), () => _FakeFoo())'), + _containsAllOf( + '_i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []),\n' + ' returnValue: () => _FakeFoo()) as _i2.Foo Function());'), ); }); @@ -1781,8 +1845,10 @@ void main() { '''), // TODO(srawlins): This output is invalid: `T __p0` is out of the scope // where T is defined. - _containsAllOf('T? Function(T) m() =>', - 'super.noSuchMethod(Invocation.method(#m, []), (T __p0) => null)'), + _containsAllOf(dedent2(''' + T? Function(T) m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: (T __p0) => null) as T? Function(T)); + ''')), ); }); @@ -2340,3 +2406,12 @@ String dedent(String input) { return input.splitMapJoin('\n', onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), '')); } + +/// Dedent [input], so that each line is shifted to the left, so that the first +/// line is at column 2 (starting position for a class member). +String dedent2(String input) { + final indentMatch = RegExp(r'^ (\s*)').firstMatch(input); + final indent = ''.padRight(indentMatch.group(1).length); + return input.replaceFirst(RegExp(r'\s*$'), '').splitMapJoin('\n', + onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), '')); +} diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 9e6d08ffc..8dcf99dc8 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -7,6 +7,8 @@ class Foo { String? nullableMethod(int x) => 'Real'; String? get nullableGetter => 'Real'; String methodWithBarArg(Bar bar) => 'result'; + set setter(int value) {} + Future returnsFutureVoid() => Future.value(); } class FooSub extends Foo {} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index c74c31e13..716806ac6 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -56,6 +56,15 @@ void main() { expect(fooSub.positionalParameter(42), equals('Stubbed')); }); + test('a setter can be called without stubbing', () { + expect(() => foo.setter = 7, returnsNormally); + }); + + test('a method which returns Future can be called without stubbing', + () { + expect(() => foo.returnsFutureVoid(), returnsNormally); + }); + test( 'a method with a non-nullable positional parameter accepts an argument ' 'matcher while stubbing', () { diff --git a/pkgs/mockito/test/manual_mocks_test.dart b/pkgs/mockito/test/manual_mocks_test.dart index 5d145bbf4..87e6855ac 100644 --- a/pkgs/mockito/test/manual_mocks_test.dart +++ b/pkgs/mockito/test/manual_mocks_test.dart @@ -54,7 +54,8 @@ class MockedClass extends Mock implements RealClass { @override int nonNullableReturn(int? x) => - super.noSuchMethod(Invocation.method(#nonNullableReturn, [x]), 1) as int; + super.noSuchMethod(Invocation.method(#nonNullableReturn, [x]), + returnValue: 1) as int; // A generic return type is very tricky to work with in a manually mocked // method. What value can be passed as the second argument to @@ -63,16 +64,17 @@ class MockedClass extends Mock implements RealClass { // is optional, so that the override is still legal. @override T nonNullableReturn2(T? x, {T? sentinal}) => - super.noSuchMethod(Invocation.method(#nonNullableReturn2, [x]), sentinal!) - as T; + super.noSuchMethod(Invocation.method(#nonNullableReturn2, [x]), + returnValue: sentinal!) as T; @override - Future nonNullableFutureReturn(int? x) => super.noSuchMethod( - Invocation.method(#nonNullableFutureReturn, [x]), Future.value(1)) - as Future; + Future nonNullableFutureReturn(int? x) => + super.noSuchMethod(Invocation.method(#nonNullableFutureReturn, [x]), + returnValue: Future.value(1)) as Future; @override - int get getter => super.noSuchMethod(Invocation.getter(#getter), 1) as int; + int get getter => + super.noSuchMethod(Invocation.getter(#getter), returnValue: 1) as int; } void main() { diff --git a/pkgs/mockito/test/nnbd_support_test.dart b/pkgs/mockito/test/nnbd_support_test.dart index 9e31129fc..dc0724a45 100644 --- a/pkgs/mockito/test/nnbd_support_test.dart +++ b/pkgs/mockito/test/nnbd_support_test.dart @@ -26,8 +26,8 @@ class Foo { class MockFoo extends Mock implements Foo { @override String returnsNonNullableString() { - return super.noSuchMethod( - Invocation.method(#returnsNonNullableString, []), 'Dummy') as String; + return super.noSuchMethod(Invocation.method(#returnsNonNullableString, []), + returnValue: 'Dummy') as String; } } From 6c549b358663d3caf755b8f86fb2885b1c52394d Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 3 Feb 2021 14:27:46 -0500 Subject: [PATCH 288/607] Expand constraint on package_config dependency. analyzer is not ready yet for null safe package_config. PiperOrigin-RevId: 355441180 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index a96fbcdcd..14d4c0997 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -24,6 +24,6 @@ dev_dependencies: build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' http: ^0.13.0-nullsafety.0 - package_config: ^2.0.0-nullsafety.0 + package_config: '>=1.9.3 <3.0.0' pedantic: ^1.10.0-nullsafety test: ^1.16.0-nullsafety.12 From f98dc59a6fb414bd6c686c0168d7d8784321c5fe Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 4 Feb 2021 09:31:06 -0800 Subject: [PATCH 289/607] Put back GitHub actions which were inadvertently reverted --- .../.github/workflows/test-package.yml | 67 +++++++++++++++++++ pkgs/mockito/dart_test.yaml | 5 -- 2 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 pkgs/mockito/.github/workflows/test-package.yml delete mode 100644 pkgs/mockito/dart_test.yaml diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml new file mode 100644 index 000000000..3706bf0c1 --- /dev/null +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -0,0 +1,67 @@ +name: Dart CI + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze lib + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart run build_runner test -- --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run DDC build + run: dart run build_runner build --fail-on-severe + if: always() && steps.install.outcome == 'success' + - name: Run DDC tests + run: dart run build_runner test -- --platform chrome + if: always() && steps.install.outcome == 'success' diff --git a/pkgs/mockito/dart_test.yaml b/pkgs/mockito/dart_test.yaml deleted file mode 100644 index 0a3cb47e0..000000000 --- a/pkgs/mockito/dart_test.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# TODO(https://github.com/dart-lang/test/issues/772): Headless chrome timeout. -override_platforms: - chrome: - settings: - headless: false From 4c8eafab40ca0f402ad8039437d45311f644fd91 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 2 Feb 2021 19:32:02 -0800 Subject: [PATCH 290/607] Add a dartdoc verify action in GitHub Actions --- .../.github/workflows/test-package.yml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 3706bf0c1..54c23eb55 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -45,7 +45,6 @@ jobs: strategy: fail-fast: false matrix: - # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] sdk: [dev] steps: @@ -65,3 +64,24 @@ jobs: - name: Run DDC tests run: dart run build_runner test -- --platform chrome if: always() && steps.install.outcome == 'success' + document: + needs: analyze + runs-on: dev + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: dev + - id: install + name: Install dependencies + run: | + dart pub get + dart pub global install dartdoc + - name: Verify dartdoc + run: dart pub global run dartdoc \ + --no-generate-docs \ + --errors=unresolved-doc-reference,ambiguous-doc-reference,ambiguous-reexport,broken-link,deprecated,no-library-level-docs,unknown-directive,unknown-macro From 8ba2d54ae920647562b7771fcd56313a8e571fe3 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 2 Feb 2021 21:40:47 -0800 Subject: [PATCH 291/607] s/install/activate/ --- pkgs/mockito/.github/workflows/test-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 54c23eb55..af649352e 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -80,7 +80,7 @@ jobs: name: Install dependencies run: | dart pub get - dart pub global install dartdoc + dart pub global activate dartdoc - name: Verify dartdoc run: dart pub global run dartdoc \ --no-generate-docs \ From e185ea0032fb66156aa2969f7a7597f082987ec6 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 2 Feb 2021 22:49:41 -0800 Subject: [PATCH 292/607] remove escaped newlines --- pkgs/mockito/.github/workflows/test-package.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index af649352e..57873b57c 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -82,6 +82,4 @@ jobs: dart pub get dart pub global activate dartdoc - name: Verify dartdoc - run: dart pub global run dartdoc \ - --no-generate-docs \ - --errors=unresolved-doc-reference,ambiguous-doc-reference,ambiguous-reexport,broken-link,deprecated,no-library-level-docs,unknown-directive,unknown-macro + run: dart pub global run dartdoc --no-generate-docs --errors=unresolved-doc-reference,ambiguous-doc-reference,ambiguous-reexport,broken-link,deprecated,no-library-level-docs,unknown-directive,unknown-macro From cb311ce8cc1afb81c544c2f3274a3c1eec56d435 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 2 Feb 2021 23:08:52 -0800 Subject: [PATCH 293/607] Add 'if' entry --- pkgs/mockito/.github/workflows/test-package.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 57873b57c..241bab283 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -82,4 +82,7 @@ jobs: dart pub get dart pub global activate dartdoc - name: Verify dartdoc - run: dart pub global run dartdoc --no-generate-docs --errors=unresolved-doc-reference,ambiguous-doc-reference,ambiguous-reexport,broken-link,deprecated,no-library-level-docs,unknown-directive,unknown-macro + run: dart pub global run dartdoc \ + --no-generate-docs \ + --errors=unresolved-doc-reference,ambiguous-doc-reference,ambiguous-reexport,broken-link,deprecated,no-library-level-docs,unknown-directive,unknown-macro + if: always() && steps.install.outcome == 'success' From 902d46cacdc37bf8da2c0bd7cb5a2a39258a49d0 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 2 Feb 2021 23:15:39 -0800 Subject: [PATCH 294/607] matrix sdk --- pkgs/mockito/.github/workflows/test-package.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 241bab283..647a4b6b4 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -71,11 +71,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] + sdk: [dev] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v0.3 with: - sdk: dev + sdk: ${{ matrix.sdk }} - id: install name: Install dependencies run: | From 2e7ea613763e0a2fe012fdb07035b36fe17ee398 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 2 Feb 2021 23:34:40 -0800 Subject: [PATCH 295/607] fix runs-on --- pkgs/mockito/.github/workflows/test-package.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 647a4b6b4..b7b644ff6 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -66,17 +66,16 @@ jobs: if: always() && steps.install.outcome == 'success' document: needs: analyze - runs-on: dev + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [dev] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v0.3 with: - sdk: ${{ matrix.sdk }} + sdk: dev - id: install name: Install dependencies run: | From ebc0cf7f33ffdb034cf62a4f47aea6da3b9de8ac Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 4 Feb 2021 10:48:28 -0500 Subject: [PATCH 296/607] Fix generation of duplicate mock getters and setters from inherited classes. Fixes https://github.com/dart-lang/mockito/issues/325 PiperOrigin-RevId: 355624817 --- pkgs/mockito/CHANGELOG.md | 4 ++ pkgs/mockito/lib/src/builder.dart | 3 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 55 +++++++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 273596978..3d8a40fc0 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.0-nullsafety.7 + +* Fix generation of duplicate mock getters and setters from inherited classes. + ## 5.0.0-nullsafety.6 * Fix generation of method with a parameter with a default value which includes diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0342bbec1..b0fa8ade3 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -719,9 +719,10 @@ class _MockLibraryInfo { if (accessor.isPrivate || accessor.isStatic) { continue; } - if (overriddenFields.contains(accessor)) { + if (overriddenFields.contains(accessor.name)) { continue; } + overriddenFields.add(accessor.name); if (accessor.isGetter != null && _returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index fd3e9bd6b..f804bd993 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.0-nullsafety.6'; +const packageVersion = '5.0.0-nullsafety.7'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 14d4c0997..a8baecec2 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.6 +version: 5.0.0-nullsafety.7 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 7252a42c7..df4e8ccd2 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1363,6 +1363,19 @@ void main() { ); }); + test('overrides inherited methods with a non-nullable return type', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + class FooBase { + num m() => 7; + } + class Foo extends FooBase { + int m() => 7; + } + ''')); + expect(mocksContent, contains('int m()')); + expect(mocksContent, isNot(contains('num m()'))); + }); + test('overrides methods with a potentially non-nullable parameter', () async { await testWithNonNullable( { @@ -1430,6 +1443,19 @@ void main() { ); }); + test('overrides inherited instance getters only once', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + class FooBase { + num get m => 7; + } + class Foo extends FooBase { + int get m => 7; + } + ''')); + expect(mocksContent, contains('int get m')); + expect(mocksContent, isNot(contains('num get m'))); + }); + test('overrides non-nullable instance setters', () async { await expectSingleNonNullableOutput( dedent(''' @@ -1468,6 +1494,19 @@ void main() { ); }); + test('overrides inherited non-nullable instance setters only once', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + class FooBase { + set m(int a) {} + } + class Foo extends FooBase { + set m(num a) {} + } + ''')); + expect(mocksContent, contains('set m(num? a)')); + expect(mocksContent, isNot(contains('set m(int? a)'))); + }); + test('overrides non-nullable fields', () async { await expectSingleNonNullableOutput( dedent(r''' @@ -1503,6 +1542,22 @@ void main() { ); }); + test('overrides inherited non-nullable fields only once', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + class FooBase { + num m; + } + class Foo extends FooBase { + int get m => 7; + void set m(covariant int value) {} + } + ''')); + expect(mocksContent, contains('int get m')); + expect(mocksContent, contains('set m(int? value)')); + expect(mocksContent, isNot(contains('num get m'))); + expect(mocksContent, isNot(contains('set m(num? value)'))); + }); + test('overrides final non-nullable fields', () async { await expectSingleNonNullableOutput( dedent(r''' From e07b6cdd4448c7e71f6c1dc25bbd516e96c67e38 Mon Sep 17 00:00:00 2001 From: alyssoncs Date: Fri, 29 Jan 2021 10:05:03 -0300 Subject: [PATCH 297/607] fix typo on verifyInOrder documentation There was a missing backtick on verifyInOrder documentation --- pkgs/mockito/lib/src/mock.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 1a4b3a37b..772ad9182 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -914,7 +914,7 @@ Verification _makeVerify(bool never) { /// verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood(any)]); /// ``` /// -/// This verifies that `eatFood` was called with `"Milk"`, sound` was called +/// This verifies that `eatFood` was called with `"Milk"`, `sound` was called /// with no arguments, and `eatFood` was then called with some argument. /// /// Note: [verifyInOrder] only verifies that each call was made in the order From c5063e748526713080a709667d9db70645682228 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 4 Feb 2021 12:15:08 -0500 Subject: [PATCH 298/607] Edit copybara config to support GitHub Actions file. Delete old travis configurations. PiperOrigin-RevId: 355640942 --- pkgs/mockito/lib/src/mock.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 772ad9182..1a4b3a37b 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -914,7 +914,7 @@ Verification _makeVerify(bool never) { /// verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood(any)]); /// ``` /// -/// This verifies that `eatFood` was called with `"Milk"`, `sound` was called +/// This verifies that `eatFood` was called with `"Milk"`, sound` was called /// with no arguments, and `eatFood` was then called with some argument. /// /// Note: [verifyInOrder] only verifies that each call was made in the order From a840cbdb7ad8bd8b971fba2b9892cd9a720c9307 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Fri, 5 Feb 2021 12:11:54 -0500 Subject: [PATCH 299/607] Use DartType.aliasElement instead of 'element'. PiperOrigin-RevId: 355862739 --- pkgs/mockito/lib/src/builder.dart | 54 +++++++++++++------------------ 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b0fa8ade3..d2de0b03c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -195,13 +195,9 @@ class _TypeVisitor extends RecursiveElementVisitor { (type.typeArguments ?? []).forEach(_addType); } else if (type is analyzer.FunctionType) { _addType(type.returnType); - var element = type.element; - if (element != null) { - if (element is FunctionTypeAliasElement) { - _elements.add(element); - } else { - _elements.add(element.enclosingElement as FunctionTypeAliasElement); - } + var aliasElement = type.aliasElement; + if (aliasElement != null) { + _elements.add(aliasElement); } } } @@ -355,8 +351,8 @@ class _MockTargetGatherer { /// [InvalidMockitoAnnotationException] under various conditions. static analyzer.InterfaceType _determineDartType( analyzer.DartType typeToMock, TypeProvider typeProvider) { - final elementToMock = typeToMock.element; - if (elementToMock is ClassElement) { + if (typeToMock is analyzer.InterfaceType) { + final elementToMock = typeToMock.element; if (elementToMock.isEnum) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock an enum: ${elementToMock.displayName}'); @@ -382,17 +378,16 @@ class _MockTargetGatherer { "'${elementToMock.displayName}' for the following reasons:\n" '$joinedMessages'); } - return typeToMock as analyzer.InterfaceType; - } else if (elementToMock is FunctionTypeAliasElement) { - throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: ' - '${elementToMock.displayName}'); - } else if (elementToMock is GenericFunctionTypeElement && - elementToMock.enclosingElement is FunctionTypeAliasElement) { + return typeToMock; + } + + var aliasElement = typeToMock.aliasElement; + if (aliasElement != null) { throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: ' - '${elementToMock.enclosingElement.displayName}'); + '${aliasElement.displayName}'); } else { throw InvalidMockitoAnnotationException( - 'Mockito cannot mock a non-class: ${elementToMock.displayName}'); + 'Mockito cannot mock a non-class: $typeToMock'); } } @@ -499,8 +494,8 @@ class _MockTargetGatherer { for (var parameter in function.parameters) { var parameterType = parameter.type; - var parameterTypeElement = parameterType.element; if (parameterType is analyzer.InterfaceType) { + var parameterTypeElement = parameterType.element; if (parameterTypeElement?.isPrivate ?? false) { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not @@ -518,8 +513,12 @@ class _MockTargetGatherer { errorMessages .addAll(_checkTypeParameters(function.typeFormals, enclosingElement)); - errorMessages - .addAll(_checkTypeArguments(function.typeArguments, enclosingElement)); + + var aliasArguments = function.aliasArguments; + if (aliasArguments != null) { + errorMessages + .addAll(_checkTypeArguments(aliasArguments, enclosingElement)); + } return errorMessages; } @@ -1220,7 +1219,7 @@ class _MockLibraryInfo { ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { - var element = type.element; + var element = type.aliasElement; if (element == null) { // [type] represents a FunctionTypedFormalParameter. return FunctionType((b) { @@ -1241,18 +1240,11 @@ class _MockLibraryInfo { }); } return TypeReference((b) { - Element typedef; - if (element is FunctionTypeAliasElement) { - typedef = element; - } else { - typedef = element.enclosingElement as FunctionTypeAliasElement; - } - b - ..symbol = typedef.name - ..url = _typeImport(typedef) + ..symbol = element.name + ..url = _typeImport(element) ..isNullable = forceNullable || typeSystem.isNullable(type); - for (var typeArgument in type.typeArguments) { + for (var typeArgument in type.aliasArguments) { b.types.add(_typeReference(typeArgument)); } }); From 4bab43995980adeae2da70bc5550fbd7c0e43e90 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 10 Feb 2021 13:29:23 -0500 Subject: [PATCH 300/607] New feature: return a `List` from verifyInOrder, which stores arguments which were captured in a call to `verifiyInOrder`. Also bumps mockito to 5.0.0. Tested=https://test.corp.google.com/OCL:321942363:BASE:356660177:1612935112848:36db91d9 PiperOrigin-RevId: 356768094 --- pkgs/mockito/CHANGELOG.md | 6 ++ pkgs/mockito/lib/mockito.dart | 1 + pkgs/mockito/lib/src/mock.dart | 133 ++++++++++++++++++++++------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/verify_test.dart | 15 ++++ 6 files changed, 128 insertions(+), 31 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 3d8a40fc0..139cc638f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.0 + +* `verifyInOrder` now returns a `List` which stores + arguments which were captured in a call to `verifiyInOrder`. +* **Breaking change:** Remove the public constructor for `VerificationResult`. + ## 5.0.0-nullsafety.7 * Fix generation of duplicate mock getters and setters from inherited classes. diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 17454b358..59ec2a2a0 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -40,6 +40,7 @@ export 'src/mock.dart' verifyZeroInteractions, VerificationResult, Verification, + ListOfVerificationResult, // -- misc throwOnMissingStub, diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 1a4b3a37b..115bc8b5b 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -26,9 +26,35 @@ import 'package:test_api/test_api.dart'; // https://github.com/dart-lang/mockito/issues/175 import 'package:test_api/src/backend/invoker.dart'; +/// Whether a [when] call is "in progress." +/// +/// Since [when] is a getter, this is `true` immediately after [when] returns, +/// `false` immediately after the closure which [when] returns has returned, and +/// `false` otherwise. For example: +/// +/// ```none +/// ,--- (1) Before [when] is called, [_whenInProgress] is `false`. +/// | ,--- (2) The [when] getter sets [_whenInProgress] to `true`, so that it +/// | | is true immediately after [when] returns. +/// | | ,--- (3) The argument given to [when]'s return closure is computed +/// | | | before entering the closure, so [_whenInProgress] is still +/// | | | `true`. +/// | | | ,--- (4) The closure resets [_whenInProgress] to `false`. +/// v v v v +/// when(foo.bar()).thenReturn(7); +/// ``` bool _whenInProgress = false; + +/// Whether an [untilCalled] call is "in progress." +/// +/// This follows similar logic to [_whenInProgress]; see its comment. bool _untilCalledInProgress = false; + +/// Whether a [verify], [verifyNever], or [verifyInOrder] call is "in progress." +/// +/// This follows similar logic to [_whenInProgress]; see its comment. bool _verificationInProgress = false; + _WhenCall? _whenCall; _UntilCall? _untilCall; final List<_VerifyCall> _verifyCalls = <_VerifyCall>[]; @@ -588,22 +614,49 @@ class _UntilCall { } } +/// A simple struct for storing a [RealCall] and any [capturedArgs] stored +/// during [InvocationMatcher.match]. +class _RealCallWithCapturedArgs { + final RealCall realCall; + final List capturedArgs; + + _RealCallWithCapturedArgs(this.realCall, this.capturedArgs); +} + class _VerifyCall { final Mock mock; final Invocation verifyInvocation; - late List matchingInvocations; + final List<_RealCallWithCapturedArgs> matchingInvocations; + final List matchingCapturedArgs; - _VerifyCall(this.mock, this.verifyInvocation) { + factory _VerifyCall(Mock mock, Invocation verifyInvocation) { var expectedMatcher = InvocationMatcher(verifyInvocation); - matchingInvocations = mock._realCalls.where((RealCall recordedInvocation) { - return !recordedInvocation.verified && - expectedMatcher.matches(recordedInvocation.invocation); - }).toList(); + var matchingInvocations = <_RealCallWithCapturedArgs>[]; + for (var realCall in mock._realCalls) { + if (!realCall.verified && expectedMatcher.matches(realCall.invocation)) { + // [Invocation.matcher] collects captured arguments if + // [verifyInvocation] included capturing matchers. + matchingInvocations + .add(_RealCallWithCapturedArgs(realCall, [..._capturedArgs])); + _capturedArgs.clear(); + } + } + + var matchingCapturedArgs = [ + for (var invocation in matchingInvocations) ...invocation.capturedArgs, + ]; + + return _VerifyCall._( + mock, verifyInvocation, matchingInvocations, matchingCapturedArgs); } - RealCall _findAfter(DateTime dt) { - return matchingInvocations - .firstWhere((inv) => !inv.verified && inv.timeStamp.isAfter(dt)); + _VerifyCall._(this.mock, this.verifyInvocation, this.matchingInvocations, + this.matchingCapturedArgs); + + _RealCallWithCapturedArgs _findAfter(DateTime time) { + return matchingInvocations.firstWhere((invocation) => + !invocation.realCall.verified && + invocation.realCall.timeStamp.isAfter(time)); } void _checkWith(bool never) { @@ -623,9 +676,9 @@ class _VerifyCall { var calls = mock._unverifiedCallsToString(); fail('Unexpected calls: $calls'); } - matchingInvocations.forEach((inv) { - inv.verified = true; - }); + for (var invocation in matchingInvocations) { + invocation.realCall.verified = true; + } } @override @@ -765,15 +818,7 @@ class VerificationResult { bool _testApiMismatchHasBeenChecked = false; - @Deprecated( - 'User-constructed VerificationResult is deprecated; this constructor may ' - 'be deleted as early as Mockito 5.0.0') - VerificationResult(int callCount) : this._(callCount); - - VerificationResult._(this.callCount) - : _captured = List.from(_capturedArgs, growable: false) { - _capturedArgs.clear(); - } + VerificationResult._(this.callCount, this._captured); /// Check for a version incompatibility between mockito, test, and test_api. /// @@ -835,7 +880,8 @@ typedef Answering = T Function(Invocation realInvocation); typedef Verification = VerificationResult Function(T matchingInvocations); -typedef _InOrderVerification = void Function(List recordedInvocations); +typedef _InOrderVerification = List Function( + List recordedInvocations); /// Verify that a method on a mock object was never called with the given /// arguments. @@ -898,7 +944,8 @@ Verification _makeVerify(bool never) { _verificationInProgress = false; if (_verifyCalls.length == 1) { var verifyCall = _verifyCalls.removeLast(); - var result = VerificationResult._(verifyCall.matchingInvocations.length); + var result = VerificationResult._(verifyCall.matchingInvocations.length, + verifyCall.matchingCapturedArgs); verifyCall._checkWith(never); return result; } else { @@ -907,16 +954,35 @@ Verification _makeVerify(bool never) { }; } -/// Verify that a list of methods on a mock object have been called with the +/// Verifies that a list of methods on a mock object have been called with the /// given arguments. For example: /// /// ```dart /// verifyInOrder([cat.eatFood("Milk"), cat.sound(), cat.eatFood(any)]); /// ``` /// -/// This verifies that `eatFood` was called with `"Milk"`, sound` was called +/// This verifies that `eatFood` was called with `"Milk"`, `sound` was called /// with no arguments, and `eatFood` was then called with some argument. /// +/// Returns a list of verification results, one for each call which was +/// verified. +/// +/// For example, if [verifyInOrder] is given these calls to verify: +/// +/// ```dart +/// var verification = verifyInOrder( +/// [cat.eatFood(captureAny), cat.chew(), cat.eatFood(captureAny)]); +/// ``` +/// +/// then `verification` is a list which contains a `captured` getter which +/// returns three lists: +/// +/// 1. a list containing the argument passed to `eatFood` in the first +/// verified `eatFood` call, +/// 2. an empty list, as nothing was captured in the verified `chew` call, +/// 3. a list containing the argument passed to `eatFood` in the second +/// verified `eatFood` call. +/// /// Note: [verifyInOrder] only verifies that each call was made in the order /// given, but not that those were the only calls. In the example above, if /// other calls were made to `eatFood` or `sound` between the three given @@ -928,15 +994,17 @@ _InOrderVerification get verifyInOrder { _verificationInProgress = true; return (List _) { _verificationInProgress = false; - var dt = DateTime.fromMillisecondsSinceEpoch(0); + var verificationResults = []; + var time = DateTime.fromMillisecondsSinceEpoch(0); var tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); _verifyCalls.clear(); var matchedCalls = []; for (var verifyCall in tmpVerifyCalls) { try { - var matched = verifyCall._findAfter(dt); - matchedCalls.add(matched); - dt = matched.timeStamp; + var matched = verifyCall._findAfter(time); + matchedCalls.add(matched.realCall); + verificationResults.add(VerificationResult._(1, matched.capturedArgs)); + time = matched.realCall.timeStamp; } on StateError { var mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); var allInvocations = @@ -954,6 +1022,7 @@ _InOrderVerification get verifyInOrder { for (var call in matchedCalls) { call.verified = true; } + return verificationResults; }; } @@ -1118,3 +1187,9 @@ extension on Invocation { return method; } } + +extension ListOfVerificationResult on List { + /// Returns the list of argument lists which were captured within + /// [verifyInOrder]. + List> get captured => [...map((result) => result.captured)]; +} diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index f804bd993..92cae6b69 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.0-nullsafety.7'; +const packageVersion = '5.0.0'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index a8baecec2..0c1968723 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0-nullsafety.7 +version: 5.0.0 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index b4425180b..c4e001844 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -474,6 +474,21 @@ void main() { [mock.methodWithoutArgs(), mock.getter, mock.methodWithoutArgs()]); }); + test('can return captures from capturing argument matchers', () { + mock.methodWithNormalArgs(1); + mock.methodWithoutArgs(); + mock.methodWithNormalArgs(2); + var captured = verifyInOrder([ + mock.methodWithNormalArgs(captureAny), + mock.methodWithoutArgs(), + mock.methodWithNormalArgs(captureAny) + ]).captured; + expect(captured, hasLength(3)); + expect(captured[0], equals([1])); + expect(captured[1], equals([])); + expect(captured[2], equals([2])); + }); + test('methods can be called again and again - fail case', () { mock.methodWithoutArgs(); mock.methodWithoutArgs(); From b83b1bd5b6fbeb933336e142c4bab1a93ba8d642 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 10 Feb 2021 17:14:31 -0500 Subject: [PATCH 301/607] Allow analyzer ^1.0.0 in mockito. Override dependency versions during local development and CI for correct test results. PiperOrigin-RevId: 356824157 --- pkgs/mockito/pubspec.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 0c1968723..890b75a0c 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=0.39.15 <0.42.0' + analyzer: '>=0.39.15 <2.0.0' build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.15.0-nullsafety.5 @@ -27,3 +27,10 @@ dev_dependencies: package_config: '>=1.9.3 <3.0.0' pedantic: ^1.10.0-nullsafety test: ^1.16.0-nullsafety.12 + +# Force analyzer 1.0.0 during development and CI, so that test expectations work perfectly. +# This is necessary as analyze 1.0.0 is not in the version constraints of some other dependencies. +# glob also gets into the mix. +dependency_overrides: + analyzer: ^1.0.0 + glob: ^1.1.0 From ece06669b23098a5204e5e41cacde2f1bf17fef2 Mon Sep 17 00:00:00 2001 From: Thiago Zagui Giacomini Date: Fri, 19 Feb 2021 18:09:25 -0300 Subject: [PATCH 302/607] Add verification before execute verifyInOrder (dart-lang/mockito#334) --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/mock.dart | 4 ++++ pkgs/mockito/test/verify_test.dart | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 139cc638f..c61683a04 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,7 @@ * `verifyInOrder` now returns a `List` which stores arguments which were captured in a call to `verifiyInOrder`. * **Breaking change:** Remove the public constructor for `VerificationResult`. +* Doesn't allow `verify` been used inside the `verifyInOrder`. ## 5.0.0-nullsafety.7 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 115bc8b5b..76b88a910 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -939,6 +939,10 @@ Verification _makeVerify(bool never) { } throw StateError(message); } + if (_verificationInProgress) { + fail('There is already a verification in progress, ' + 'check if it was not called with a verify argument(s)'); + } _verificationInProgress = true; return (T mock) { _verificationInProgress = false; diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index c4e001844..885f09790 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -466,6 +466,19 @@ void main() { }); }); + test('verify been used as an argument fails', () { + mock.methodWithoutArgs(); + mock.getter; + 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()), + ]); + }); + }); + test('methods can be called again and again', () { mock.methodWithoutArgs(); mock.getter; From d2d03bae8f162ac151a01486f83900940fffd763 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 19 Feb 2021 16:18:14 -0500 Subject: [PATCH 303/607] Bump analyzer dependency to 1.0.0. The ecosystem has been moved forward so that version solving is happy with this. PiperOrigin-RevId: 358469555 --- pkgs/mockito/CHANGELOG.md | 1 - pkgs/mockito/lib/src/mock.dart | 4 ---- pkgs/mockito/pubspec.yaml | 8 +------- pkgs/mockito/test/verify_test.dart | 13 ------------- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index c61683a04..139cc638f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,7 +3,6 @@ * `verifyInOrder` now returns a `List` which stores arguments which were captured in a call to `verifiyInOrder`. * **Breaking change:** Remove the public constructor for `VerificationResult`. -* Doesn't allow `verify` been used inside the `verifyInOrder`. ## 5.0.0-nullsafety.7 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 76b88a910..115bc8b5b 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -939,10 +939,6 @@ Verification _makeVerify(bool never) { } throw StateError(message); } - if (_verificationInProgress) { - fail('There is already a verification in progress, ' - 'check if it was not called with a verify argument(s)'); - } _verificationInProgress = true; return (T mock) { _verificationInProgress = false; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 890b75a0c..c3099067d 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=0.39.15 <2.0.0' + analyzer: ^1.0.0 build: ^1.3.0 code_builder: ^3.4.0 collection: ^1.15.0-nullsafety.5 @@ -28,9 +28,3 @@ dev_dependencies: pedantic: ^1.10.0-nullsafety test: ^1.16.0-nullsafety.12 -# Force analyzer 1.0.0 during development and CI, so that test expectations work perfectly. -# This is necessary as analyze 1.0.0 is not in the version constraints of some other dependencies. -# glob also gets into the mix. -dependency_overrides: - analyzer: ^1.0.0 - glob: ^1.1.0 diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 885f09790..c4e001844 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -466,19 +466,6 @@ void main() { }); }); - test('verify been used as an argument fails', () { - mock.methodWithoutArgs(); - mock.getter; - 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()), - ]); - }); - }); - test('methods can be called again and again', () { mock.methodWithoutArgs(); mock.getter; From 23ff4e370ed6d577d3695a81c4240945c60d85ac Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 22 Feb 2021 15:50:04 -0500 Subject: [PATCH 304/607] Import PR dart-lang/mockito#334 from GitHub. Add verification before execute verifyInOrder PiperOrigin-RevId: 358883295 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/mock.dart | 4 ++++ pkgs/mockito/test/verify_test.dart | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 139cc638f..c61683a04 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,7 @@ * `verifyInOrder` now returns a `List` which stores arguments which were captured in a call to `verifiyInOrder`. * **Breaking change:** Remove the public constructor for `VerificationResult`. +* Doesn't allow `verify` been used inside the `verifyInOrder`. ## 5.0.0-nullsafety.7 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 115bc8b5b..76b88a910 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -939,6 +939,10 @@ Verification _makeVerify(bool never) { } throw StateError(message); } + if (_verificationInProgress) { + fail('There is already a verification in progress, ' + 'check if it was not called with a verify argument(s)'); + } _verificationInProgress = true; return (T mock) { _verificationInProgress = false; diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index c4e001844..59c257538 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -457,6 +457,19 @@ void main() { }); }); + test('verify been used as an argument fails', () { + mock.methodWithoutArgs(); + mock.getter; + 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()), + ]); + }); + }); + test('incomplete fails', () { mock.methodWithoutArgs(); expectFail( From fd0770e16d76d4aac09eb9db1deddce7490eed17 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 23 Feb 2021 15:12:32 -0500 Subject: [PATCH 305/607] Bump nullsafety dependencies to their non-prerelease versions. PiperOrigin-RevId: 359103139 --- pkgs/mockito/pubspec.yaml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c3099067d..1cb2d0d8f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -11,20 +11,19 @@ dependencies: analyzer: ^1.0.0 build: ^1.3.0 code_builder: ^3.4.0 - collection: ^1.15.0-nullsafety.5 + collection: ^1.15.0 dart_style: ^1.3.6 - matcher: ^0.12.10-nullsafety.3 - meta: ^1.3.0-nullsafety - path: ^1.8.0-nullsafety.3 + matcher: ^0.12.10 + meta: ^1.3.0 + path: ^1.8.0 source_gen: ^0.9.6 - test_api: ^0.2.19-nullsafety + test_api: ^0.2.19 dev_dependencies: build_runner: ^1.0.0 build_test: ^1.1.0 build_web_compilers: '>=1.0.0 <3.0.0' - http: ^0.13.0-nullsafety.0 + http: ^0.13.0 package_config: '>=1.9.3 <3.0.0' - pedantic: ^1.10.0-nullsafety - test: ^1.16.0-nullsafety.12 - + pedantic: ^1.10.0 + test: ^1.16.0 From 68221d08d3148f52cc5a0f93f92c6145c5348d2c Mon Sep 17 00:00:00 2001 From: tijoforyou Date: Thu, 25 Feb 2021 13:51:40 -0500 Subject: [PATCH 306/607] Clean up violations of Dart lint "prefer_typing_uninitialized_variables". PREFER specifying a type annotation for uninitialized variables and fields. [Lint documentation](https://dart-lang.github.io/linter/lints/prefer_typing_uninitialized_variables.html) [Dart lints discussion](http://g/dart-lints/GrWil4gpWQ0/V_0S9MjlAQAJ) Tested: tap_presubmit: http://test/OCL:359424889:BASE:359405290:1614246178194:7129d106 Sample tests for this CL passed, but some tests failed during the TGP run. Test failures are believed to be unrelated to this CL PiperOrigin-RevId: 359562635 --- pkgs/mockito/lib/src/mock.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 76b88a910..8d4770801 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -661,7 +661,7 @@ class _VerifyCall { void _checkWith(bool never) { if (!never && matchingInvocations.isEmpty) { - var message; + String message; if (mock._realCalls.isEmpty) { message = 'No matching calls (actually, no calls at all).'; } else { From 680211f2bfac83e513dbc729c5ec250b14303074 Mon Sep 17 00:00:00 2001 From: jakemac Date: Tue, 2 Mar 2021 13:32:10 -0500 Subject: [PATCH 307/607] Move mockito off of `AssetId.parse` - we are looking at dropping this constructor. PiperOrigin-RevId: 360461193 --- pkgs/mockito/test/builder/auto_mocks_test.dart | 8 ++++---- pkgs/mockito/test/builder/custom_mocks_test.dart | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index df4e8ccd2..7247604f7 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -112,7 +112,7 @@ void main() { await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); - var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]); } @@ -141,7 +141,7 @@ void main() { ...simpleTestAsset, 'foo|lib/foo.dart': sourceAssetText, }); - var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]); } @@ -961,7 +961,7 @@ void main() { } ''', }); - var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); var mocksContent = utf8.decode(writer.assets[mocksAsset]); expect(mocksContent, contains("import 'dart:async' as _i3;")); expect(mocksContent, contains('m(_i3.Future? a)')); @@ -984,7 +984,7 @@ void main() { } ''', }); - var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); var mocksContent = utf8.decode(writer.assets[mocksAsset]); expect(mocksContent, contains("import 'dart:async' as _i3;")); expect(mocksContent, contains('m(_i3.Future? a)')); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 1b1c670e7..fafdcad64 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -94,7 +94,7 @@ void main() { ]); await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); - var mocksAsset = AssetId.parse('foo|test/foo_test.mocks.dart'); + var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]); } From 472dbf706039a7f80776c90109f0381692e11df4 Mon Sep 17 00:00:00 2001 From: Stephen James Date: Fri, 5 Mar 2021 11:50:46 -0500 Subject: [PATCH 308/607] Clean up violations of the "prefer_is_not_operator" Dart lint. Lint documentation: https://dart-lang.github.io/linter/lints/prefer_is_not_operator Discussion: http://g/dart-lints/I0cvlcUqvTI LSC: http://go/dart-trivial-lint-cleanup-lsc PiperOrigin-RevId: 361155269 --- pkgs/mockito/test/mockito_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 7085cad93..fc985b558 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -51,7 +51,7 @@ void expectFail(String expectedMessage, void Function() expectedToFail) { expectedToFail(); fail('It was expected to fail!'); } catch (e) { - if (!(e is TestFailure)) { + if (e is! TestFailure) { rethrow; } else { if (expectedMessage != e.message) { From b3cf2479ad35d4e5f7920710b91a90d47f69f23c Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 8 Mar 2021 12:35:50 -0500 Subject: [PATCH 309/607] Fix crash when generating mocks on class which extends class with mixins. Mixin elements do not have superclasses, so `type.superclass`, for a mixin type, is null. This changes mockito to first check if type.superclass is null before checking if the superclass is Object. PiperOrigin-RevId: 361586483 --- pkgs/mockito/lib/src/builder.dart | 10 ++++++---- pkgs/mockito/test/builder/auto_mocks_test.dart | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index d2de0b03c..b08549a6e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -734,8 +734,9 @@ class _MockLibraryInfo { yield* fieldOverrides(mixin, overriddenFields); } } - if (!type.superclass.isDartCoreObject) { - yield* fieldOverrides(type.superclass, overriddenFields); + var superclass = type.superclass; + if (superclass != null && !superclass.isDartCoreObject) { + yield* fieldOverrides(superclass, overriddenFields); } } @@ -773,8 +774,9 @@ class _MockLibraryInfo { yield* methodOverrides(mixin, overriddenMethods); } } - if (!type.superclass.isDartCoreObject) { - yield* methodOverrides(type.superclass, overriddenMethods); + var superclass = type.superclass; + if (superclass != null && !superclass.isDartCoreObject) { + yield* methodOverrides(superclass, overriddenMethods); } } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 7247604f7..23ecd673e 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -599,6 +599,20 @@ void main() { ); }); + test('overrides methods of mixed in classes, from hierarchy', () async { + await expectSingleNonNullableOutput( + dedent(r''' + mixin Mixin { + void m(int a) {} + } + class FooBase with Mixin {} + class Foo extends FooBase {} + '''), + _containsAllOf( + 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + test( 'overrides methods of indirect generic super classes, substituting types', () async { From 21acd28a3505c7f495c6d19d94a2ae34201b4513 Mon Sep 17 00:00:00 2001 From: jakemac Date: Mon, 8 Mar 2021 23:21:24 -0500 Subject: [PATCH 310/607] Update test_api and matcher from github, and fix mockito to be compatible. PiperOrigin-RevId: 361719286 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- pkgs/mockito/test/invocation_matcher_test.dart | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index c61683a04..a1d603980 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.1 + +* Update to the latest test_api. + ## 5.0.0 * `verifyInOrder` now returns a `List` which stores diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 92cae6b69..eb1e3d4ee 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.0'; +const packageVersion = '5.0.1'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1cb2d0d8f..94a03a2dd 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.0 +version: 5.0.1 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -17,7 +17,7 @@ dependencies: meta: ^1.3.0 path: ^1.8.0 source_gen: ^0.9.6 - test_api: ^0.2.19 + test_api: ^0.3.0 dev_dependencies: build_runner: ^1.0.0 diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index b2b440fce..9b139abdb 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -173,7 +173,7 @@ void shouldFail(value, Matcher matcher, expected) { : expected is RegExp ? contains(expected) : expected; - expect(collapseWhitespace(e.message), matcher, reason: reason); + expect(collapseWhitespace(e.message!), matcher, reason: reason); } } From ab08c82c568569f71020e38e9d9a18353e91a251 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 10 Mar 2021 15:27:19 -0500 Subject: [PATCH 311/607] Support non-nullable generic function types as method return types. If a method, which is to be mocked, returns a type like `T Function(T)`, the generated code did not include generics on the dummy return value's closure. Using a new API from code_builder 3.7.0, we can generate the correct dummy return value. PiperOrigin-RevId: 362115101 --- pkgs/mockito/CHANGELOG.md | 3 + pkgs/mockito/lib/src/builder.dart | 18 +++++- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 56 ++++++++++++++++++- 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a1d603980..1113f49c9 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,9 @@ ## 5.0.1 * Update to the latest test_api. +* Fix mock generation of type which has a supertype which mixes in a mixin. +* Fix mock generation of method which returns a non-nullable generic function + type. ## 5.0.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b08549a6e..c08df8991 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -187,6 +187,7 @@ class _TypeVisitor extends RecursiveElementVisitor { super.visitTypeParameterElement(element); } + /// Adds [type] to the collected [_elements]. void _addType(analyzer.DartType type) { if (type == null) return; @@ -195,6 +196,18 @@ class _TypeVisitor extends RecursiveElementVisitor { (type.typeArguments ?? []).forEach(_addType); } else if (type is analyzer.FunctionType) { _addType(type.returnType); + + // [RecursiveElementVisitor] does not "step out of" the element model, + // into types, while traversing, so we must explicitly traverse [type] + // here, in order to visit contained elements. + if (type.typeFormals != null) { + for (var typeParameter in type.typeFormals) { + typeParameter.accept(this); + } + } + for (var parameter in type.parameters) { + parameter.accept(this); + } var aliasElement = type.aliasElement; if (aliasElement != null) { _elements.add(aliasElement); @@ -944,6 +957,9 @@ class _MockLibraryInfo { // The positional parameters in a FunctionType have no names. This // counter lets us create unique dummy names. var counter = 0; + if (type.typeFormals != null) { + b.types.addAll(type.typeFormals.map(_typeParameterReference)); + } for (final parameter in type.parameters) { if (parameter.isRequiredPositional) { b.requiredParameters @@ -962,7 +978,7 @@ class _MockLibraryInfo { } else { b.body = _dummyValue(type.returnType).code; } - }).closure; + }).genericClosure; } Expression _dummyValueImplementing(analyzer.InterfaceType dartType) { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 94a03a2dd..7a66838b5 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: analyzer: ^1.0.0 build: ^1.3.0 - code_builder: ^3.4.0 + code_builder: ^3.7.0 collection: ^1.15.0 dart_style: ^1.3.6 matcher: ^0.12.10 diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 23ecd673e..942ca4d16 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1912,11 +1912,61 @@ void main() { T? Function(T) m() => (int i, [String s]) {}; } '''), - // TODO(srawlins): This output is invalid: `T __p0` is out of the scope - // where T is defined. _containsAllOf(dedent2(''' T? Function(T) m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: (T __p0) => null) as T? Function(T)); + returnValue: (T __p0) => null) as T? Function(T)); + ''')), + ); + }); + + test('creates a dummy non-null generic bounded function-typed return value', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + import 'dart:io'; + class Foo { + T? Function(T) m() => (int i, [String s]) {}; + } + '''), + _containsAllOf(dedent2(''' + T? Function(T) m() => + (super.noSuchMethod(Invocation.method(#m, []), + returnValue: (T __p0) => null) + as T? Function(T)); + ''')), + ); + }); + + test( + 'creates a dummy non-null function-typed (with an imported parameter type) return value', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + import 'dart:io'; + class Foo { + void Function(File) m() => (int i, [String s]) {}; + } + '''), + _containsAllOf(dedent2(''' + void Function(_i3.File) m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: (_i3.File __p0) {}) as void Function(_i3.File)); + ''')), + ); + }); + + test( + 'creates a dummy non-null function-typed (with an imported return type) return value', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + import 'dart:io'; + class Foo { + File Function() m() => (int i, [String s]) {}; + } + '''), + _containsAllOf(dedent2(''' + _i2.File Function() m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: () => _FakeFile()) as _i2.File Function()); ''')), ); }); From ac977e172d8022f6c21e2740c9c01fcabf9c2caa Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 10 Mar 2021 19:37:48 -0500 Subject: [PATCH 312/607] Widen constraints on test_api. mockito is OK with 0.2.x and with 0.3.x. Let pub choose at `pub get`, for users of mockito. PiperOrigin-RevId: 362170601 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 7a66838b5..bc8a9d9fa 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: meta: ^1.3.0 path: ^1.8.0 source_gen: ^0.9.6 - test_api: ^0.3.0 + test_api: '>=0.2.1 <0.4.0' dev_dependencies: build_runner: ^1.0.0 From 300ae1fd90ca67d48b580bea1e280534f3714804 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 11 Mar 2021 15:34:29 -0500 Subject: [PATCH 313/607] Update the first section of the README with steps for code generation. The **Let's create mocks** section now includes steps for build_runner, and using the `GenerateMocks` annotation. I reviewed the NULL_SAFETY_README.md, and I think it does not need editing; it still contains helpful details, explanations, and alternatives. PiperOrigin-RevId: 362353715 --- pkgs/mockito/README.md | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a6c8a3809..91bd25a87 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -7,8 +7,21 @@ Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). ## Let's create mocks +Mockito 5.0.0 supports Dart's new **null safety** language feature in Dart 2.12, +primarily with code generation. + +To use Mockito's generated mock classes, add a `build_runner` dependency in your +package's `pubspec.yaml` file, under `dev_dependencies`; something like +`build_runner: ^1.11.0`. + +For alternatives to the code generation API, see the [NULL_SAFETY_README][]. + +Let's start with a Dart library, `cat.dart`: + ```dart +import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'cat.mocks.dart'; // Real class class Cat { @@ -21,18 +34,30 @@ class Cat { int lives = 9; } -// Mock class -class MockCat extends Mock implements Cat {} +// Annotation which generates the cat.mocks.dart library and the MockCat class. +@GenerateMocks([Cat]) +void main() { + // Create mock object. + var cat = MockCat(); +} +``` + +By annotating a library element (such as a test file's `main` function, or a +class) with `@GenerateMocks`, you are directing Mockito's code generation to +write a mock class for each "real" class listed, in a new library. -// Create mock object. -var cat = MockCat(); +The next step is to run `build_runner` in order to generate this new library: + +```shell +pub run build_runner build ``` -By declaring a class which extends Mockito's Mock class and implements the Cat -class under test, we have a class which supports stubbing and verifying. +`build_runner` will generate a file with a name based on the file containing the +`@GenerateMocks` annotation. In the above `cat.dart` example, we import the +generated library as `cat.mocks.dart`. -**Using Dart's new null safety feature?** Read the [NULL_SAFETY_README][] for -help on creating mocks of classes featuring non-nullable types. +The generated mock class, `MockCat`, extends Mockito's Mock class and implements +the Cat class, giving us a class which supports stubbing and verifying. [NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md @@ -127,7 +152,6 @@ final future = Future.value('Stub'); when(mock.methodThatReturnsAFuture()).thenAnswer((_) => future); ``` - ## Argument matchers Mockito provides the concept of the "argument matcher" (using the class From 115878fc99b50f92f2f1ce99c88f2cab668f07fa Mon Sep 17 00:00:00 2001 From: Goddchen Date: Fri, 12 Mar 2021 09:40:36 +0100 Subject: [PATCH 314/607] Fix 404 link --- pkgs/mockito/lib/src/mock.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 8d4770801..d76919523 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -115,11 +115,12 @@ void throwOnMissingStub( /// with [verify] or [when]. To implement a subset of an interface manually use /// [Fake] instead. /// -/// **WARNING**: [Mock] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of -/// runtime reflection, and causes sub-standard code to be generated. As such, -/// [Mock] 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). +/// **WARNING**: [Mock] uses +/// [noSuchMethod](http://bit.ly/dart-emulating-functions) +/// , which is a _form_ of runtime reflection, and causes sub-standard code to +/// be generated. As such, [Mock] 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). class Mock { static Null _answerNull(_) => null; From 23a26a0e311b9128022997671f42bb4d3b1d449b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 15 Mar 2021 21:36:15 +0100 Subject: [PATCH 315/607] Support build 2 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/pubspec.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 1113f49c9..41616b8e9 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.2-dev + +* Support the latest build packages + ## 5.0.1 * Update to the latest test_api. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index bc8a9d9fa..ab4e0a499 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.1 +version: 5.0.2-dev description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -9,7 +9,7 @@ environment: dependencies: analyzer: ^1.0.0 - build: ^1.3.0 + build: '>=1.3.0 <3.0.0' code_builder: ^3.7.0 collection: ^1.15.0 dart_style: ^1.3.6 @@ -20,8 +20,8 @@ dependencies: test_api: '>=0.2.1 <0.4.0' dev_dependencies: - build_runner: ^1.0.0 - build_test: ^1.1.0 + build_runner: ^1.12.0 + build_test: ^2.0.0 build_web_compilers: '>=1.0.0 <3.0.0' http: ^0.13.0 package_config: '>=1.9.3 <3.0.0' From ee894618eff66b9ad820f323f10f3699786bb99a Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 15 Mar 2021 21:10:08 -0400 Subject: [PATCH 316/607] Import https://github.com/dart-lang/mockito/pull/366 PiperOrigin-RevId: 363076152 --- pkgs/mockito/lib/src/mock.dart | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index d76919523..e497e2503 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -115,7 +115,7 @@ void throwOnMissingStub( /// with [verify] or [when]. To implement a subset of an interface manually use /// [Fake] instead. /// -/// **WARNING**: [Mock] uses +/// **WARNING**: [Mock] uses /// [noSuchMethod](http://bit.ly/dart-emulating-functions) /// , which is a _form_ of runtime reflection, and causes sub-standard code to /// be generated. As such, [Mock] should strictly _not_ be used in any diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index eb1e3d4ee..aa112b0d3 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.1'; +const packageVersion = '5.0.2-dev'; From 0be7e6351f9571549435630e5f6fba34afe6f61c Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 16 Mar 2021 19:32:05 -0400 Subject: [PATCH 317/607] Remove unnecessary null checks from null safe code. PiperOrigin-RevId: 363296708 --- pkgs/mockito/lib/src/invocation_matcher.dart | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index 7d8d5e905..40f5b54cb 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -40,12 +40,6 @@ Matcher invokes( if (isGetter && isSetter) { throw ArgumentError('Cannot set isGetter and iSetter'); } - if (positionalArguments == null) { - throw ArgumentError.notNull('positionalArguments'); - } - if (namedArguments == null) { - throw ArgumentError.notNull('namedArguments'); - } return _InvocationMatcher(_InvocationSignature( memberName: memberName, positionalArguments: positionalArguments, @@ -127,11 +121,7 @@ class _InvocationMatcher implements Matcher { final Invocation _invocation; - _InvocationMatcher(this._invocation) { - if (_invocation == null) { - throw ArgumentError.notNull(); - } - } + _InvocationMatcher(this._invocation); @override Description describe(Description d) => _describeInvocation(d, _invocation); From c4c72dbe9210d6d411a7354e051a03bbc9bc1ddd Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 16 Mar 2021 19:43:09 -0400 Subject: [PATCH 318/607] Fix doc reference typo. [InvocationMatcher.matches] is the name of the method, not 'match'. PiperOrigin-RevId: 363299046 --- pkgs/mockito/lib/src/mock.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index e497e2503..4142f1fe0 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -616,7 +616,7 @@ class _UntilCall { } /// A simple struct for storing a [RealCall] and any [capturedArgs] stored -/// during [InvocationMatcher.match]. +/// during [InvocationMatcher.matches]. class _RealCallWithCapturedArgs { final RealCall realCall; final List capturedArgs; From 3a1bd02f82305e2c43616bee582b8b94cb26b596 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 16 Mar 2021 21:39:46 -0400 Subject: [PATCH 319/607] Support dart_style 2.x and bump to 5.0.2. PiperOrigin-RevId: 363317758 --- pkgs/mockito/CHANGELOG.md | 4 ++-- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 41616b8e9..2104b9bfb 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,6 @@ -## 5.0.2-dev +## 5.0.2 -* Support the latest build packages +* Support the latest build packages and dart_style. ## 5.0.1 diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index aa112b0d3..041813eda 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.2-dev'; +const packageVersion = '5.0.2'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index ab4e0a499..5ecc027b5 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.2-dev +version: 5.0.2 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -12,7 +12,7 @@ dependencies: build: '>=1.3.0 <3.0.0' code_builder: ^3.7.0 collection: ^1.15.0 - dart_style: ^1.3.6 + dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 meta: ^1.3.0 path: ^1.8.0 From a8c7a13b8e131e5fdef78a6e42e3048136c9955e Mon Sep 17 00:00:00 2001 From: srawlins Date: Sun, 21 Mar 2021 23:38:49 -0400 Subject: [PATCH 320/607] Release 5.0.3, supporting source_gen 1.x releases. PiperOrigin-RevId: 364232507 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 2104b9bfb..d490d9aab 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.3 + +* Support 1.x releases of source_gen. + ## 5.0.2 * Support the latest build packages and dart_style. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 041813eda..977890f09 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.2'; +const packageVersion = '5.0.3'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 5ecc027b5..dff34bfe5 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.2 +version: 5.0.3 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -16,7 +16,7 @@ dependencies: matcher: ^0.12.10 meta: ^1.3.0 path: ^1.8.0 - source_gen: ^0.9.6 + source_gen: '>=0.9.6 <2.0.0' test_api: '>=0.2.1 <0.4.0' dev_dependencies: From 11913a916f1eca4a5ddbdb21b619ba3efa455538 Mon Sep 17 00:00:00 2001 From: Raven Black Date: Wed, 24 Mar 2021 13:19:28 -0700 Subject: [PATCH 321/607] Make verifyInOrder fail given non-mock-calls. Also adds a test for verify failing when given a non-mock call. --- pkgs/mockito/lib/src/mock.dart | 5 ++++- pkgs/mockito/test/verify_test.dart | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 4142f1fe0..44bec8421 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -997,7 +997,10 @@ _InOrderVerification get verifyInOrder { throw StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (List _) { + return (List responses) { + if (responses.length != _verifyCalls.length) { + fail('Used on a non-mockito object'); + } _verificationInProgress = false; var verificationResults = []; var time = DateTime.fromMillisecondsSinceEpoch(0); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 59c257538..659fe0733 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -206,6 +206,12 @@ void main() { '2 verify calls have been stored.'))); } }); + + test('should fail when called with non-mock-call parameter', () { + expectFail('Used on a non-mockito object', () { + verifyInOrder(['a string is not a mock call']); + }); + }); }); group('verify should fail when no matching call is found', () { @@ -457,6 +463,12 @@ void main() { }); }); + test('should fail when given non-mock-call parameters', () { + expectFail('Used on a non-mockito object', () { + verifyInOrder(['a string is not a mock call']); + }); + }); + test('verify been used as an argument fails', () { mock.methodWithoutArgs(); mock.getter; From 16d6118e6d6cea5abbefc0b6660f70b9ca85a66a Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 7 Apr 2021 11:34:21 -0400 Subject: [PATCH 322/607] Import https://github.com/dart-lang/mockito/pull/362 PiperOrigin-RevId: 367225603 --- pkgs/mockito/lib/src/mock.dart | 5 +---- pkgs/mockito/test/verify_test.dart | 12 ------------ 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 44bec8421..4142f1fe0 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -997,10 +997,7 @@ _InOrderVerification get verifyInOrder { throw StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (List responses) { - if (responses.length != _verifyCalls.length) { - fail('Used on a non-mockito object'); - } + return (List _) { _verificationInProgress = false; var verificationResults = []; var time = DateTime.fromMillisecondsSinceEpoch(0); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 659fe0733..59c257538 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -206,12 +206,6 @@ void main() { '2 verify calls have been stored.'))); } }); - - test('should fail when called with non-mock-call parameter', () { - expectFail('Used on a non-mockito object', () { - verifyInOrder(['a string is not a mock call']); - }); - }); }); group('verify should fail when no matching call is found', () { @@ -463,12 +457,6 @@ void main() { }); }); - test('should fail when given non-mock-call parameters', () { - expectFail('Used on a non-mockito object', () { - verifyInOrder(['a string is not a mock call']); - }); - }); - test('verify been used as an argument fails', () { mock.methodWithoutArgs(); mock.getter; From cbeb36b74291b3c6df9caf2f835262795f0d7530 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 7 Apr 2021 12:45:07 -0400 Subject: [PATCH 323/607] Import https://github.com/dart-lang/mockito/pull/376 PiperOrigin-RevId: 367238585 --- pkgs/mockito/lib/src/mock.dart | 5 ++++- pkgs/mockito/test/verify_test.dart | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 4142f1fe0..44bec8421 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -997,7 +997,10 @@ _InOrderVerification get verifyInOrder { throw StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (List _) { + return (List responses) { + if (responses.length != _verifyCalls.length) { + fail('Used on a non-mockito object'); + } _verificationInProgress = false; var verificationResults = []; var time = DateTime.fromMillisecondsSinceEpoch(0); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 59c257538..659fe0733 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -206,6 +206,12 @@ void main() { '2 verify calls have been stored.'))); } }); + + test('should fail when called with non-mock-call parameter', () { + expectFail('Used on a non-mockito object', () { + verifyInOrder(['a string is not a mock call']); + }); + }); }); group('verify should fail when no matching call is found', () { @@ -457,6 +463,12 @@ void main() { }); }); + test('should fail when given non-mock-call parameters', () { + expectFail('Used on a non-mockito object', () { + verifyInOrder(['a string is not a mock call']); + }); + }); + test('verify been used as an argument fails', () { mock.methodWithoutArgs(); mock.getter; From 857c47ac391709d9defd7bea28b25db9f7b1efdf Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 7 Apr 2021 13:28:30 -0400 Subject: [PATCH 324/607] Allow calling methods with void return types w/o stubbing. Having to stub a `void` function before calling it confuses developers. They write `when(mock.methodReturnsVoid(any)).thenReturn(` and then might really have to scratch their heads about what makes sense to return from a mock of a void function. And all this just to satisfy a requirement to have sane return values, which I think just doesn't apply to void (and `Future` and `Future?`) functions. This CL changes the logic that determines whether a method (or field, or setter) needs to be overridden. It now includes the case for these void-ish methods. Fixes https://github.com/dart-lang/mockito/issues/367 PiperOrigin-RevId: 367247168 --- pkgs/mockito/lib/src/builder.dart | 43 ++++++++------- .../mockito/test/builder/auto_mocks_test.dart | 54 +++++++++---------- pkgs/mockito/test/end2end/foo.dart | 4 +- .../test/end2end/generated_mocks_test.dart | 9 ++++ 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index c08df8991..47a8dd060 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -735,10 +735,10 @@ class _MockLibraryInfo { continue; } overriddenFields.add(accessor.name); - if (accessor.isGetter != null && _returnTypeIsNonNullable(accessor)) { + if (accessor.isGetter && _returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } - if (accessor.isSetter != null && _hasNonNullableParameter(accessor)) { + if (accessor.isSetter) { yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); } } @@ -777,7 +777,8 @@ class _MockLibraryInfo { continue; } if (_returnTypeIsNonNullable(method) || - _hasNonNullableParameter(method)) { + _hasNonNullableParameter(method) || + _needsOverrideForVoidStub(method)) { yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method, className: type.getDisplayString(withNullability: true))); } @@ -804,6 +805,9 @@ class _MockLibraryInfo { bool _returnTypeIsNonNullable(ExecutableElement method) => typeSystem.isPotentiallyNonNullable(method.returnType); + bool _needsOverrideForVoidStub(ExecutableElement method) => + method.returnType.isVoid || method.returnType.isFutureOfVoid; + // Returns whether [method] has at least one parameter whose type is // potentially non-nullable. // @@ -858,9 +862,6 @@ class _MockLibraryInfo { } } - if (_returnTypeIsNonNullable(method) && - method.returnType is analyzer.TypeParameterType) {} - final invocation = refer('Invocation').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), @@ -870,8 +871,7 @@ class _MockLibraryInfo { Expression returnValueForMissingStub; if (method.returnType.isVoid) { returnValueForMissingStub = refer('null'); - } else if (method.returnType == - typeProvider.futureType2(typeProvider.voidType)) { + } else if (method.returnType.isFutureOfVoid) { returnValueForMissingStub = refer('Future').property('value').call([]); } final namedArgs = { @@ -1179,21 +1179,19 @@ class _MockLibraryInfo { ..annotations.addAll([refer('override')]) ..type = MethodType.setter; - final invocationPositionalArgs = []; - // There should only be one required positional parameter. Should we assert - // on that? Leave it alone? - for (final parameter in setter.parameters) { - if (parameter.isRequiredPositional) { - builder.requiredParameters.add(Parameter((pBuilder) => pBuilder - ..name = parameter.displayName - ..type = _typeReference(parameter.type, forceNullable: true))); - invocationPositionalArgs.add(refer(parameter.displayName)); - } + Expression invocationPositionalArg; + assert(setter.parameters.length == 1); + final parameter = setter.parameters.single; + if (parameter.isRequiredPositional) { + builder.requiredParameters.add(Parameter((pBuilder) => pBuilder + ..name = parameter.displayName + ..type = _typeReference(parameter.type, forceNullable: true))); + invocationPositionalArg = refer(parameter.displayName); } final invocation = refer('Invocation').property('setter').call([ refer('#${setter.displayName}'), - invocationPositionalArgs.single, + invocationPositionalArg, ]); final returnNoSuchMethod = refer('super') .property('noSuchMethod') @@ -1334,3 +1332,10 @@ extension on Element { } } } + +extension on analyzer.DartType { + /// Returns whether this type is `Future` or `Future?`. + bool get isFutureOfVoid => + isDartAsyncFuture && + (this as analyzer.InterfaceType).typeArguments.first.isVoid; +} diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 942ca4d16..d69317d2a 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -629,15 +629,17 @@ void main() { ); }); - test('does not override methods of generic super classes using void', - () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + test('overrides methods of generic super classes using void', () async { + await expectSingleNonNullableOutput( + dedent(r''' class FooBase { - T method1() {} + T m() {} } class Foo extends FooBase {} - ''')); - expect(mocksContent, isNot(contains('method1'))); + '''), + _containsAllOf('void m() => super', + '.noSuchMethod(Invocation.method(#m, []), returnValueForMissingStub: null);'), + ); }); test('overrides methods of generic super classes (type variable)', () async { @@ -1258,7 +1260,7 @@ void main() { test('does not override methods with all nullable parameters', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - void method1(int? p) {} + int? method1(int? p) => null; } ''')); expect(mocksContent, isNot(contains('method1'))); @@ -1268,7 +1270,7 @@ void main() { () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - void method1(dynamic p) {} + int? method1(dynamic p) => null; } ''')); expect(mocksContent, isNot(contains('method1'))); @@ -1278,7 +1280,7 @@ void main() { () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - void method1(var p) {} + int? method1(var p) => null; } ''')); expect(mocksContent, isNot(contains('method1'))); @@ -1288,7 +1290,7 @@ void main() { () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - void method1(final p) {} + int? method1(final p) => null; } ''')); expect(mocksContent, isNot(contains('method1'))); @@ -1298,7 +1300,7 @@ void main() { () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - void method1(T? p) {} + int? method1(T? p) => null; } ''')); expect(mocksContent, isNot(contains('method1'))); @@ -1309,16 +1311,7 @@ void main() { () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { - void method1(int Function()? p) {} - } - ''')); - expect(mocksContent, isNot(contains('method1'))); - }); - - test('does not override methods with a void return type', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' - abstract class Foo { - void method1(); + int? method1(int Function()? p) => null; } ''')); expect(mocksContent, isNot(contains('method1'))); @@ -1484,13 +1477,18 @@ void main() { ); }); - test('does not override nullable instance setters', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + test('overrides nullable instance setters', () async { + await expectSingleNonNullableOutput( + dedent(''' class Foo { - void set setter1(int? a) {} + void set m(int? a) {} } - ''')); - expect(mocksContent, isNot(contains('setter1'))); + '''), + _containsAllOf(dedent2(''' + set m(int? a) => super + .noSuchMethod(Invocation.setter(#m, a), returnValueForMissingStub: null); + ''')), + ); }); test('overrides inherited non-nullable instance setters', () async { @@ -1587,13 +1585,13 @@ void main() { ); }); - test('does not override nullable fields', () async { + test('does not override getters for nullable fields', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? field1; } ''')); - expect(mocksContent, isNot(contains('field1'))); + expect(mocksContent, isNot(contains('get field1'))); }); test('does not override private fields', () async { diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 8dcf99dc8..17da34178 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -7,8 +7,10 @@ class Foo { String? nullableMethod(int x) => 'Real'; String? get nullableGetter => 'Real'; String methodWithBarArg(Bar bar) => 'result'; - set setter(int value) {} + set setter(int? value) {} + void returnsVoid() {} Future returnsFutureVoid() => Future.value(); + Future? returnsNullableFutureVoid() => Future.value(); } class FooSub extends Foo {} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 716806ac6..44eadc82a 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -60,11 +60,20 @@ void main() { expect(() => foo.setter = 7, returnsNormally); }); + test('a method which returns void can be called without stubbing', () { + expect(() => foo.returnsVoid(), returnsNormally); + }); + test('a method which returns Future can be called without stubbing', () { expect(() => foo.returnsFutureVoid(), returnsNormally); }); + test('a method which returns Future? can be called without stubbing', + () { + expect(() => foo.returnsNullableFutureVoid(), returnsNormally); + }); + test( 'a method with a non-nullable positional parameter accepts an argument ' 'matcher while stubbing', () { From 210f583281b8bef1d28555d6cc4522fc43b4c808 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 7 Apr 2021 17:12:18 -0400 Subject: [PATCH 325/607] Automated g4 rollback of changelist 367238585. *** Reason for rollback *** Broke https://test.corp.google.com/ui#cl=365134781&flags=CAMQBQ==&id=OCL:365134781:BASE:367271161:1617822936639:6a37b0ed&t=//chrome/cloudcast/client/gamerx/audio/test:active_controller_audio_service_test_ddc_headless-chrome-linux *** Original change description *** Import https://github.com/dart-lang/mockito/pull/376 *** PiperOrigin-RevId: 367295097 --- pkgs/mockito/lib/src/mock.dart | 5 +---- pkgs/mockito/test/verify_test.dart | 12 ------------ 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 44bec8421..4142f1fe0 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -997,10 +997,7 @@ _InOrderVerification get verifyInOrder { throw StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (List responses) { - if (responses.length != _verifyCalls.length) { - fail('Used on a non-mockito object'); - } + return (List _) { _verificationInProgress = false; var verificationResults = []; var time = DateTime.fromMillisecondsSinceEpoch(0); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 659fe0733..59c257538 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -206,12 +206,6 @@ void main() { '2 verify calls have been stored.'))); } }); - - test('should fail when called with non-mock-call parameter', () { - expectFail('Used on a non-mockito object', () { - verifyInOrder(['a string is not a mock call']); - }); - }); }); group('verify should fail when no matching call is found', () { @@ -463,12 +457,6 @@ void main() { }); }); - test('should fail when given non-mock-call parameters', () { - expectFail('Used on a non-mockito object', () { - verifyInOrder(['a string is not a mock call']); - }); - }); - test('verify been used as an argument fails', () { mock.methodWithoutArgs(); mock.getter; From 71fbd84a905d0591616d24576128951c72abf810 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 9 Apr 2021 13:25:27 -0400 Subject: [PATCH 326/607] Automated g4 rollback of changelist 367295097. *** Reason for rollback *** Roll forward after fixes *** Original change description *** Automated g4 rollback of changelist 367238585. *** Reason for rollback *** Broke https://test.corp.google.com/ui#cl=365134781&flags=CAMQBQ==&id=OCL:365134781:BASE:367271161:1617822936639:6a37b0ed&t=//chrome/cloudcast/client/gamerx/audio/test:active_controller_audio_service_test_ddc_headless-chrome-linux *** Original change description *** Import https://github.com/dart-lang/mockito/pull/376 *** *** PiperOrigin-RevId: 367657308 --- pkgs/mockito/lib/src/mock.dart | 5 ++++- pkgs/mockito/test/verify_test.dart | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 4142f1fe0..44bec8421 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -997,7 +997,10 @@ _InOrderVerification get verifyInOrder { throw StateError(_verifyCalls.join()); } _verificationInProgress = true; - return (List _) { + return (List responses) { + if (responses.length != _verifyCalls.length) { + fail('Used on a non-mockito object'); + } _verificationInProgress = false; var verificationResults = []; var time = DateTime.fromMillisecondsSinceEpoch(0); diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 59c257538..659fe0733 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -206,6 +206,12 @@ void main() { '2 verify calls have been stored.'))); } }); + + test('should fail when called with non-mock-call parameter', () { + expectFail('Used on a non-mockito object', () { + verifyInOrder(['a string is not a mock call']); + }); + }); }); group('verify should fail when no matching call is found', () { @@ -457,6 +463,12 @@ void main() { }); }); + test('should fail when given non-mock-call parameters', () { + expectFail('Used on a non-mockito object', () { + verifyInOrder(['a string is not a mock call']); + }); + }); + test('verify been used as an argument fails', () { mock.methodWithoutArgs(); mock.getter; From 9e6f8c77a9f048c07a88c9eba96d9ac975da0314 Mon Sep 17 00:00:00 2001 From: srawlins Date: Sun, 11 Apr 2021 19:22:22 -0400 Subject: [PATCH 327/607] Add type annotations to Future dummy return values. These allow the `as` cast to succeed; otherwise it is guaranteed to fail. Fixes https://github.com/dart-lang/mockito/issues/380 PiperOrigin-RevId: 367907501 --- pkgs/mockito/CHANGELOG.md | 7 ++++++ pkgs/mockito/lib/src/builder.dart | 13 +++++++++-- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 22 +++++++++++++++++-- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index d490d9aab..0d7afb4af 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.0.4 + +* Allow calling methods with void return types w/o stubbing. + [#367](https://github.com/dart-lang/mockito/issues/367) +* Add type argument to dummy `Future` return value. + [#380](https://github.com/dart-lang/mockito/issues/380) + ## 5.0.3 * Support 1.x releases of source_gen. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 47a8dd060..0f7da3185 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -872,7 +872,7 @@ class _MockLibraryInfo { if (method.returnType.isVoid) { returnValueForMissingStub = refer('null'); } else if (method.returnType.isFutureOfVoid) { - returnValueForMissingStub = refer('Future').property('value').call([]); + returnValueForMissingStub = _futureReference().property('value').call([]); } final namedArgs = { if (_returnTypeIsNonNullable(method)) @@ -912,7 +912,7 @@ class _MockLibraryInfo { } else if (interfaceType.isDartAsyncFuture || interfaceType.isDartAsyncFutureOr) { var typeArgument = typeArguments.first; - return refer('Future') + return _futureReference(_typeReference(typeArgument)) .property('value') .call([_dummyValue(typeArgument)]); } else if (interfaceType.isDartCoreInt) { @@ -952,6 +952,15 @@ class _MockLibraryInfo { return _dummyValueImplementing(type as analyzer.InterfaceType); } + /// Returns a reference to [Future], optionally with a type argument for the + /// value of the Future. + TypeReference _futureReference([Reference valueType]) => TypeReference((b) { + b.symbol = 'Future'; + if (valueType != null) { + b.types.add(valueType); + } + }); + Expression _dummyFunctionValue(analyzer.FunctionType type) { return Method((b) { // The positional parameters in a FunctionType have no names. This diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 977890f09..2c9f52fd6 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.3'; +const packageVersion = '5.0.4'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index dff34bfe5..543d0b679 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.3 +version: 5.0.4 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index d69317d2a..e1e53f4d7 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -524,7 +524,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future.value(null), + returnValue: Future.value(null), returnValueForMissingStub: Future.value()) as _i3.Future); ''')), ); @@ -1779,7 +1779,25 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future.value(false)) as _i3.Future); + returnValue: Future.value(false)) as _i3.Future); + ''')), + ); + }); + + test( + 'creates dummy non-null return values for Futures of known generic core classes', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Foo { + Future> m() async => false; + } + '''), + _containsAllOf(dedent2(''' + _i3.Future> m() => + (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Future>.value([])) + as _i3.Future>); ''')), ); }); From b4a7355581220321eadbe2a564df329199d538bd Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 12 Apr 2021 14:47:55 -0400 Subject: [PATCH 328/607] Add URL to references of a mark target's type arguments. Fixes https://github.com/dart-lang/mockito/issues/382 All imports in mockito's generated code use import prefixes, and so a prefix always needs to be attached to a reference to a library element (like a class). Forgetting a URL has been the source of multiple bugs, so this change includes a broader refactor as well to hide code_builder's `refer` function, and instead provide our own. PiperOrigin-RevId: 368050695 --- pkgs/mockito/lib/src/builder.dart | 36 ++++++++++++++----- .../test/builder/custom_mocks_test.dart | 5 +-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0f7da3185..b14580466 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -21,7 +21,13 @@ import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/dart/element/type_system.dart'; import 'package:analyzer/dart/element/visitor.dart'; import 'package:build/build.dart'; -import 'package:code_builder/code_builder.dart'; +// Do not expose [refer] in the default namespace. +// +// [refer] allows a reference to include or not include a URL. Omitting the URL +// of an element, like a class, has resulted in many bugs. [_MockLibraryInfo] +// provides a [refer] function and a [referBasic] function. The former requires +// a URL to be passed. +import 'package:code_builder/code_builder.dart' hide refer; import 'package:dart_style/dart_style.dart'; import 'package:meta/meta.dart'; import 'package:mockito/src/version.dart'; @@ -665,7 +671,7 @@ class _MockLibraryInfo { return Class((cBuilder) { cBuilder ..name = mockTarget.mockName - ..extend = refer('Mock', 'package:mockito/mockito.dart') + ..extend = referImported('Mock', 'package:mockito/mockito.dart') // TODO(srawlins): Refer to [classToMock] properly, which will yield the // appropriate import prefix. ..docs.add('/// A class which mocks [$className].') @@ -685,7 +691,8 @@ class _MockLibraryInfo { // implements the mock target with said type arguments. For example: // `class MockFoo extends Mock implements Foo {}` for (var typeArgument in typeToMock.typeArguments) { - typeArguments.add(refer(typeArgument.element.name)); + typeArguments.add(referImported( + typeArgument.element.name, _typeImport(typeArgument.element))); } } else if (classToMock.typeParameters != null) { // [typeToMock] is a simple reference to a generic type (for example: @@ -799,7 +806,7 @@ class _MockLibraryInfo { /// `throwOnMissingStub`. Constructor get _constructorWithThrowOnMissingStub => Constructor((cBuilder) => cBuilder.body = - refer('throwOnMissingStub', 'package:mockito/mockito.dart') + referImported('throwOnMissingStub', 'package:mockito/mockito.dart') .call([refer('this').expression]).statement); bool _returnTypeIsNonNullable(ExecutableElement method) => @@ -1009,7 +1016,7 @@ class _MockLibraryInfo { fakeClasses.add(Class((cBuilder) { cBuilder ..name = fakeName - ..extend = refer('Fake', 'package:mockito/mockito.dart'); + ..extend = referImported('Fake', 'package:mockito/mockito.dart'); if (elementToFake.typeParameters != null) { for (var typeParameter in elementToFake.typeParameters) { cBuilder.types.add(_typeParameterReference(typeParameter)); @@ -1119,11 +1126,12 @@ class _MockLibraryInfo { // A top-level function, like `void f() {}` must be referenced by its // identifier, rather than a revived value. var element = object.toFunctionValue(); - return refer(revivable.accessor, _typeImport(element)); + return referImported(revivable.accessor, _typeImport(element)); } else if (revivable.source.fragment.isEmpty) { // We can create this invocation by referring to a const field or // top-level variable. - return refer(revivable.accessor, _typeImport(object.type.element)); + return referImported( + revivable.accessor, _typeImport(object.type.element)); } final name = revivable.source.fragment; @@ -1135,7 +1143,7 @@ class _MockLibraryInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final type = refer(name, _typeImport(object.type.element)); + final type = referImported(name, _typeImport(object.type.element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( revivable.accessor, @@ -1280,7 +1288,7 @@ class _MockLibraryInfo { ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { - return refer( + return referImported( type.getDisplayString(withNullability: false), _typeImport(type.element), ); @@ -1302,6 +1310,16 @@ class _MockLibraryInfo { return assetUris[element]; } + + /// Returns a [Reference] to [symbol] with [url]. + /// + /// This function overrides [code_builder.refer] so as to ensure that [url] is + /// given. + static Reference referImported(String symbol, String url) => + Reference(symbol, url); + + /// Returns a [Reference] to [symbol] with no URL. + static Reference refer(String symbol) => Reference(symbol); } /// An exception thrown when reviving a potentially deep value in a constant. diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index fafdcad64..059851fc7 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -124,19 +124,20 @@ void main() { ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} + class Bar {} '''), 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks( - [], customMocks: [MockSpec>(as: #MockFooOfIntBool)]) + [], customMocks: [MockSpec>(as: #MockFooOfIntBar)]) void main() {} ''' }); expect( mocksContent, contains( - 'class MockFooOfIntBool extends _i1.Mock implements _i2.Foo')); + 'class MockFooOfIntBar extends _i1.Mock implements _i2.Foo')); }); test('generates a generic mock class with type arguments but no name', From 875ac49aaafdeff50463e13a65b8ee3238daa26a Mon Sep 17 00:00:00 2001 From: James Lin Date: Thu, 15 Apr 2021 20:05:04 -0700 Subject: [PATCH 329/607] Fix some issues with the mockito README.md * The example uses code-generation but doesn't use nullable types. (I suppose this might be technically okay, but I'm skeptical that anyone would use code-generation without null-safety.) * Use `dart run` intead of `pub run` to perform code generation. * Remove references to unstubbed methods returning null by default. * Fix an example that compared a `List` element to a `List`. * Fix a broken link. * Consistently use the American English spelling of "behavior". --- pkgs/mockito/README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 91bd25a87..5f6b063ae 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -26,7 +26,7 @@ import 'cat.mocks.dart'; // Real class class Cat { String sound() => "Meow"; - bool eatFood(String food, {bool hungry}) => true; + bool eatFood(String food, {bool? hungry}) => true; Future chew() async => print("Chewing..."); int walk(List places) => 7; void sleep() {} @@ -49,7 +49,7 @@ write a mock class for each "real" class listed, in a new library. The next step is to run `build_runner` in order to generate this new library: ```shell -pub run build_runner build +dart run build_runner build ``` `build_runner` will generate a file with a name based on the file containing the @@ -61,7 +61,7 @@ the Cat class, giving us a class which supports stubbing and verifying. [NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md -## Let's verify some behaviour! +## Let's verify some behavior! ```dart // Interact with the mock object. @@ -77,9 +77,6 @@ interactions you are interested in. ## How about some stubbing? ```dart -// Unstubbed methods return null. -expect(cat.sound(), nullValue); - // Stub a mock method before interacting. when(cat.sound()).thenReturn("Purr"); expect(cat.sound(), "Purr"); @@ -106,8 +103,7 @@ expect(cat.sound(), "Purr"); expect(cat.sound(), "Meow"); ``` -By default, any instance method of the mock instance returns `null`. The -[`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a +The [`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a stubbing mechanism to override this behavior. Once stubbed, the method will always return stubbed value regardless of how many times it is called. If a method invocation matches multiple stubs, the one which was declared last will @@ -286,7 +282,7 @@ Use the [`captureAny`], [`captureThat`], and [`captureNamed`] argument matchers: ```dart // Simple capture cat.eatFood("Fish"); -expect(verify(cat.eatFood(captureAny)).captured.single, ["Fish"]); +expect(verify(cat.eatFood(captureAny)).captured.single, "Fish"); // Capture multiple calls cat.eatFood("Milk"); @@ -325,7 +321,7 @@ the Fake class implements the default behavior of throwing [UnimplementedError] // Fake class class FakeCat extends Fake implements Cat { @override - bool eatFood(String food, {bool hungry}) { + bool eatFood(String food, {bool? hungry}) { print('Fake eat $food'); return true; } @@ -420,5 +416,5 @@ Read more information about this package in the [Fake]: https://pub.dev/documentation/mockito/latest/mockito/Fake-class.html [UnimplementedError]: https://api.dartlang.org/stable/dart-core/UnimplementedError-class.html [`reset`]: https://pub.dev/documentation/mockito/latest/mockito/reset.html -[`logInvocations]: https://pub.dev/documentation/mockito/latest/mockito/logInvocations.html +[`logInvocations`]: https://pub.dev/documentation/mockito/latest/mockito/logInvocations.html [`throwOnMissingStub`]: https://pub.dev/documentation/mockito/latest/mockito/throwOnMissingStub.html From 1c924ce67d93f64caa853e0a1ec1d9a285b1ba39 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Apr 2021 15:44:23 -0700 Subject: [PATCH 330/607] Update setup-dart CI version --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index b7b644ff6..f63563bb4 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 + - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 + - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} - id: install @@ -73,7 +73,7 @@ jobs: os: [ubuntu-latest] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 + - uses: dart-lang/setup-dart@v1.0 with: sdk: dev - id: install From c2d550976263bfd9dea451c4c9ab96a99f4baea4 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 19 Apr 2021 17:51:54 -0700 Subject: [PATCH 331/607] Support code_builder 4.x and bump mockito to 5.0.5 (dart-lang/mockito#387) PiperOrigin-RevId: 369312175 Co-authored-by: srawlins --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 0d7afb4af..cc322ac3d 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.5 + +* Support 4.x releases of code_builder. + ## 5.0.4 * Allow calling methods with void return types w/o stubbing. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 2c9f52fd6..d1809d2fa 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.4'; +const packageVersion = '5.0.5'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 543d0b679..7537f178e 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.4 +version: 5.0.5 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -10,7 +10,7 @@ environment: dependencies: analyzer: ^1.0.0 build: '>=1.3.0 <3.0.0' - code_builder: ^3.7.0 + code_builder: '>=3.7.0 <5.0.0' collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 From 2ba21e032a0b16461bd95d72a28994446fe39bd1 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 21 Apr 2021 12:16:55 -0400 Subject: [PATCH 332/607] Add details to verifyInOrder's new error. This should help users identify which element in a list of supposed stub call, doesn't belong. PiperOrigin-RevId: 369669195 --- pkgs/mockito/lib/src/mock.dart | 4 +++- pkgs/mockito/test/verify_test.dart | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 44bec8421..b90d0363d 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -999,7 +999,9 @@ _InOrderVerification get verifyInOrder { _verificationInProgress = true; return (List responses) { if (responses.length != _verifyCalls.length) { - fail('Used on a non-mockito object'); + fail("'verifyInOrder' called with non-mockito stub calls; List contains " + '${responses.length} elements, but ${_verifyCalls.length} stub calls ' + 'were stored: $_verifyCalls'); } _verificationInProgress = false; var verificationResults = []; diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 659fe0733..31069d573 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -209,7 +209,7 @@ void main() { test('should fail when called with non-mock-call parameter', () { expectFail('Used on a non-mockito object', () { - verifyInOrder(['a string is not a mock call']); + verify(['a string is not a mock call']); }); }); }); @@ -464,8 +464,20 @@ void main() { }); test('should fail when given non-mock-call parameters', () { - expectFail('Used on a non-mockito object', () { - verifyInOrder(['a string is not a mock call']); + expectFail( + RegExp("'verifyInOrder' called with non-mockito stub calls; List " + 'contains 3 elements, but 2 stub calls were stored'), () { + verifyInOrder([mock.getter, mock.methodWithoutArgs, mock.getter]); + }); + }); + + test('should fail when parameters do not map to List elements', () { + expectFail( + RegExp("'verifyInOrder' called with non-mockito stub calls; List " + 'contains 1 elements, but 2 stub calls were stored'), () { + verifyInOrder([ + [mock.getter, mock.getter] + ]); }); }); From ffe23e998c2875baebc3068d58484277e1b2b5a5 Mon Sep 17 00:00:00 2001 From: nbosch Date: Thu, 22 Apr 2021 01:44:07 -0400 Subject: [PATCH 333/607] Support the latest test_api The changes in version 0.4.0 are not breaking for mockito's usage. PiperOrigin-RevId: 369805002 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index cc322ac3d..130041c73 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,5 +1,9 @@ ## 5.0.5 +* Support the 0.4.x releases of `test_api`. + +## 5.0.5 + * Support 4.x releases of code_builder. ## 5.0.4 diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index d1809d2fa..99384576f 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.5'; +const packageVersion = '5.0.6'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 7537f178e..52ec5c24a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.5 +version: 5.0.6 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -17,7 +17,7 @@ dependencies: meta: ^1.3.0 path: ^1.8.0 source_gen: '>=0.9.6 <2.0.0' - test_api: '>=0.2.1 <0.4.0' + test_api: '>=0.2.1 <0.5.0' dev_dependencies: build_runner: ^1.12.0 From ca512c643edac29a192b2c3cb2a16fed69e27a2d Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 22 Apr 2021 23:47:46 -0400 Subject: [PATCH 334/607] Fix version number in CHANGELOG. PiperOrigin-RevId: 370006741 --- pkgs/mockito/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 130041c73..4339bdab5 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.5 +## 5.0.6 * Support the 0.4.x releases of `test_api`. From 2cacfcdcb2a8f300d757d581ead2c2f7d3ea9898 Mon Sep 17 00:00:00 2001 From: Yerzhan Tulepov Date: Mon, 26 Apr 2021 23:59:42 +0600 Subject: [PATCH 335/607] Add prefer_const_constructors and avoid_redundant_argument_values to ignore_for_file --- pkgs/mockito/lib/src/builder.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b14580466..cb8d427f6 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -78,6 +78,9 @@ class MockBuilder implements Builder { b.body.add(Code('\n\n// ignore_for_file: comment_references\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n\n')); + b.body.add(Code('// ignore_for_file: prefer_const_constructors\n\n')); + b.body + .add(Code('// ignore_for_file: avoid_redundant_argument_values\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); From 2da11ca5ab548b6053e98700d1ecb05523094d07 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 28 Apr 2021 13:04:43 -0400 Subject: [PATCH 336/607] Properly collect types in type parameter bounds. Fixes https://github.com/dart-lang/mockito/issues/389 We already track type parameter bounds for classes being mocked. This change allows for classes which need to be Faked to be properly written as well, with import prefixes on imported types. PiperOrigin-RevId: 370927872 --- pkgs/mockito/lib/src/builder.dart | 6 +- .../mockito/test/builder/auto_mocks_test.dart | 59 +++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index cb8d427f6..c3f6afc76 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -201,8 +201,12 @@ class _TypeVisitor extends RecursiveElementVisitor { if (type == null) return; if (type is analyzer.InterfaceType) { + final alreadyVisitedElement = _elements.contains(type.element); _elements.add(type.element); (type.typeArguments ?? []).forEach(_addType); + if (!alreadyVisitedElement) { + (type.element.typeParameters ?? []).forEach(visitTypeParameterElement); + } } else if (type is analyzer.FunctionType) { _addType(type.returnType); @@ -1309,7 +1313,7 @@ class _MockLibraryInfo { if (element?.library == null) return null; assert(assetUris.containsKey(element), - () => 'An element, "$element", is missing from the asset URI mapping'); + 'An element, "$element", is missing from the asset URI mapping'); return assetUris[element]; } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index e1e53f4d7..2daa0766a 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -18,6 +18,7 @@ import 'dart:convert' show utf8; import 'package:build/build.dart'; +import 'package:build/experiments.dart'; import 'package:build_test/build_test.dart'; import 'package:meta/meta.dart'; import 'package:mockito/src/builder.dart'; @@ -95,10 +96,14 @@ void main() { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 12)) + languageVersion: LanguageVersion(2, 13)) ]); - await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, - writer: writer, outputs: outputs, packageConfig: packageConfig); + await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, outputs: outputs, packageConfig: packageConfig), + ['nonfunction-type-aliases'], + ); } /// Builds with [MockBuilder] in a package which has opted into null safety, @@ -2003,7 +2008,7 @@ void main() { await expectSingleNonNullableOutput( dedent(r''' class Foo { - Bar m1() => Bar('name1'); + Bar m1() => Bar(); } class Bar {} '''), @@ -2012,6 +2017,52 @@ void main() { ); }); + test('generates a fake, bounded generic class used in return values', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Baz {} + class Bar {} + class Foo { + Bar m1() => Bar(); + } + '''), + _containsAllOf( + 'class _FakeBar extends _i2.Fake implements _i1.Bar {}'), + ); + }); + + test('generates a fake, aliased class used in return values', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Baz {} + class Bar {} + typedef BarOfBaz = Bar; + class Foo { + BarOfBaz m1() => Bar(); + } + '''), + _containsAllOf( + 'class _FakeBar extends _i2.Fake implements _i1.Bar {}'), + ); + }); + + test( + 'generates a fake, recursively bounded generic class used in return values', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Baz> {} + class Bar {} + class Foo { + Bar m1() => Bar(); + } + '''), + _containsAllOf( + 'class _FakeBar extends _i1.Fake implements _i2.Bar {}'), + ); + }); + test('deduplicates fake classes', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { From 97dd73208a4f2f1464b4103e2c41c0ad72248b31 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 28 Apr 2021 14:35:48 -0400 Subject: [PATCH 337/607] Do not use private type alias names in generated code. We prefer generally to use type alias names in order to match the user's code; extracting out the type signature may surprise users. However, given a private type alias, we can recover the situation by extracting out the type signature. Fixes https://github.com/dart-lang/mockito/issues/396. PiperOrigin-RevId: 370949384 --- pkgs/mockito/CHANGELOG.md | 9 +++ pkgs/mockito/lib/src/builder.dart | 7 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 67 +++++++++++++++++-- 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 4339bdab5..57fb6dc84 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,12 @@ +## 5.0.7 + +* Properly refer to type parameter bounds with import prefixes. + [#389](https://github.com/dart-lang/mockito/issues/389) +* Stop referring to private typedefs in generated code. + [#396](https://github.com/dart-lang/mockito/issues/396) +* Ignore `prefer_const_constructors` and `avoid_redundant_argument_values` lint + rule violations in generated code. + ## 5.0.6 * Support the 0.4.x releases of `test_api`. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index c3f6afc76..bc4480db9 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1259,9 +1259,10 @@ class _MockLibraryInfo { ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { - var element = type.aliasElement; - if (element == null) { - // [type] represents a FunctionTypedFormalParameter. + final element = type.aliasElement; + if (element == null || element.isPrivate) { + // [type] does not refer to a type alias, or it refers to a private type + // alias; we must instead write out its signature. return FunctionType((b) { b ..isNullable = diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 99384576f..5fd5c6778 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.6'; +const packageVersion = '5.0.7'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 52ec5c24a..607cf6a40 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.6 +version: 5.0.7 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 2daa0766a..8ca7e709e 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1925,6 +1925,22 @@ void main() { ); }); + test( + 'creates a dummy non-null function-typed return value, with private type ' + 'alias', () async { + await expectSingleNonNullableOutput( + dedent(r''' + typedef _Callback = Foo Function(); + class Foo { + _Callback m() => () => Foo(); + } + '''), + _containsAllOf( + '_i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []),\n' + ' returnValue: () => _FakeFoo()) as _i2.Foo Function());'), + ); + }); + test('creates a dummy non-null generic function-typed return value', () async { await expectSingleNonNullableOutput( @@ -1959,8 +1975,8 @@ void main() { }); test( - 'creates a dummy non-null function-typed (with an imported parameter type) return value', - () async { + 'creates a dummy non-null function-typed (with an imported parameter ' + 'type) return value', () async { await expectSingleNonNullableOutput( dedent(r''' import 'dart:io'; @@ -1976,8 +1992,8 @@ void main() { }); test( - 'creates a dummy non-null function-typed (with an imported return type) return value', - () async { + 'creates a dummy non-null function-typed (with an imported return type) ' + 'return value', () async { await expectSingleNonNullableOutput( dedent(r''' import 'dart:io'; @@ -2121,6 +2137,49 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'type alias return type which refers to private types', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + Callback m(int a); + } + class _Bar {} + typedef Callback = Function(_Bar?); + '''), + }, + message: contains( + "The method 'Foo.m' features a private parameter type, '_Bar', and " + 'cannot be stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a method with a ' + 'private type alias parameter type which refers to private types', + () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + void m(_Callback c); + } + class _Bar {} + typedef _Callback = Function(_Bar?); + '''), + }, + message: contains( + "The method 'Foo.m' features a private parameter type, '_Bar', and " + 'cannot be stubbed.'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a return ' 'type with private type arguments', () async { From ad2462e54543cfa3450d242d020f8e8996756c8a Mon Sep 17 00:00:00 2001 From: Yoshihiro Tanaka Date: Sat, 8 May 2021 00:54:16 +0900 Subject: [PATCH 338/607] Fix example tests (dart-lang/mockito#392) --- pkgs/mockito/build.yaml | 1 + pkgs/mockito/example/example.dart | 46 +++++++++++++++++++++++--- pkgs/mockito/example/iss/iss_test.dart | 6 ++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index 2120fc9fb..baa5dc23e 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -3,6 +3,7 @@ targets: builders: mockito|mockBuilder: generate_for: + - example/**.dart - test/end2end/*.dart builders: diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 9e63a1173..ffc208864 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -1,8 +1,11 @@ // ignore_for_file: sdk_version_async_exported_from_core // ignore_for_file: unawaited_futures +import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; +import 'example.mocks.dart'; + // Real class class Cat { String? sound() => 'Meow'; @@ -14,9 +17,6 @@ class Cat { int lives = 9; } -// Mock class -class MockCat extends Mock implements Cat {} - // Fake class class FakeCat extends Fake implements Cat { @override @@ -26,6 +26,11 @@ class FakeCat extends Fake implements Cat { } } +@GenerateMocks([ + Cat +], customMocks: [ + MockSpec(as: #MockCatRelaxed, returnNullOnMissingStub: true), +]) void main() { late Cat cat; @@ -35,6 +40,9 @@ void main() { }); test("Let's verify some behaviour!", () { + // Stub a method before interacting with it. + when(cat.sound()).thenReturn('Meow'); + // Interact with the mock object. cat.sound(); @@ -43,8 +51,11 @@ void main() { }); test('How about some stubbing?', () { - // Unstubbed methods return null. - expect(cat.sound(), null); + try { + cat.sound(); + } on MissingStubError { + // Unstubbed methods throw MissingStubError. + } // Stub a method before interacting with it. when(cat.sound()).thenReturn('Purr'); @@ -118,6 +129,8 @@ void main() { }); test('Verifying exact number of invocations / at least x / never', () { + when(cat.sound()).thenReturn('Meow'); + cat.sound(); cat.sound(); // Exact number of invocations @@ -134,6 +147,9 @@ void main() { }); test('Verification in order', () { + when(cat.sound()).thenReturn('Meow'); + when(cat.eatFood(any)).thenReturn(true); + cat.eatFood('Milk'); cat.sound(); cat.eatFood('Fish'); @@ -145,12 +161,16 @@ void main() { }); test('Finding redundant invocations', () { + when(cat.sound()).thenReturn('Meow'); + cat.sound(); verify(cat.sound()); verifyNoMoreInteractions(cat); }); test('Capturing arguments for further assertions', () { + when(cat.eatFood(any)).thenReturn(true); + // Simple capture: cat.eatFood('Fish'); expect(verify(cat.eatFood(captureAny)).captured.single, 'Fish'); @@ -168,6 +188,8 @@ void main() { }); test('Waiting for an interaction', () async { + when(cat.eatFood(any)).thenReturn(true); + Future chewHelper(Cat cat) { return cat.chew(); } @@ -188,4 +210,18 @@ void main() { cat.eatFood('Milk'); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); }); + + test('Relaxed mock class', () { + // Create a new mock Cat at runtime. + var cat = MockCatRelaxed(); + + // You can call it without stubbing. + cat.sleep(); + + // Returns null unless you stub it. + expect(cat.sound(), null); + expect(cat.eatFood('Milk'), null); + + verify(cat.sleep()); + }); } diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index dc4f3cd86..2ec901a0c 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -14,14 +14,14 @@ import 'dart:math'; +import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'iss.dart'; +import 'iss_test.mocks.dart'; -// The Mock class uses noSuchMethod to catch all method invocations. -class MockIssLocator extends Mock implements IssLocator {} - +@GenerateMocks([IssLocator]) void main() { // Given two predefined points on earth, // verify the calculated distance between them. From ae2780111a12a1e1f2ededf66eebc1694e0d8ed7 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 6 May 2021 14:57:01 -0400 Subject: [PATCH 339/607] Migrate mockito codegen to null safety. PiperOrigin-RevId: 372392706 --- pkgs/mockito/CHANGELOG.md | 4 + pkgs/mockito/bin/codegen.dart | 2 - pkgs/mockito/build.yaml | 1 - pkgs/mockito/example/example.dart | 46 +---- pkgs/mockito/example/iss/iss_test.dart | 6 +- pkgs/mockito/lib/src/builder.dart | 175 +++++++++--------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 +- pkgs/mockito/test/all.dart | 2 - .../mockito/test/builder/auto_mocks_test.dart | 34 ++-- .../test/builder/custom_mocks_test.dart | 20 +- 11 files changed, 125 insertions(+), 171 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 57fb6dc84..2eff7c649 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.0.8-dev + +* Migrate Mockito codegen to null safety. + ## 5.0.7 * Properly refer to type parameter bounds with import prefixes. diff --git a/pkgs/mockito/bin/codegen.dart b/pkgs/mockito/bin/codegen.dart index 998362493..e216c351e 100644 --- a/pkgs/mockito/bin/codegen.dart +++ b/pkgs/mockito/bin/codegen.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - import 'package:build/build.dart'; import 'package:mockito/src/builder.dart' as b; diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index baa5dc23e..2120fc9fb 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -3,7 +3,6 @@ targets: builders: mockito|mockBuilder: generate_for: - - example/**.dart - test/end2end/*.dart builders: diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index ffc208864..9e63a1173 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -1,11 +1,8 @@ // ignore_for_file: sdk_version_async_exported_from_core // ignore_for_file: unawaited_futures -import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; -import 'example.mocks.dart'; - // Real class class Cat { String? sound() => 'Meow'; @@ -17,6 +14,9 @@ class Cat { int lives = 9; } +// Mock class +class MockCat extends Mock implements Cat {} + // Fake class class FakeCat extends Fake implements Cat { @override @@ -26,11 +26,6 @@ class FakeCat extends Fake implements Cat { } } -@GenerateMocks([ - Cat -], customMocks: [ - MockSpec(as: #MockCatRelaxed, returnNullOnMissingStub: true), -]) void main() { late Cat cat; @@ -40,9 +35,6 @@ void main() { }); test("Let's verify some behaviour!", () { - // Stub a method before interacting with it. - when(cat.sound()).thenReturn('Meow'); - // Interact with the mock object. cat.sound(); @@ -51,11 +43,8 @@ void main() { }); test('How about some stubbing?', () { - try { - cat.sound(); - } on MissingStubError { - // Unstubbed methods throw MissingStubError. - } + // Unstubbed methods return null. + expect(cat.sound(), null); // Stub a method before interacting with it. when(cat.sound()).thenReturn('Purr'); @@ -129,8 +118,6 @@ void main() { }); test('Verifying exact number of invocations / at least x / never', () { - when(cat.sound()).thenReturn('Meow'); - cat.sound(); cat.sound(); // Exact number of invocations @@ -147,9 +134,6 @@ void main() { }); test('Verification in order', () { - when(cat.sound()).thenReturn('Meow'); - when(cat.eatFood(any)).thenReturn(true); - cat.eatFood('Milk'); cat.sound(); cat.eatFood('Fish'); @@ -161,16 +145,12 @@ void main() { }); test('Finding redundant invocations', () { - when(cat.sound()).thenReturn('Meow'); - cat.sound(); verify(cat.sound()); verifyNoMoreInteractions(cat); }); test('Capturing arguments for further assertions', () { - when(cat.eatFood(any)).thenReturn(true); - // Simple capture: cat.eatFood('Fish'); expect(verify(cat.eatFood(captureAny)).captured.single, 'Fish'); @@ -188,8 +168,6 @@ void main() { }); test('Waiting for an interaction', () async { - when(cat.eatFood(any)).thenReturn(true); - Future chewHelper(Cat cat) { return cat.chew(); } @@ -210,18 +188,4 @@ void main() { cat.eatFood('Milk'); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); }); - - test('Relaxed mock class', () { - // Create a new mock Cat at runtime. - var cat = MockCatRelaxed(); - - // You can call it without stubbing. - cat.sleep(); - - // Returns null unless you stub it. - expect(cat.sound(), null); - expect(cat.eatFood('Milk'), null); - - verify(cat.sleep()); - }); } diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index 2ec901a0c..dc4f3cd86 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -14,14 +14,14 @@ import 'dart:math'; -import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'iss.dart'; -import 'iss_test.mocks.dart'; -@GenerateMocks([IssLocator]) +// The Mock class uses noSuchMethod to catch all method invocations. +class MockIssLocator extends Mock implements IssLocator {} + void main() { // Given two predefined points on earth, // verify the calculated distance between them. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index bc4480db9..c21783114 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; @@ -28,8 +26,8 @@ import 'package:build/build.dart'; // provides a [refer] function and a [referBasic] function. The former requires // a URL to be passed. import 'package:code_builder/code_builder.dart' hide refer; +import 'package:collection/collection.dart'; import 'package:dart_style/dart_style.dart'; -import 'package:meta/meta.dart'; import 'package:mockito/src/version.dart'; import 'package:path/path.dart' as p; import 'package:source_gen/source_gen.dart'; @@ -112,7 +110,7 @@ $rawOutput seenTypes.add(type); type.element.accept(typeVisitor); // For a type like `Foo`, add the `Bar`. - (type.typeArguments ?? []) + type.typeArguments .whereType() .forEach(addTypesFrom); // For a type like `Foo extends Bar`, add the `Baz`. @@ -128,13 +126,14 @@ $rawOutput final typeUris = {}; for (var element in typeVisitor._elements) { - if (element.library.isInSdk) { - typeUris[element] = element.library.source.uri.toString(); + final elementLibrary = element.library!; + if (elementLibrary.isInSdk) { + typeUris[element] = elementLibrary.source.uri.toString(); continue; } try { - var typeAssetId = await resolver.assetIdForElement(element.library); + final typeAssetId = await resolver.assetIdForElement(elementLibrary); if (typeAssetId.path.startsWith('lib/')) { typeUris[element] = typeAssetId.uri.toString(); @@ -144,7 +143,7 @@ $rawOutput } } on UnresolvableAssetException { // Asset may be in a summary. - typeUris[element] = element.library.source.uri.toString(); + typeUris[element] = elementLibrary.source.uri.toString(); continue; } } @@ -185,7 +184,7 @@ class _TypeVisitor extends RecursiveElementVisitor { void visitParameterElement(ParameterElement element) { _addType(element.type); if (element.hasDefaultValue) { - _addTypesFromConstant(element.computeConstantValue()); + _addTypesFromConstant(element.computeConstantValue()!); } super.visitParameterElement(element); } @@ -197,15 +196,15 @@ class _TypeVisitor extends RecursiveElementVisitor { } /// Adds [type] to the collected [_elements]. - void _addType(analyzer.DartType type) { + void _addType(analyzer.DartType? type) { if (type == null) return; if (type is analyzer.InterfaceType) { final alreadyVisitedElement = _elements.contains(type.element); _elements.add(type.element); - (type.typeArguments ?? []).forEach(_addType); + type.typeArguments.forEach(_addType); if (!alreadyVisitedElement) { - (type.element.typeParameters ?? []).forEach(visitTypeParameterElement); + type.element.typeParameters.forEach(visitTypeParameterElement); } } else if (type is analyzer.FunctionType) { _addType(type.returnType); @@ -248,11 +247,11 @@ class _TypeVisitor extends RecursiveElementVisitor { } } else if (constant.isMap) { for (var pair in constant.mapValue.entries) { - _addTypesFromConstant(pair.key); - _addTypesFromConstant(pair.value); + _addTypesFromConstant(pair.key!); + _addTypesFromConstant(pair.value!); } } else if (object.toFunctionValue() != null) { - _elements.add(object.toFunctionValue()); + _elements.add(object.toFunctionValue()!); } else { // If [constant] is not null, a literal, or a type, then it must be an // object constructed with `const`. Revive it. @@ -277,7 +276,8 @@ class _MockTarget { final bool returnNullOnMissingStub; - _MockTarget(this.classType, this.mockName, {this.returnNullOnMissingStub}); + _MockTarget(this.classType, this.mockName, + {required this.returnNullOnMissingStub}); ClassElement get classElement => classType.element; } @@ -305,7 +305,7 @@ class _MockTargetGatherer { for (final annotation in element.metadata) { if (annotation == null) continue; if (annotation.element is! ConstructorElement) continue; - final annotationClass = annotation.element.enclosingElement.name; + final annotationClass = annotation.element!.enclosingElement!.name; // TODO(srawlins): check library as well. if (annotationClass == 'GenerateMocks') { mockTargets @@ -319,15 +319,15 @@ class _MockTargetGatherer { static Iterable<_MockTarget> _mockTargetsFromGenerateMocks( ElementAnnotation annotation, LibraryElement entryLib) { - final generateMocksValue = annotation.computeConstantValue(); - final classesField = generateMocksValue.getField('classes'); + final generateMocksValue = annotation.computeConstantValue()!; + final classesField = generateMocksValue.getField('classes')!; if (classesField.isNull) { throw InvalidMockitoAnnotationException( 'The GenerateMocks "classes" argument is missing, includes an ' 'unknown type, or includes an extension'); } final mockTargets = <_MockTarget>[]; - for (var objectToMock in classesField.toListValue()) { + for (var objectToMock in classesField.toListValue()!) { final typeToMock = objectToMock.toTypeValue(); if (typeToMock == null) { throw InvalidMockitoAnnotationException( @@ -348,8 +348,8 @@ class _MockTargetGatherer { } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { - for (var mockSpec in customMocksField.toListValue()) { - final mockSpecType = mockSpec.type; + for (var mockSpec in customMocksField.toListValue()!) { + final mockSpecType = mockSpec.type!; assert(mockSpecType.typeArguments.length == 1); final typeToMock = mockSpecType.typeArguments.single; if (typeToMock.isDynamic) { @@ -358,10 +358,10 @@ class _MockTargetGatherer { 'arguments on MockSpec(), in @GenerateMocks.'); } var type = _determineDartType(typeToMock, entryLib.typeProvider); - final mockName = mockSpec.getField('mockName').toSymbolValue() ?? + final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? 'Mock${type.element.name}'; final returnNullOnMissingStub = - mockSpec.getField('returnNullOnMissingStub').toBoolValue(); + mockSpec.getField('returnNullOnMissingStub')!.toBoolValue()!; mockTargets.add(_MockTarget(type, mockName, returnNullOnMissingStub: returnNullOnMissingStub)); } @@ -427,11 +427,11 @@ class _MockTargetGatherer { for (final mockTarget in _mockTargets) { var name = mockTarget.mockName; if (classNamesToMock.containsKey(name)) { - var firstSource = classNamesToMock[name].source.fullName; + var firstSource = classNamesToMock[name]!.source.fullName; var secondSource = mockTarget.classElement.source.fullName; throw InvalidMockitoAnnotationException( 'Mockito cannot generate two mocks with the same name: $name (for ' - '${classNamesToMock[name].name} declared in $firstSource, and for ' + '${classNamesToMock[name]!.name} declared in $firstSource, and for ' '${mockTarget.classElement.name} declared in $secondSource); ' '$uniqueNameSuggestion.'); } @@ -439,8 +439,8 @@ class _MockTargetGatherer { } classNamesToMock.forEach((name, element) { - var conflictingClass = classesInEntryLib.firstWhere((c) => c.name == name, - orElse: () => null); + var conflictingClass = + classesInEntryLib.firstWhereOrNull((c) => c.name == name); if (conflictingClass != null) { throw InvalidMockitoAnnotationException( 'Mockito cannot generate a mock with a name which conflicts with ' @@ -448,11 +448,9 @@ class _MockTargetGatherer { '$uniqueNameSuggestion.'); } - var preexistingMock = classesInEntryLib.firstWhere( - (c) => - c.interfaces.map((type) => type.element).contains(element) && - _isMockClass(c.supertype), - orElse: () => null); + var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => + c.interfaces.map((type) => type.element).contains(element) && + _isMockClass(c.supertype!)); if (preexistingMock != null) { throw InvalidMockitoAnnotationException( 'The GenerateMocks annotation contains a class which appears to ' @@ -500,7 +498,7 @@ class _MockTargetGatherer { var errorMessages = []; var returnType = function.returnType; if (returnType is analyzer.InterfaceType) { - if (returnType.element?.isPrivate ?? false) { + if (returnType.element.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private return type, and ' 'cannot be stubbed.'); @@ -522,7 +520,7 @@ class _MockTargetGatherer { var parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { var parameterTypeElement = parameterType.element; - if (parameterTypeElement?.isPrivate ?? false) { + if (parameterTypeElement.isPrivate) { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not // generate such a mock. @@ -558,7 +556,7 @@ class _MockTargetGatherer { var typeParameter = element.bound; if (typeParameter == null) continue; if (typeParameter is analyzer.InterfaceType) { - if (typeParameter.element?.isPrivate ?? false) { + if (typeParameter.element.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type parameter ' 'bound, and cannot be stubbed.'); @@ -578,7 +576,7 @@ class _MockTargetGatherer { var errorMessages = []; for (var typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { - if (typeArgument.element?.isPrivate ?? false) { + if (typeArgument.element.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type argument, ' 'and cannot be stubbed.'); @@ -626,9 +624,11 @@ class _MockLibraryInfo { final Map assetUris; /// Build mock classes for [mockTargets]. - _MockLibraryInfo(Iterable<_MockTarget> mockTargets, - {this.assetUris, LibraryElement entryLib}) - : sourceLibIsNonNullable = entryLib.isNonNullableByDefault, + _MockLibraryInfo( + Iterable<_MockTarget> mockTargets, { + required this.assetUris, + required LibraryElement entryLib, + }) : sourceLibIsNonNullable = entryLib.isNonNullableByDefault, typeProvider = entryLib.typeProvider, typeSystem = entryLib.typeSystem { for (final mockTarget in mockTargets) { @@ -699,7 +699,7 @@ class _MockLibraryInfo { // `class MockFoo extends Mock implements Foo {}` for (var typeArgument in typeToMock.typeArguments) { typeArguments.add(referImported( - typeArgument.element.name, _typeImport(typeArgument.element))); + typeArgument.element!.name!, _typeImport(typeArgument.element))); } } else if (classToMock.typeParameters != null) { // [typeToMock] is a simple reference to a generic type (for example: @@ -842,7 +842,7 @@ class _MockLibraryInfo { /// This new method just calls `super.noSuchMethod`, optionally passing a /// return value for methods with a non-nullable return type. void _buildOverridingMethod(MethodBuilder builder, MethodElement method, - {@required String className}) { + {required String className}) { var name = method.displayName; if (method.isOperator) name = 'operator$name'; builder @@ -882,7 +882,7 @@ class _MockLibraryInfo { if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs), ]); - Expression returnValueForMissingStub; + Expression? returnValueForMissingStub; if (method.returnType.isVoid) { returnValueForMissingStub = refer('null'); } else if (method.returnType.isFutureOfVoid) { @@ -902,9 +902,7 @@ class _MockLibraryInfo { superNoSuchMethod.asA(_typeReference(method.returnType)); } - builder - ..lambda = true - ..body = superNoSuchMethod.code; + builder.body = superNoSuchMethod.code; } Expression _dummyValue(analyzer.DartType type) { @@ -917,39 +915,36 @@ class _MockLibraryInfo { return literalNull; } - var interfaceType = type as analyzer.InterfaceType; - var typeArguments = interfaceType.typeArguments; - if (interfaceType.isDartCoreBool) { + var typeArguments = type.typeArguments; + if (type.isDartCoreBool) { return literalFalse; - } else if (interfaceType.isDartCoreDouble) { + } else if (type.isDartCoreDouble) { return literalNum(0.0); - } else if (interfaceType.isDartAsyncFuture || - interfaceType.isDartAsyncFutureOr) { + } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { var typeArgument = typeArguments.first; return _futureReference(_typeReference(typeArgument)) .property('value') .call([_dummyValue(typeArgument)]); - } else if (interfaceType.isDartCoreInt) { + } else if (type.isDartCoreInt) { return literalNum(0); - } else if (interfaceType.isDartCoreIterable) { + } else if (type.isDartCoreIterable) { return literalList([]); - } else if (interfaceType.isDartCoreList) { + } else if (type.isDartCoreList) { assert(typeArguments.length == 1); var elementType = _typeReference(typeArguments[0]); return literalList([], elementType); - } else if (interfaceType.isDartCoreMap) { + } else if (type.isDartCoreMap) { assert(typeArguments.length == 2); var keyType = _typeReference(typeArguments[0]); var valueType = _typeReference(typeArguments[1]); return literalMap({}, keyType, valueType); - } else if (interfaceType.isDartCoreNum) { + } else if (type.isDartCoreNum) { return literalNum(0); - } else if (interfaceType.isDartCoreSet) { + } else if (type.isDartCoreSet) { assert(typeArguments.length == 1); var elementType = _typeReference(typeArguments[0]); return literalSet({}, elementType); - } else if (interfaceType.element?.declaration == - typeProvider.streamElement) { + } else if (type.element.declaration == typeProvider.streamElement) { assert(typeArguments.length == 1); var elementType = _typeReference(typeArguments[0]); return TypeReference((b) { @@ -957,18 +952,18 @@ class _MockLibraryInfo { ..symbol = 'Stream' ..types.add(elementType); }).property('empty').call([]); - } else if (interfaceType.isDartCoreString) { + } else if (type.isDartCoreString) { return literalString(''); } // This class is unknown; we must likely generate a fake class, and return // an instance here. - return _dummyValueImplementing(type as analyzer.InterfaceType); + return _dummyValueImplementing(type); } /// Returns a reference to [Future], optionally with a type argument for the /// value of the Future. - TypeReference _futureReference([Reference valueType]) => TypeReference((b) { + TypeReference _futureReference([Reference? valueType]) => TypeReference((b) { b.symbol = 'Future'; if (valueType != null) { b.types.add(valueType); @@ -1056,8 +1051,13 @@ class _MockLibraryInfo { /// If the type needs to be nullable, rather than matching the nullability of /// [parameter], use [forceNullable]. Parameter _matchingParameter(ParameterElement parameter, - {String defaultName, bool forceNullable = false}) { - var name = parameter.name?.isEmpty ?? false ? defaultName : parameter.name; + {String? defaultName, bool forceNullable = false}) { + assert( + parameter.name.isNotEmpty || defaultName != null, + 'parameter must have a non-empty name, or non-null defaultName must be ' + 'passed, but parameter name is "${parameter.name}" and defaultName is ' + '$defaultName'); + var name = parameter.name.isEmpty ? defaultName! : parameter.name; return Parameter((pBuilder) { pBuilder ..name = name @@ -1066,10 +1066,10 @@ class _MockLibraryInfo { if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = - _expressionFromDartObject(parameter.computeConstantValue()).code; + _expressionFromDartObject(parameter.computeConstantValue()!).code; } on _ReviveException catch (e) { - final method = parameter.enclosingElement; - final clazz = method.enclosingElement; + final method = parameter.enclosingElement!; + final clazz = method.enclosingElement!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid stub for method ' "'${clazz.displayName}.${method.displayName}'; parameter " @@ -1104,8 +1104,8 @@ class _MockLibraryInfo { } else if (constant.isMap) { return literalConstMap({ for (var pair in constant.mapValue.entries) - _expressionFromDartObject(pair.key): - _expressionFromDartObject(pair.value) + _expressionFromDartObject(pair.key!): + _expressionFromDartObject(pair.value!) }); } else if (constant.isSet) { return literalConstSet({ @@ -1115,7 +1115,7 @@ class _MockLibraryInfo { } else if (constant.isType) { // TODO(srawlins): It seems like this might be revivable, but Angular // does not revive Types; we should investigate this if users request it. - var type = object.toTypeValue(); + var type = object.toTypeValue()!; var typeStr = type.getDisplayString(withNullability: false); throw _ReviveException('default value is a Type: $typeStr.'); } else { @@ -1123,7 +1123,7 @@ class _MockLibraryInfo { // object constructed with `const`. Revive it. var revivable = constant.revive(); if (revivable.isPrivate) { - final privateReference = revivable.accessor?.isNotEmpty == true + final privateReference = revivable.accessor.isNotEmpty ? '${revivable.source}::${revivable.accessor}' : '${revivable.source}'; throw _ReviveException( @@ -1138,7 +1138,7 @@ class _MockLibraryInfo { // We can create this invocation by referring to a const field or // top-level variable. return referImported( - revivable.accessor, _typeImport(object.type.element)); + revivable.accessor, _typeImport(object.type!.element)); } final name = revivable.source.fragment; @@ -1150,7 +1150,7 @@ class _MockLibraryInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final type = referImported(name, _typeImport(object.type.element)); + final type = referImported(name, _typeImport(object.type!.element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( revivable.accessor, @@ -1187,9 +1187,7 @@ class _MockLibraryInfo { superNoSuchMethod.asA(_typeReference(getter.returnType)); } - builder - ..lambda = true - ..body = superNoSuchMethod.code; + builder.body = superNoSuchMethod.code; } /// Build a setter which overrides [setter], widening the single parameter @@ -1203,15 +1201,12 @@ class _MockLibraryInfo { ..annotations.addAll([refer('override')]) ..type = MethodType.setter; - Expression invocationPositionalArg; assert(setter.parameters.length == 1); final parameter = setter.parameters.single; - if (parameter.isRequiredPositional) { - builder.requiredParameters.add(Parameter((pBuilder) => pBuilder - ..name = parameter.displayName - ..type = _typeReference(parameter.type, forceNullable: true))); - invocationPositionalArg = refer(parameter.displayName); - } + builder.requiredParameters.add(Parameter((pBuilder) => pBuilder + ..name = parameter.displayName + ..type = _typeReference(parameter.type, forceNullable: true))); + final invocationPositionalArg = refer(parameter.displayName); final invocation = refer('Invocation').property('setter').call([ refer('#${setter.displayName}'), @@ -1230,7 +1225,7 @@ class _MockLibraryInfo { return TypeReference((b) { b.symbol = typeParameter.name; if (typeParameter.bound != null) { - b.bound = _typeReference(typeParameter.bound); + b.bound = _typeReference(typeParameter.bound!); } }); } @@ -1285,7 +1280,7 @@ class _MockLibraryInfo { ..symbol = element.name ..url = _typeImport(element) ..isNullable = forceNullable || typeSystem.isNullable(type); - for (var typeArgument in type.aliasArguments) { + for (var typeArgument in type.aliasArguments!) { b.types.add(_typeReference(typeArgument)); } }); @@ -1306,7 +1301,7 @@ class _MockLibraryInfo { /// Returns the import URL for [element]. /// /// For some types, like `dynamic` and type variables, this may return null. - String _typeImport(Element element) { + String? _typeImport(Element? element) { // For type variables, no import needed. if (element is TypeParameterElement) return null; @@ -1316,14 +1311,14 @@ class _MockLibraryInfo { assert(assetUris.containsKey(element), 'An element, "$element", is missing from the asset URI mapping'); - return assetUris[element]; + return assetUris[element]!; } /// Returns a [Reference] to [symbol] with [url]. /// /// This function overrides [code_builder.refer] so as to ensure that [url] is /// given. - static Reference referImported(String symbol, String url) => + static Reference referImported(String symbol, String? url) => Reference(symbol, url); /// Returns a [Reference] to [symbol] with no URL. @@ -1360,7 +1355,7 @@ extension on Element { if (this is ClassElement) { return "The class '$name'"; } else if (this is MethodElement) { - var className = enclosingElement.name; + var className = enclosingElement!.name; return "The method '$className.$name'"; } else { return 'unknown element'; diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 5fd5c6778..de763e618 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.7'; +const packageVersion = '5.0.8-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 607cf6a40..54d96d6ed 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.7 +version: 5.0.8-dev description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -10,7 +10,7 @@ environment: dependencies: analyzer: ^1.0.0 build: '>=1.3.0 <3.0.0' - code_builder: '>=3.7.0 <5.0.0' + code_builder: ^4.0.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 diff --git a/pkgs/mockito/test/all.dart b/pkgs/mockito/test/all.dart index 9af848fad..5fcc7b4cf 100644 --- a/pkgs/mockito/test/all.dart +++ b/pkgs/mockito/test/all.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - // This file explicitly does _not_ end in `_test.dart`, so that it is not picked // up by `pub run test`. It is here for coveralls. diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 8ca7e709e..457ee5fbf 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -12,15 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - @TestOn('vm') import 'dart:convert' show utf8; import 'package:build/build.dart'; import 'package:build/experiments.dart'; import 'package:build_test/build_test.dart'; -import 'package:meta/meta.dart'; import 'package:mockito/src/builder.dart'; import 'package:package_config/package_config.dart'; import 'package:test/test.dart'; @@ -76,11 +73,11 @@ void main() {} }; void main() { - InMemoryAssetWriter writer; + late InMemoryAssetWriter writer; /// Test [MockBuilder] in a package which has not opted into null safety. Future testPreNonNullable(Map sourceAssets, - {Map*/ dynamic> outputs}) async { + {Map*/ Object>? outputs}) async { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), @@ -92,7 +89,7 @@ void main() { /// Test [MockBuilder] in a package which has opted into null safety. Future testWithNonNullable(Map sourceAssets, - {Map>*/ dynamic> outputs}) async { + {Map>*/ Object>? outputs}) async { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), @@ -118,14 +115,14 @@ void main() { await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); - return utf8.decode(writer.assets[mocksAsset]); + return utf8.decode(writer.assets[mocksAsset]!); } /// Test [MockBuilder] on a single source file, in a package which has opted /// into null safety, and with the non-nullable experiment enabled. Future expectSingleNonNullableOutput( String sourceAssetText, - /*String|Matcher>*/ dynamic output) async { + /*String|Matcher>*/ Object output) async { await testWithNonNullable({ ...metaAssets, ...annotationsAsset, @@ -147,7 +144,7 @@ void main() { 'foo|lib/foo.dart': sourceAssetText, }); var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); - return utf8.decode(writer.assets[mocksAsset]); + return utf8.decode(writer.assets[mocksAsset]!); } setUp(() { @@ -983,7 +980,7 @@ void main() { ''', }); var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); - var mocksContent = utf8.decode(writer.assets[mocksAsset]); + var mocksContent = utf8.decode(writer.assets[mocksAsset]!); expect(mocksContent, contains("import 'dart:async' as _i3;")); expect(mocksContent, contains('m(_i3.Future? a)')); }); @@ -1006,7 +1003,7 @@ void main() { ''', }); var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); - var mocksContent = utf8.decode(writer.assets[mocksAsset]); + var mocksContent = utf8.decode(writer.assets[mocksAsset]!); expect(mocksContent, contains("import 'dart:async' as _i3;")); expect(mocksContent, contains('m(_i3.Future? a)')); }); @@ -2627,9 +2624,10 @@ TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( /// Expect that [testBuilder], given [assets], in a package which has opted into /// null safety, throws an [InvalidMockitoAnnotationException] with a message /// containing [message]. -void _expectBuilderThrows( - {@required Map assets, - @required dynamic /*String|Matcher>*/ message}) { +void _expectBuilderThrows({ + required Map assets, + required dynamic /*String|Matcher>*/ message, +}) { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), @@ -2646,8 +2644,8 @@ void _expectBuilderThrows( /// Dedent [input], so that each line is shifted to the left, so that the first /// line is at the 0 column. String dedent(String input) { - final indentMatch = RegExp(r'^(\s*)').firstMatch(input); - final indent = ''.padRight(indentMatch.group(1).length); + final indentMatch = RegExp(r'^(\s*)').firstMatch(input)!; + final indent = ''.padRight(indentMatch.group(1)!.length); return input.splitMapJoin('\n', onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), '')); } @@ -2655,8 +2653,8 @@ String dedent(String input) { /// Dedent [input], so that each line is shifted to the left, so that the first /// line is at column 2 (starting position for a class member). String dedent2(String input) { - final indentMatch = RegExp(r'^ (\s*)').firstMatch(input); - final indent = ''.padRight(indentMatch.group(1).length); + final indentMatch = RegExp(r'^ (\s*)').firstMatch(input)!; + final indent = ''.padRight(indentMatch.group(1)!.length); return input.replaceFirst(RegExp(r'\s*$'), '').splitMapJoin('\n', onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), '')); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 059851fc7..9dc3b1e2c 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -12,14 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @dart=2.9 - @TestOn('vm') import 'dart:convert' show utf8; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; -import 'package:meta/meta.dart'; import 'package:mockito/src/builder.dart'; import 'package:package_config/package_config.dart'; import 'package:test/test.dart'; @@ -70,11 +67,11 @@ MockFoo() { }'''; void main() { - InMemoryAssetWriter writer; + late InMemoryAssetWriter writer; /// Test [MockBuilder] in a package which has not opted into null safety. Future testPreNonNullable(Map sourceAssets, - {Map*/ dynamic> outputs}) async { + {Map*/ Object>? outputs}) async { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), @@ -95,7 +92,7 @@ void main() { await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); - return utf8.decode(writer.assets[mocksAsset]); + return utf8.decode(writer.assets[mocksAsset]!); } setUp(() { @@ -436,9 +433,10 @@ TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( /// Expect that [testBuilder], given [assets], throws an /// [InvalidMockitoAnnotationException] with a message containing [message]. -void _expectBuilderThrows( - {@required Map assets, - @required dynamic /*String|Matcher>*/ message}) { +void _expectBuilderThrows({ + required Map assets, + required dynamic /*String|Matcher>*/ message, +}) { expect( () async => await testBuilder(buildMocks(BuilderOptions({})), assets), throwsA(TypeMatcher() @@ -448,8 +446,8 @@ void _expectBuilderThrows( /// Dedent [input], so that each line is shifted to the left, so that the first /// line is at the 0 column. String dedent(String input) { - final indentMatch = RegExp(r'^(\s*)').firstMatch(input); - final indent = ''.padRight(indentMatch.group(1).length); + final indentMatch = RegExp(r'^(\s*)').firstMatch(input)!; + final indent = ''.padRight(indentMatch.group(1)!.length); return input.splitMapJoin('\n', onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), '')); } From 191064e9e9c6d31da5ad5a15848ad5c991ccdcb3 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 7 May 2021 12:52:20 -0400 Subject: [PATCH 340/607] Import https://github.com/dart-lang/mockito/pull/392 Fix example tests PiperOrigin-RevId: 372575099 --- pkgs/mockito/build.yaml | 1 + pkgs/mockito/example/example.dart | 45 ++++++++++++++++++++++++-- pkgs/mockito/example/iss/iss_test.dart | 6 ++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index 2120fc9fb..baa5dc23e 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -3,6 +3,7 @@ targets: builders: mockito|mockBuilder: generate_for: + - example/**.dart - test/end2end/*.dart builders: diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 9e63a1173..21a4336ca 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -1,8 +1,11 @@ // ignore_for_file: sdk_version_async_exported_from_core // ignore_for_file: unawaited_futures +import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; +import 'example.mocks.dart'; + // Real class class Cat { String? sound() => 'Meow'; @@ -14,9 +17,6 @@ class Cat { int lives = 9; } -// Mock class -class MockCat extends Mock implements Cat {} - // Fake class class FakeCat extends Fake implements Cat { @override @@ -26,6 +26,11 @@ class FakeCat extends Fake implements Cat { } } +@GenerateMocks([ + Cat +], customMocks: [ + MockSpec(as: #MockCatRelaxed, returnNullOnMissingStub: true), +]) void main() { late Cat cat; @@ -35,6 +40,9 @@ void main() { }); test("Let's verify some behaviour!", () { + // Stub a method before interacting with it. + when(cat.sound()).thenReturn('Meow'); + // Interact with the mock object. cat.sound(); @@ -43,6 +51,12 @@ void main() { }); test('How about some stubbing?', () { + try { + cat.sound(); + } on MissingStubError { + // Unstubbed methods throw MissingStubError. + } + // Unstubbed methods return null. expect(cat.sound(), null); @@ -118,6 +132,8 @@ void main() { }); test('Verifying exact number of invocations / at least x / never', () { + when(cat.sound()).thenReturn('Meow'); + cat.sound(); cat.sound(); // Exact number of invocations @@ -134,6 +150,9 @@ void main() { }); test('Verification in order', () { + when(cat.sound()).thenReturn('Meow'); + when(cat.eatFood(any)).thenReturn(true); + cat.eatFood('Milk'); cat.sound(); cat.eatFood('Fish'); @@ -145,12 +164,16 @@ void main() { }); test('Finding redundant invocations', () { + when(cat.sound()).thenReturn('Meow'); + cat.sound(); verify(cat.sound()); verifyNoMoreInteractions(cat); }); test('Capturing arguments for further assertions', () { + when(cat.eatFood(any)).thenReturn(true); + // Simple capture: cat.eatFood('Fish'); expect(verify(cat.eatFood(captureAny)).captured.single, 'Fish'); @@ -168,6 +191,8 @@ void main() { }); test('Waiting for an interaction', () async { + when(cat.eatFood(any)).thenReturn(true); + Future chewHelper(Cat cat) { return cat.chew(); } @@ -188,4 +213,18 @@ void main() { cat.eatFood('Milk'); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); }); + + test('Relaxed mock class', () { + // Create a new mock Cat at runtime. + var cat = MockCatRelaxed(); + + // You can call it without stubbing. + cat.sleep(); + + // Returns null unless you stub it. + expect(cat.sound(), null); + expect(cat.eatFood('Milk'), null); + + verify(cat.sleep()); + }); } diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index dc4f3cd86..2ec901a0c 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -14,14 +14,14 @@ import 'dart:math'; +import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'iss.dart'; +import 'iss_test.mocks.dart'; -// The Mock class uses noSuchMethod to catch all method invocations. -class MockIssLocator extends Mock implements IssLocator {} - +@GenerateMocks([IssLocator]) void main() { // Given two predefined points on earth, // verify the calculated distance between them. From 554598dd6d29ede19bfeadeea3faf9da5db7af04 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 7 May 2021 13:02:21 -0400 Subject: [PATCH 341/607] Correct NULL_SAFETY_README's example of passing a return value to noSuchMethod. Fixes https://github.com/dart-lang/mockito/issues/403 PiperOrigin-RevId: 372577046 --- pkgs/mockito/NULL_SAFETY_README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index a1402c9a3..50a2def58 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -247,7 +247,8 @@ additional argument: class MockHttpServer extends Mock implements HttpServer { @override Uri get uri => - super.noSuchMethod(Invocation.getter(#uri), Uri.http('example.org', '/')); + super.noSuchMethod( + Invocation.getter(#uri), returnValue: Uri.http('example.org', '/')); } ``` From 684634c5782eed47a85945d429c34cf3573c7556 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 10 May 2021 13:54:29 -0400 Subject: [PATCH 342/607] Fix breakage introduced by converting example code to generated mocks. * Add a dependency on the annotations, so that generators are run. * Allow using `super.noSuchMethod` in the ISS example. * Sort ignores alphabetically. PiperOrigin-RevId: 372965453 --- pkgs/mockito/lib/src/builder.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index c21783114..e9ce6a45a 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -72,13 +72,18 @@ class MockBuilder implements Builder { final mockLibrary = Library((b) { // These comments are added after import directives; leading newlines // are necessary. + b.body.add( + Code('\n\n// ignore_for_file: avoid_redundant_argument_values\n')); // We don't properly prefix imported class names in doc comments. - b.body.add(Code('\n\n// ignore_for_file: comment_references\n')); + b.body.add(Code('// ignore_for_file: comment_references\n')); + // `Mock.noSuchMethod` is `@visibleForTesting`, but the generated code is + // not always in a test directory; the Mockito `example/iss` tests, for + // example. + b.body.add(Code( + '// ignore_for_file: invalid_use_of_visible_for_testing_member\n')); + b.body.add(Code('// ignore_for_file: prefer_const_constructors\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n\n')); - b.body.add(Code('// ignore_for_file: prefer_const_constructors\n\n')); - b.body - .add(Code('// ignore_for_file: avoid_redundant_argument_values\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); From 49c140e99256a4b4878adeec2f834c0e6d6ab94a Mon Sep 17 00:00:00 2001 From: matanl Date: Mon, 10 May 2021 15:38:30 -0400 Subject: [PATCH 343/607] Remove various additional, very out-dated TODOs for matanl in `f: third_party`. PiperOrigin-RevId: 372989442 --- pkgs/mockito/lib/src/invocation_matcher.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index 40f5b54cb..005eb0500 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -126,7 +126,7 @@ class _InvocationMatcher implements Matcher { @override Description describe(Description d) => _describeInvocation(d, _invocation); - // TODO(matanl): Better implement describeMismatch and use state from matches. + // TODO: Better implement describeMismatch and use state from matches. // Specifically, if a Matcher is passed as an argument, we'd like to get an // error like "Expected fly(miles: > 10), Actual: fly(miles: 5)". @override From 03184e2c99cc8a4bf25f8bcc37ed05a9f2a511bd Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 12 May 2021 14:39:55 -0400 Subject: [PATCH 344/607] Dart GenerateMocks function parameter fix. During the validation phase there is a check that rejects any function that has a result type that includes a non-nullable type parameter. This is because the mock has no way to a priori generate a non-null element of type T to return to the caller. However, this same check is currently applied to the parameters of mockable methods. The same restriction does not need to be imposed on the result type of function-type parameters because the mock method can simply ignore the function parameter. No value needs to exist of the function's return type (the function can return bottom for all we care). PiperOrigin-RevId: 373416046 --- pkgs/mockito/lib/src/builder.dart | 8 +++++--- pkgs/mockito/test/builder/auto_mocks_test.dart | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index e9ce6a45a..115278e88 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -499,7 +499,8 @@ class _MockTargetGatherer { /// - bounds of type parameters /// - type arguments List _checkFunction( - analyzer.FunctionType function, Element enclosingElement) { + analyzer.FunctionType function, Element enclosingElement, + {bool isParameter = false}) { var errorMessages = []; var returnType = function.returnType; if (returnType is analyzer.InterfaceType) { @@ -513,7 +514,7 @@ class _MockTargetGatherer { } else if (returnType is analyzer.FunctionType) { errorMessages.addAll(_checkFunction(returnType, enclosingElement)); } else if (returnType is analyzer.TypeParameterType) { - if (function.returnType is analyzer.TypeParameterType && + if (!isParameter && _entryLib.typeSystem.isPotentiallyNonNullable(function.returnType)) { errorMessages .add('${enclosingElement.fullName} features a non-nullable unknown ' @@ -536,7 +537,8 @@ class _MockTargetGatherer { errorMessages.addAll( _checkTypeArguments(parameterType.typeArguments, enclosingElement)); } else if (parameterType is analyzer.FunctionType) { - errorMessages.addAll(_checkFunction(parameterType, enclosingElement)); + errorMessages.addAll( + _checkFunction(parameterType, enclosingElement, isParameter: true)); } } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 457ee5fbf..9af195684 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -798,6 +798,18 @@ void main() { ); }); + test('matches function parameters with scoped return types', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m(T Function() a) {} + } + '''), + _containsAllOf( + 'void m(T Function()? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + test('writes type variables types w/o import prefixes', () async { await expectSingleNonNullableOutput( dedent(r''' From b61f0b85e59304628e222016890cdba57db2aabc Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 12 May 2021 15:44:04 -0400 Subject: [PATCH 345/607] Support mocking methods with typed_data List return types. Without this fix, mockito tries to create, for example, a subclass: `class _FakeUint8List implements Uint8List`, but these classes are illegal to subtype. Fortunately, instances are easy to create with an unnamed constructor. PiperOrigin-RevId: 373429568 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/builder.dart | 27 +++++++++++++++++++ .../mockito/test/builder/auto_mocks_test.dart | 19 +++++++++++++ 3 files changed, 47 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 2eff7c649..34ea6c50e 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,7 @@ ## 5.0.8-dev * Migrate Mockito codegen to null safety. +* Support mocking methods with typed_data List return types. ## 5.0.7 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 115278e88..a924acb56 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -961,6 +961,14 @@ class _MockLibraryInfo { }).property('empty').call([]); } else if (type.isDartCoreString) { return literalString(''); + } else if (type.isDartTypedDataList) { + // These "List" types from dart:typed_data are "non-subtypeable", but they + // have predicatble constructors; each has an unnamed constructor which + // takes a single int argument. + return referImported(type.displayName, 'dart:typed_data') + .call([literalNum(0)]); + // TODO(srawlins): Do other types from typed_data have a "non-subtypeable" + // restriction as well? } // This class is unknown; we must likely generate a fake class, and return @@ -1375,4 +1383,23 @@ extension on analyzer.DartType { bool get isFutureOfVoid => isDartAsyncFuture && (this as analyzer.InterfaceType).typeArguments.first.isVoid; + + /// Returns whether this type is a "List" type from the dart:typed_data + /// library. + bool get isDartTypedDataList { + if (element!.library!.name != 'dart.typed_data') { + return false; + } + final name = element!.name; + return name == 'Float32List' || + name == 'Float64List' || + name == 'Int8List' || + name == 'Int16List' || + name == 'Int32List' || + name == 'Int64List' || + name == 'Uint8List' || + name == 'Uint16List' || + name == 'Uint32List' || + name == 'Uint64List'; + } } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 9af195684..604264456 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1798,6 +1798,25 @@ void main() { ); }); + test( + 'creates dummy non-null return values for Futures of known typed_data classes', + () async { + await expectSingleNonNullableOutput( + dedent(''' + import 'dart:typed_data'; + class Foo { + Future m() async => Uint8List(0); + } + '''), + _containsAllOf(dedent2(''' + _i3.Future<_i4.Uint8List> m() => + (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Future<_i4.Uint8List>.value(_i4.Uint8List(0))) + as _i3.Future<_i4.Uint8List>); + ''')), + ); + }); + test( 'creates dummy non-null return values for Futures of known generic core classes', () async { From a266f12f530d04a49d06052ad78a2a739172de70 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 17 May 2021 14:13:19 -0400 Subject: [PATCH 346/607] Support mocking methods with return types declared in private SDK libraries. Examples include HttpClient and WebSocket. Fixes https://github.com/dart-lang/mockito/issues/405 PiperOrigin-RevId: 374237555 --- pkgs/mockito/CHANGELOG.md | 2 + pkgs/mockito/lib/src/builder.dart | 42 ++++++++++++++++--- .../mockito/test/builder/auto_mocks_test.dart | 38 +++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 34ea6c50e..bea561e70 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,8 @@ * Migrate Mockito codegen to null safety. * Support mocking methods with typed_data List return types. +* Support mocking methods with return types declared in private SDK libraries + (such as HttpClient and WebSocket, declared in `dart:_http`). ## 5.0.7 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a924acb56..5f438ba6e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:collection'; + import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; @@ -57,8 +59,8 @@ class MockBuilder implements Builder { final mockTargetGatherer = _MockTargetGatherer(entryLib); var entryAssetId = await buildStep.resolver.assetIdForElement(entryLib); - final assetUris = await _resolveAssetUris( - buildStep.resolver, mockTargetGatherer._mockTargets, entryAssetId.path); + final assetUris = await _resolveAssetUris(buildStep.resolver, + mockTargetGatherer._mockTargets, entryAssetId.path, entryLib); final mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets, assetUris: assetUris, entryLib: entryLib); @@ -102,10 +104,14 @@ $rawOutput await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent); } - Future> _resolveAssetUris(Resolver resolver, - List<_MockTarget> mockTargets, String entryAssetPath) async { + Future> _resolveAssetUris( + Resolver resolver, + List<_MockTarget> mockTargets, + String entryAssetPath, + LibraryElement entryLib) async { final typeVisitor = _TypeVisitor(); final seenTypes = {}; + final librariesWithTypes = {}; void addTypesFrom(analyzer.InterfaceType type) { // Prevent infinite recursion. @@ -113,6 +119,7 @@ $rawOutput return; } seenTypes.add(type); + librariesWithTypes.add(type.element.library); type.element.accept(typeVisitor); // For a type like `Foo`, add the `Bar`. type.typeArguments @@ -133,7 +140,12 @@ $rawOutput for (var element in typeVisitor._elements) { final elementLibrary = element.library!; if (elementLibrary.isInSdk) { - typeUris[element] = elementLibrary.source.uri.toString(); + if (elementLibrary.name!.startsWith('dart._')) { + typeUris[element] = _findPublicExportOf( + Queue.of(librariesWithTypes), elementLibrary)!; + } else { + typeUris[element] = elementLibrary.source.uri.toString(); + } continue; } @@ -156,6 +168,26 @@ $rawOutput return typeUris; } + /// Returns the String import path of the correct public library which + /// exports [privateLibrary], selecting from the imports of [inputLibraries]. + static String? _findPublicExportOf( + Queue inputLibraries, LibraryElement privateLibrary) { + final libraries = Queue.of([ + for (final library in inputLibraries) ...library.importedLibraries, + ]); + + while (libraries.isNotEmpty) { + final library = libraries.removeFirst(); + if (library.exportedLibraries.contains(privateLibrary)) { + return library.source.uri.toString(); + } + // A library may provide [privateLibrary] by exporting a library which + // provides it (directly or via further exporting). + libraries.addAll(library.exportedLibraries); + } + return null; + } + @override final buildExtensions = const { '.dart': ['.mocks.dart'] diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 604264456..b61261b0b 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1039,6 +1039,44 @@ void main() { expect(mocksContent, contains('_i2.Callback3<_i2.Foo>? c')); }); + test('imports libraries for types declared in private SDK libraries', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + import 'dart:io'; + abstract class Foo { + HttpClient f() {} + } + ''')); + expect(mocksContent, contains("import 'dart:io' as _i2;")); + expect(mocksContent, contains('_i2.HttpClient f() =>')); + }); + + test( + 'imports libraries for types declared in private SDK libraries exported ' + 'in dart:io', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + import 'dart:io'; + abstract class Foo { + HttpStatus f() {} + } + ''')); + expect(mocksContent, contains("import 'dart:io' as _i2;")); + expect(mocksContent, contains('_i2.HttpStatus f() =>')); + }); + + test( + 'imports libraries for types declared in private SDK libraries exported ' + 'in dart:html', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + import 'dart:html'; + abstract class Foo { + HttpStatus f() {} + } + ''')); + expect(mocksContent, contains("import 'dart:html' as _i2;")); + expect(mocksContent, contains('_i2.HttpStatus f() =>')); + }); + test('prefixes parameter type on generic function-typed parameter', () async { await expectSingleNonNullableOutput( dedent(r''' From 2ccaa3594ca517d1bc7a73405ec6d7d9c93625bc Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 17 May 2021 14:34:39 -0400 Subject: [PATCH 347/607] Do not generate a fake for a class which is only used as a nullable type in a Future. Fixes https://github.com/dart-lang/mockito/issues/409 Other containers do not feature this problem, like List, Stream, etc, as they are allowed to be empty. A Future can only be empty in this nullable type case. PiperOrigin-RevId: 374242451 --- pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/builder.dart | 8 ++++++-- pkgs/mockito/test/builder/auto_mocks_test.dart | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index bea561e70..3b93d2b77 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -4,6 +4,8 @@ * Support mocking methods with typed_data List return types. * Support mocking methods with return types declared in private SDK libraries (such as HttpClient and WebSocket, declared in `dart:_http`). +* Do not generate a fake for a class which is only used as a nullable type in a + Future. [#409](https://github.com/dart-lang/mockito/issues/409) ## 5.0.7 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 5f438ba6e..fbc4c74b3 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -960,10 +960,14 @@ class _MockLibraryInfo { } else if (type.isDartCoreDouble) { return literalNum(0.0); } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { - var typeArgument = typeArguments.first; + final typeArgument = typeArguments.first; + final futureValueArguments = + typeSystem.isPotentiallyNonNullable(typeArgument) + ? [_dummyValue(typeArgument)] + : []; return _futureReference(_typeReference(typeArgument)) .property('value') - .call([_dummyValue(typeArgument)]); + .call(futureValueArguments); } else if (type.isDartCoreInt) { return literalNum(0); } else if (type.isDartCoreIterable) { diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index b61261b0b..6141c9469 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -526,7 +526,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future.value(null), + returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i3.Future); ''')), ); @@ -1836,6 +1836,22 @@ void main() { ); }); + test('creates dummy non-null return values for Futures of nullable types', + () async { + await expectSingleNonNullableOutput( + dedent(''' + class Bar {} + class Foo { + Future m() async => null; + } + '''), + _containsAllOf(dedent2(''' + _i3.Future<_i2.Bar?> m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Future<_i2.Bar?>.value()) as _i3.Future<_i2.Bar?>); + ''')), + ); + }); + test( 'creates dummy non-null return values for Futures of known typed_data classes', () async { From 3dac8e279ef23acde3e6e9809f2a55a724476897 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 17 May 2021 19:14:04 -0400 Subject: [PATCH 348/607] Bump mockito to 5.0.8 in order to release a few fixes. PiperOrigin-RevId: 374301838 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 3b93d2b77..75183ea43 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.8-dev +## 5.0.8 * Migrate Mockito codegen to null safety. * Support mocking methods with typed_data List return types. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index de763e618..b3ae609cf 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.8-dev'; +const packageVersion = '5.0.8'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 54d96d6ed..436686547 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.8-dev +version: 5.0.8 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito @@ -20,9 +20,9 @@ dependencies: test_api: '>=0.2.1 <0.5.0' dev_dependencies: - build_runner: ^1.12.0 + build_runner: ^2.0.0 build_test: ^2.0.0 - build_web_compilers: '>=1.0.0 <3.0.0' + build_web_compilers: ^3.0.0 http: ^0.13.0 package_config: '>=1.9.3 <3.0.0' pedantic: ^1.10.0 From 502cea5012fe2597ff6d962bd08f34c07a1b6060 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 17 May 2021 17:37:20 -0700 Subject: [PATCH 349/607] format with Dart 2.14-dev --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index fbc4c74b3..b4e1876d4 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -667,7 +667,7 @@ class _MockLibraryInfo { Iterable<_MockTarget> mockTargets, { required this.assetUris, required LibraryElement entryLib, - }) : sourceLibIsNonNullable = entryLib.isNonNullableByDefault, + }) : sourceLibIsNonNullable = entryLib.isNonNullableByDefault, typeProvider = entryLib.typeProvider, typeSystem = entryLib.typeSystem { for (final mockTarget in mockTargets) { From acd962bb46e97c22a19b2f9cabb97f294ff03aca Mon Sep 17 00:00:00 2001 From: MarkG Date: Wed, 5 May 2021 12:55:39 +0700 Subject: [PATCH 350/607] Fix missing interface's fields and methods in builder --- pkgs/mockito/lib/src/builder.dart | 10 ++++++ .../mockito/test/builder/auto_mocks_test.dart | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b4e1876d4..3eee4ca8a 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -800,6 +800,11 @@ class _MockLibraryInfo { yield* fieldOverrides(mixin, overriddenFields); } } + if (type.interfaces != null) { + for (var interface in type.interfaces) { + yield* fieldOverrides(interface, overriddenFields); + } + } var superclass = type.superclass; if (superclass != null && !superclass.isDartCoreObject) { yield* fieldOverrides(superclass, overriddenFields); @@ -841,6 +846,11 @@ class _MockLibraryInfo { yield* methodOverrides(mixin, overriddenMethods); } } + if (type.interfaces != null) { + for (var interface in type.interfaces) { + yield* methodOverrides(interface, overriddenMethods); + } + } var superclass = type.superclass; if (superclass != null && !superclass.isDartCoreObject) { yield* methodOverrides(superclass, overriddenMethods); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 6141c9469..0cda1c6de 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -615,6 +615,37 @@ void main() { ); }); + test('contains methods of implemented classes', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Interface { + void m(T a) {} + } + class Foo implements Interface {} + '''), + _containsAllOf( + 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + + test('contains fields of implemented classes', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Interface { + int m; + } + class Foo implements Interface {} + '''), + _containsAllOf(dedent2(''' + int get m => + (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + '''), dedent2(''' + set m(int? _m) => super + .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); + ''')), + ); + }); + test( 'overrides methods of indirect generic super classes, substituting types', () async { From 6dd58989f4a33894383b118311cbae2707250d20 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 19 May 2021 19:34:35 -0400 Subject: [PATCH 351/607] In a generated Mock class, implement a type's nested type arguments properly. Fixes https://github.com/dart-lang/mockito/issues/410 For example: ```dart @GenerateMocks([], customMocks: [MockSpec>>(as: #MockFooOfListOfInt)]) ``` now generates: ```dart class MockFooOfListOfInt extends _i1.Mock implements _i2.Foo> { ... } ``` PiperOrigin-RevId: 374755599 --- pkgs/mockito/CHANGELOG.md | 5 ++ pkgs/mockito/lib/src/builder.dart | 14 +--- .../mockito/test/builder/auto_mocks_test.dart | 31 -------- .../test/builder/custom_mocks_test.dart | 71 +++++++++++++++++++ 4 files changed, 79 insertions(+), 42 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 75183ea43..425c5217f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.0.9-dev + +* Mock classes now implement a type's nested type arguments properly. + [#410](https://github.com/dart-lang/mockito/issues/410) + ## 5.0.8 * Migrate Mockito codegen to null safety. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 3eee4ca8a..fd269d898 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -738,7 +738,9 @@ class _MockLibraryInfo { // `class MockFoo extends Mock implements Foo {}` for (var typeArgument in typeToMock.typeArguments) { typeArguments.add(referImported( - typeArgument.element!.name!, _typeImport(typeArgument.element))); + typeArgument.getDisplayString( + withNullability: sourceLibIsNonNullable), + _typeImport(typeArgument.element))); } } else if (classToMock.typeParameters != null) { // [typeToMock] is a simple reference to a generic type (for example: @@ -800,11 +802,6 @@ class _MockLibraryInfo { yield* fieldOverrides(mixin, overriddenFields); } } - if (type.interfaces != null) { - for (var interface in type.interfaces) { - yield* fieldOverrides(interface, overriddenFields); - } - } var superclass = type.superclass; if (superclass != null && !superclass.isDartCoreObject) { yield* fieldOverrides(superclass, overriddenFields); @@ -846,11 +843,6 @@ class _MockLibraryInfo { yield* methodOverrides(mixin, overriddenMethods); } } - if (type.interfaces != null) { - for (var interface in type.interfaces) { - yield* methodOverrides(interface, overriddenMethods); - } - } var superclass = type.superclass; if (superclass != null && !superclass.isDartCoreObject) { yield* methodOverrides(superclass, overriddenMethods); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 0cda1c6de..6141c9469 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -615,37 +615,6 @@ void main() { ); }); - test('contains methods of implemented classes', () async { - await expectSingleNonNullableOutput( - dedent(r''' - class Interface { - void m(T a) {} - } - class Foo implements Interface {} - '''), - _containsAllOf( - 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), - ); - }); - - test('contains fields of implemented classes', () async { - await expectSingleNonNullableOutput( - dedent(r''' - class Interface { - int m; - } - class Foo implements Interface {} - '''), - _containsAllOf(dedent2(''' - int get m => - (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); - '''), dedent2(''' - set m(int? _m) => super - .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); - ''')), - ); - }); - test( 'overrides methods of indirect generic super classes, substituting types', () async { diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 9dc3b1e2c..efb92fde6 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -137,6 +137,47 @@ void main() { 'class MockFooOfIntBar extends _i1.Mock implements _i2.Foo')); }); + test('generates a generic mock class with nullable type arguments', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks( + [], customMocks: [MockSpec>(as: #MockFooOfIntBar)]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFooOfIntBar extends _i1.Mock implements _i2.Foo')); + }); + + test('generates a generic mock class with nested type arguments', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks( + [], customMocks: [MockSpec>>(as: #MockFooOfListOfInt)]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFooOfListOfInt extends _i1.Mock implements _i2.Foo>')); + }); + test('generates a generic mock class with type arguments but no name', () async { var mocksContent = await buildWithNonNullable({ @@ -426,6 +467,36 @@ void main() { }, ); }); + + test( + 'given a pre-non-nullable safe library, does not write "?" on interface ' + 'types', () async { + await testPreNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + int f(int a); + } + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks( + [], customMocks: [MockSpec>(as: #MockFoo)]) + void main() {} + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' + class MockFoo extends _i1.Mock implements _i2.Foo { + $_constructorWithThrowOnMissingStub + } + ''')) + }, + ); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From ad9fec3107bb53a6c31954062fc5162aff82a947 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 21 May 2021 12:49:31 -0400 Subject: [PATCH 352/607] Import https://github.com/dart-lang/mockito/pull/404 Fix missing interface's fields and methods in builder PiperOrigin-RevId: 375105090 --- pkgs/mockito/lib/src/builder.dart | 10 ++++++ .../mockito/test/builder/auto_mocks_test.dart | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index fd269d898..eb447e0aa 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -802,6 +802,11 @@ class _MockLibraryInfo { yield* fieldOverrides(mixin, overriddenFields); } } + if (type.interfaces != null) { + for (var interface in type.interfaces) { + yield* fieldOverrides(interface, overriddenFields); + } + } var superclass = type.superclass; if (superclass != null && !superclass.isDartCoreObject) { yield* fieldOverrides(superclass, overriddenFields); @@ -843,6 +848,11 @@ class _MockLibraryInfo { yield* methodOverrides(mixin, overriddenMethods); } } + if (type.interfaces != null) { + for (var interface in type.interfaces) { + yield* methodOverrides(interface, overriddenMethods); + } + } var superclass = type.superclass; if (superclass != null && !superclass.isDartCoreObject) { yield* methodOverrides(superclass, overriddenMethods); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 6141c9469..0cda1c6de 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -615,6 +615,37 @@ void main() { ); }); + test('contains methods of implemented classes', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Interface { + void m(T a) {} + } + class Foo implements Interface {} + '''), + _containsAllOf( + 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + + test('contains fields of implemented classes', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Interface { + int m; + } + class Foo implements Interface {} + '''), + _containsAllOf(dedent2(''' + int get m => + (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + '''), dedent2(''' + set m(int? _m) => super + .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); + ''')), + ); + }); + test( 'overrides methods of indirect generic super classes, substituting types', () async { From 7c6c029b8ccf00ad795296662adf0d743a7c83aa Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 21 May 2021 15:26:38 -0400 Subject: [PATCH 353/607] Fix 'toPrettyString' handling of string args. Right now it's not clear that a pretty printed value of type string is actually a string. '3' and 3 both map to the same string value. And toString of the empty string is the empty string so there isn't any visual cue that an arg was the empty string. Do a runtime check to see if the arg is a string and artificially add quotes. PiperOrigin-RevId: 375139813 --- pkgs/mockito/lib/src/mock.dart | 4 +++- pkgs/mockito/test/verify_test.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index b90d0363d..b18ea83fb 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -1155,7 +1155,9 @@ extension on Invocation { /// improve readability. String toPrettyString() { String argString; - var args = positionalArguments.map((v) => '$v'); + // Add quotes around strings to clarify the type of the argument to the user + // and so the empty string is represented. + var args = positionalArguments.map((v) => v is String ? "'$v'" : '$v'); if (args.any((arg) => arg.contains('\n'))) { // As one or more arg contains newlines, put each on its own line, and // indent each, for better readability. diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 31069d573..6358ebaef 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -175,7 +175,7 @@ void main() { test('should mock setter', () { mock.setter = 'A'; final expectedMessage = RegExp.escape('No matching calls. ' - 'All calls: _MockedClass.setter==A\n$noMatchingCallsFooter'); + 'All calls: _MockedClass.setter==\'A\'\n$noMatchingCallsFooter'); // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 var expectedPattern = RegExp(expectedMessage.replaceFirst('==', '=?=')); From de64549531192bf2de095d26db3a5d38c0d4bc99 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 24 May 2021 18:25:20 -0400 Subject: [PATCH 354/607] Add support for dummy generators to Mockito's customMocks generator. If a class-to-be-mocked has a method with a type variable return type, such as: T add(T a); then mockito cannot generate a non-nullable return dummy value of type T. But the developer might know that there are only a few T types used in a test, and can generate their own dummy return values in a top-level function. PiperOrigin-RevId: 375570623 --- pkgs/mockito/lib/annotations.dart | 23 ++- pkgs/mockito/lib/src/builder.dart | 142 ++++++++++++++---- .../mockito/test/builder/auto_mocks_test.dart | 6 +- .../test/builder/custom_mocks_test.dart | 108 ++++++++++++- pkgs/mockito/test/end2end/foo.dart | 6 + .../test/end2end/generated_mocks_test.dart | 42 ++++++ 6 files changed, 288 insertions(+), 39 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 53ecd454a..d68210c5f 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -66,13 +66,28 @@ class GenerateMocks { /// directs Mockito to generate two mocks: /// `class MockFoo extends Mocks implements Foo` and /// `class MockFooOfInt extends Mock implements Foo`. -// TODO(srawlins): Document this in NULL_SAFETY_README.md. -// TODO(srawlins): Add 'mixingIn'. class MockSpec { final Symbol? mockName; final bool returnNullOnMissingStub; - const MockSpec({Symbol? as, this.returnNullOnMissingStub = false}) - : mockName = as; + final Map fallbackGenerators; + + /// Constructs a custom mock specification. + /// + /// Specify a custom name with the [as] parameter. + /// + /// If [returnNullOnMissingStub] is true, the mock class will return `null` + /// when a method is called and no stub could be found. This may result in a + /// runtime error, if the return type of the method is non-nullable. + /// + /// Each entry in [fallbackGenerators] specifies a mapping from a method name + /// to a function, with the same signature as the method. This function will + /// be used to generate fallback values when a non-null value needs to be + /// returned when stubbing or verifying. + const MockSpec({ + Symbol? as, + this.returnNullOnMissingStub = false, + this.fallbackGenerators = const {}, + }) : mockName = as; } diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index eb447e0aa..18641942a 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -137,7 +137,15 @@ $rawOutput final typeUris = {}; - for (var element in typeVisitor._elements) { + final elements = [ + // Types which may be referenced. + ...typeVisitor._elements, + // Fallback generator functions which may be referenced. + for (final mockTarget in mockTargets) + ...mockTarget.fallbackGenerators.values, + ]; + + for (final element in elements) { final elementLibrary = element.library!; if (elementLibrary.isInSdk) { if (elementLibrary.name!.startsWith('dart._')) { @@ -313,8 +321,14 @@ class _MockTarget { final bool returnNullOnMissingStub; - _MockTarget(this.classType, this.mockName, - {required this.returnNullOnMissingStub}); + final Map fallbackGenerators; + + _MockTarget( + this.classType, + this.mockName, { + required this.returnNullOnMissingStub, + required this.fallbackGenerators, + }); ClassElement get classElement => classType.element; } @@ -381,7 +395,7 @@ class _MockTargetGatherer { (type.element.declaration as ClassElement).thisType; final mockName = 'Mock${declarationType.element.name}'; mockTargets.add(_MockTarget(declarationType, mockName, - returnNullOnMissingStub: false)); + returnNullOnMissingStub: false, fallbackGenerators: {})); } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { @@ -399,13 +413,36 @@ class _MockTargetGatherer { 'Mock${type.element.name}'; final returnNullOnMissingStub = mockSpec.getField('returnNullOnMissingStub')!.toBoolValue()!; + final fallbackGeneratorObjects = + mockSpec.getField('fallbackGenerators')!.toMapValue()!; mockTargets.add(_MockTarget(type, mockName, - returnNullOnMissingStub: returnNullOnMissingStub)); + returnNullOnMissingStub: returnNullOnMissingStub, + fallbackGenerators: + _extractFallbackGenerators(fallbackGeneratorObjects))); } } return mockTargets; } + static Map _extractFallbackGenerators( + Map objects) { + final fallbackGenerators = {}; + objects.forEach((methodName, generator) { + if (methodName == null) { + throw InvalidMockitoAnnotationException( + 'Unexpected null key in fallbackGenerators: $objects'); + } + if (generator == null) { + throw InvalidMockitoAnnotationException( + 'Unexpected null value in fallbackGenerators for key ' + '"$methodName"'); + } + fallbackGenerators[methodName.toSymbolValue()!] = + generator.toFunctionValue()!; + }); + return fallbackGenerators; + } + /// Map the values passed to the GenerateMocks annotation to the classes which /// they represent. /// @@ -455,27 +492,28 @@ class _MockTargetGatherer { } void _checkClassesToMockAreValid() { - var classesInEntryLib = + final classesInEntryLib = _entryLib.topLevelElements.whereType(); - var classNamesToMock = {}; - var uniqueNameSuggestion = + final classNamesToMock = {}; + final uniqueNameSuggestion = "use the 'customMocks' argument in @GenerateMocks to specify a unique " 'name'; for (final mockTarget in _mockTargets) { - var name = mockTarget.mockName; + final name = mockTarget.mockName; if (classNamesToMock.containsKey(name)) { - var firstSource = classNamesToMock[name]!.source.fullName; - var secondSource = mockTarget.classElement.source.fullName; + final firstClass = classNamesToMock[name]!.classElement; + final firstSource = firstClass.source.fullName; + final secondSource = mockTarget.classElement.source.fullName; throw InvalidMockitoAnnotationException( 'Mockito cannot generate two mocks with the same name: $name (for ' - '${classNamesToMock[name]!.name} declared in $firstSource, and for ' + '${firstClass.name} declared in $firstSource, and for ' '${mockTarget.classElement.name} declared in $secondSource); ' '$uniqueNameSuggestion.'); } - classNamesToMock[name] = mockTarget.classElement; + classNamesToMock[name] = mockTarget; } - classNamesToMock.forEach((name, element) { + classNamesToMock.forEach((name, mockTarget) { var conflictingClass = classesInEntryLib.firstWhereOrNull((c) => c.name == name); if (conflictingClass != null) { @@ -486,7 +524,9 @@ class _MockTargetGatherer { } var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => - c.interfaces.map((type) => type.element).contains(element) && + c.interfaces + .map((type) => type.element) + .contains(mockTarget.classElement) && _isMockClass(c.supertype!)); if (preexistingMock != null) { throw InvalidMockitoAnnotationException( @@ -495,7 +535,7 @@ class _MockTargetGatherer { '$uniqueNameSuggestion.'); } - _checkMethodsToStubAreValid(element); + _checkMethodsToStubAreValid(mockTarget); }); } @@ -505,13 +545,17 @@ class _MockTargetGatherer { /// A method is not valid for stubbing if: /// - It has a private type anywhere in its signature; Mockito cannot override /// such a method. - /// - It has a non-nullable type variable return type, for example `T m()`. - /// Mockito cannot generate dummy return values for unknown types. - void _checkMethodsToStubAreValid(ClassElement classElement) { - var className = classElement.name; - var unstubbableErrorMessages = classElement.methods + /// - It has a non-nullable type variable return type, for example `T m()`, + /// and no corresponding dummy generator. Mockito cannot generate its own + /// dummy return values for unknown types. + void _checkMethodsToStubAreValid(_MockTarget mockTarget) { + final classElement = mockTarget.classElement; + final className = classElement.name; + final unstubbableErrorMessages = classElement.methods .where((m) => !m.isPrivate && !m.isStatic) - .expand((m) => _checkFunction(m.type, m)) + .expand((m) => _checkFunction(m.type, m, + hasDummyGenerator: + mockTarget.fallbackGenerators.containsKey(m.name))) .toList(); if (unstubbableErrorMessages.isNotEmpty) { @@ -531,10 +575,13 @@ class _MockTargetGatherer { /// - bounds of type parameters /// - type arguments List _checkFunction( - analyzer.FunctionType function, Element enclosingElement, - {bool isParameter = false}) { - var errorMessages = []; - var returnType = function.returnType; + analyzer.FunctionType function, + Element enclosingElement, { + bool isParameter = false, + bool hasDummyGenerator = false, + }) { + final errorMessages = []; + final returnType = function.returnType; if (returnType is analyzer.InterfaceType) { if (returnType.element.isPrivate) { errorMessages.add( @@ -547,10 +594,12 @@ class _MockTargetGatherer { errorMessages.addAll(_checkFunction(returnType, enclosingElement)); } else if (returnType is analyzer.TypeParameterType) { if (!isParameter && - _entryLib.typeSystem.isPotentiallyNonNullable(function.returnType)) { + !hasDummyGenerator && + _entryLib.typeSystem.isPotentiallyNonNullable(returnType)) { errorMessages .add('${enclosingElement.fullName} features a non-nullable unknown ' - 'return type, and cannot be stubbed.'); + 'return type, and cannot be stubbed without a dummy generator ' + 'specified on the MockSpec.'); } } @@ -662,6 +711,12 @@ class _MockLibraryInfo { /// Asset-resolving while building the mock library. final Map assetUris; + /// A mapping of any fallback generators specified for the classes-to-mock. + /// + /// Each value is another mapping from method names to the generator + /// function elements. + final Map> fallbackGenerators; + /// Build mock classes for [mockTargets]. _MockLibraryInfo( Iterable<_MockTarget> mockTargets, { @@ -669,7 +724,11 @@ class _MockLibraryInfo { required LibraryElement entryLib, }) : sourceLibIsNonNullable = entryLib.isNonNullableByDefault, typeProvider = entryLib.typeProvider, - typeSystem = entryLib.typeSystem { + typeSystem = entryLib.typeSystem, + fallbackGenerators = { + for (final mockTarget in mockTargets) + mockTarget.classElement: mockTarget.fallbackGenerators + } { for (final mockTarget in mockTargets) { mockClasses.add(_buildMockClass(mockTarget)); } @@ -939,8 +998,14 @@ class _MockLibraryInfo { } else if (method.returnType.isFutureOfVoid) { returnValueForMissingStub = _futureReference().property('value').call([]); } + final class_ = method.enclosingElement; + final fallbackGenerator = fallbackGenerators.containsKey(class_) + ? fallbackGenerators[class_]![method.name] + : null; final namedArgs = { - if (_returnTypeIsNonNullable(method)) + if (fallbackGenerator != null) + 'returnValue': _fallbackGeneratorCode(method, fallbackGenerator) + else if (_returnTypeIsNonNullable(method)) 'returnValue': _dummyValue(method.returnType), if (returnValueForMissingStub != null) 'returnValueForMissingStub': returnValueForMissingStub, @@ -956,6 +1021,23 @@ class _MockLibraryInfo { builder.body = superNoSuchMethod.code; } + Expression _fallbackGeneratorCode( + MethodElement method, ExecutableElement function) { + final positionalArguments = []; + final namedArguments = {}; + for (final parameter in method.parameters) { + if (parameter.isPositional) { + positionalArguments.add(refer(parameter.name)); + } else if (parameter.isNamed) { + namedArguments[parameter.name] = refer(parameter.name); + } + } + final functionReference = + referImported(function.name, _typeImport(function)); + return functionReference.call(positionalArguments, namedArguments, + [for (var t in method.typeParameters) refer(t.name)]); + } + Expression _dummyValue(analyzer.DartType type) { if (type is analyzer.FunctionType) { return _dummyFunctionValue(type); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 0cda1c6de..764700be3 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2467,7 +2467,7 @@ void main() { }, message: contains( "The method 'Foo.m' features a non-nullable unknown return type, and " - 'cannot be stubbed.'), + 'cannot be stubbed'), ); }); @@ -2486,7 +2486,7 @@ void main() { }, message: contains( "The method 'Foo.m' features a non-nullable unknown return type, and " - 'cannot be stubbed.'), + 'cannot be stubbed'), ); }); @@ -2506,7 +2506,7 @@ void main() { }, message: contains( "The method 'Foo.m' features a non-nullable unknown return type, and " - 'cannot be stubbed.'), + 'cannot be stubbed'), ); }); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index efb92fde6..2e4301cd6 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -37,8 +37,13 @@ class MockSpec { final bool returnNullOnMissingStub; - const MockSpec({Symbol as, this.returnNullOnMissingStub = false}) - : mockName = as; + final Map fallbackGenerators; + + const MockSpec({ + Symbol? as, + this.returnNullOnMissingStub = false, + this.fallbackGenerators = const {}, + }) : mockName = as; } ''' }; @@ -281,6 +286,105 @@ void main() { expect(mocksContent, isNot(contains('throwOnMissingStub'))); }); + test( + 'generates mock classes including a dummy builder for a generic method ' + 'with positional parameters', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + T m(T a); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T mShim(T a) { + if (a is int) return 1; + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim})], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' + ' returnValue: _i3.mShim(a)) as T)')); + }); + + test( + 'generates mock classes including a dummy builder for a generic method ' + 'with named parameters', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + T m({T a}); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T mShim({T a}) { + if (a is int) return 1; + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim})], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'T m({T? a}) => (super.noSuchMethod(Invocation.method(#m, [], {#a: a}),\n' + ' returnValue: _i3.mShim(a: a)) as T);')); + }); + + test( + 'generates mock classes including a dummy builder for a bounded generic ' + 'method with named parameters', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + T m({T a}); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T mShim({T a}) { + if (a is int) return 1; + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim})], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains('T m({T? a}) =>\n' + ' (super.noSuchMethod(Invocation.method(#m, [], {#a: a}),\n' + ' returnValue: _i3.mShim(a: a)) as T);')); + }); + test( 'throws when GenerateMocks is given a class with a type parameter with a ' 'private bound', () async { diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 17da34178..fa6e6a4de 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -16,3 +16,9 @@ class Foo { class FooSub extends Foo {} class Bar {} + +abstract class Baz { + T returnsTypeVariable(); + T returnsBoundedTypeVariable(); + T returnsTypeVariableFromTwo(); +} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 44eadc82a..39e45b5bb 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -5,6 +5,12 @@ import 'package:test/test.dart'; import 'foo.dart'; import 'generated_mocks_test.mocks.dart'; +T dummyMethod() => [1, 1.5].whereType().first!; + +T dummyBoundedMethod() => [1, 1.5].whereType().first!; + +T dummyMethodTwo() => [1, 1.5].whereType().first!; + @GenerateMocks([ Foo, FooSub, @@ -12,6 +18,11 @@ import 'generated_mocks_test.mocks.dart'; ], customMocks: [ MockSpec(as: #MockFooRelaxed, returnNullOnMissingStub: true), MockSpec(as: #MockBarRelaxed, returnNullOnMissingStub: true), + MockSpec(as: #MockBaz, fallbackGenerators: { + #returnsTypeVariable: dummyMethod, + #returnsBoundedTypeVariable: dummyBoundedMethod, + #returnsTypeVariableFromTwo: dummyMethodTwo, + }), ]) void main() { group('for a generated mock,', () { @@ -23,6 +34,12 @@ void main() { fooSub = MockFooSub(); }); + tearDown(() { + // In some of the tests that expect an Error to be thrown, Mockito's + // global state can become invalid. Reset it. + resetMockitoState(); + }); + test('a method with a positional parameter can be stubbed', () { when(foo.positionalParameter(42)).thenReturn('Stubbed'); expect(foo.positionalParameter(42), equals('Stubbed')); @@ -121,6 +138,31 @@ void main() { }); }); + group('for a generated mock using fallbackGenerators,', () { + late Baz baz; + + setUp(() { + baz = MockBaz(); + }); + + test('a method with a type variable return type can be called', () { + when(baz.returnsTypeVariable()).thenReturn(3); + baz.returnsTypeVariable(); + }); + + test('a method with a bounded type variable return type can be called', () { + when(baz.returnsBoundedTypeVariable()).thenReturn(3); + baz.returnsBoundedTypeVariable(); + }); + + test( + 'a method with multiple type parameters and a type variable return ' + 'type can be called', () { + when(baz.returnsTypeVariable()).thenReturn(3); + baz.returnsTypeVariable(); + }); + }); + group('for a generated mock using returnNullOnMissingStub,', () { late Foo foo; From 4edae1a90561422a35ab5a26bf0c78b6038ad5d0 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 25 May 2021 13:34:10 -0400 Subject: [PATCH 355/607] Add notes to CHANGELOG and NULL_SAFETY_README re: fallback generators Also add another test for fallback generators Also bump to 5.0.9 PiperOrigin-RevId: 375738620 --- pkgs/mockito/CHANGELOG.md | 12 +++++- pkgs/mockito/NULL_SAFETY_README.md | 40 +++++++++++++++++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 33 +++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 425c5217f..af40c6a1c 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,7 +1,17 @@ -## 5.0.9-dev +## 5.0.9 * Mock classes now implement a type's nested type arguments properly. [#410](https://github.com/dart-lang/mockito/issues/410) +* Mock classes now implement API from a class's interface(s) (in addition to + superclasses and mix ins). Thanks @markgravity. + [#404](https://github.com/dart-lang/mockito/pull/404) +* A MockSpec passed into a `@GenerateMocks` annotation's `customMocks` list can + now specify "fallback generators." These are functions which can be used to + generate fake responses that mockito's code generation needs in order to + return a value for a method with a generic return type. See + [NULL_SAFETY_README][] for details. + +[NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md ## 5.0.8 diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 50a2def58..2b6eecfca 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -156,6 +156,46 @@ sense in the Null safety type system), for legacy code, use @GenerateMocks([], customMocks: [MockSpec(returnNullOnMissingStub: true)]) ``` +#### Fallback generators + +If a class has a method with a type variable as a return type (for example, +`T get();`), mockito cannot generate code which will internally return valid +values. For example, given this class and test: + +```dart +abstract class Foo { + T m(T a); +} + +@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo, {#m: mShim})]) +void testFoo(Foo foo) { + when( foo.m(7) ).thenReturn(42); + // ^^^^^^^^ + // mockito needs a valid value which this call to `foo.m` will return. +} +``` + +In order to generate a mock for such a class, pass a `fallbackGenerators` +argument. Specify a mapping from the method to a top level function with the +same signature as the method: + +```dart +abstract class Foo { + T m(T a); +} + +T mShim(T a) { + if (a is int) return 1; + throw 'unknown'; +} + +@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo, {#m: mShim})]) +``` + +The fallback values will never be returned from a real method call; these are +not stub return values. They are only used internally by mockito as valid return +values. + ### Manual mock implementaion **In the general case, we strongly recommend generating mocks with the above diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index b3ae609cf..e141d2dd6 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.8'; +const packageVersion = '5.0.9'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 436686547..0d719cd60 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.8 +version: 5.0.9 description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 2e4301cd6..f4c6340a5 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -319,6 +319,39 @@ void main() { ' returnValue: _i3.mShim(a)) as T)')); }); + test( + 'generates mock classes including a dummy builder for a generic method ' + 'with positional parameters returning a Future of the generic', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + Future m(T a); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + Future mShim(T a) async { + if (a is int) return 1; + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim})], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + '_i3.Future m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' + ' returnValue: _i4.mShim(a)) as _i3.Future)')); + }); + test( 'generates mock classes including a dummy builder for a generic method ' 'with named parameters', () async { From 35d2a37891e76bdee00345817d438d935c25c584 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 26 May 2021 11:59:25 -0400 Subject: [PATCH 356/607] Documentation fixes: * Fix typo in README * Fix typo in NULL_SAFETY_README regarding fallback generators. * Add text in various argument matchers clarifying positional vs named arguments. * Add links in each argument matcher (`any`, `anyNamed`, `argThat`, `captureAny`, etc) back to their respective sections in the README. An alternative to the last item would be to inline the examples. I'd love to do this, but only via automated build... not sure how to do that yet. PiperOrigin-RevId: 375955488 --- pkgs/mockito/NULL_SAFETY_README.md | 4 ++- pkgs/mockito/README.md | 3 +- pkgs/mockito/lib/src/mock.dart | 48 +++++++++++++++++++++++++----- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 2b6eecfca..971aa4544 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -189,7 +189,9 @@ T mShim(T a) { throw 'unknown'; } -@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo, {#m: mShim})]) +@GenerateMocks([], customMocks: [ + MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim}) +]) ``` The fallback values will never be returned from a real method call; these are diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 5f6b063ae..91ea624f5 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -277,7 +277,8 @@ verifyNoMoreInteractions(cat); ## Capturing arguments for further assertions -Use the [`captureAny`], [`captureThat`], and [`captureNamed`] argument matchers: +Use the [`captureAny`], [`captureThat`], and [`captureAnyNamed`] argument +matchers: ```dart // Simple capture diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index b18ea83fb..f2db8112a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -703,31 +703,65 @@ class ArgMatcher { String toString() => '$ArgMatcher {$matcher: $_capture}'; } -/// An argument matcher that matches any argument passed in "this" position. +/// An argument matcher that matches any argument passed in this argument +/// position. +/// +/// See the README section on +/// [argument matchers](https://pub.dev/packages/mockito#argument-matchers) +/// for examples. Null get any => _registerMatcher(anything, false, argumentMatcher: 'any'); /// An argument matcher that matches any named argument passed in for the /// parameter named [named]. +/// +/// See the README section on +/// [named argument matchers](https://pub.dev/packages/mockito#named-arguments) +/// for examples. Null anyNamed(String named) => _registerMatcher(anything, false, named: named, argumentMatcher: 'anyNamed'); -/// An argument matcher that matches any argument passed in "this" position, and -/// captures the argument for later access with `captured`. +/// An argument matcher that matches any argument passed in this argument +/// position, and captures the argument for later access with +/// [VerificationResult.captured]. +/// +/// See the README section on +/// [capturing arguments](https://pub.dev/packages/mockito#capturing-arguments-for-further-assertions) +/// for examples. Null get captureAny => _registerMatcher(anything, true, argumentMatcher: 'captureAny'); /// An argument matcher that matches any named argument passed in for the /// parameter named [named], and captures the argument for later access with -/// `captured`. +/// [VerificationResult.captured]. +/// +/// See the README section on +/// [capturing arguments](https://pub.dev/packages/mockito#capturing-arguments-for-further-assertions) +/// for examples. Null captureAnyNamed(String named) => _registerMatcher(anything, true, named: named, argumentMatcher: 'captureAnyNamed'); -/// An argument matcher that matches an argument that matches [matcher]. +/// An argument matcher that matches an argument (named or positional) that +/// matches [matcher]. + +/// When capturing a named argument, the name of the argument must be passed via +/// [named]. +/// +/// See the README section on +/// [argument matchers](https://pub.dev/packages/mockito#argument-matchers) +/// for examples. Null argThat(Matcher matcher, {String? named}) => _registerMatcher(matcher, false, named: named, argumentMatcher: 'argThat'); -/// An argument matcher that matches an argument that matches [matcher], and -/// captures the argument for later access with `captured`. +/// An argument matcher that matches an argument (named or positional) that +/// matches [matcher], and captures the argument for later access with +/// [VerificationResult.captured]. + +/// When capturing a named argument, the name of the argument must be passed via +/// [named]. +/// +/// See the README section on +/// [capturing arguments](https://pub.dev/packages/mockito#capturing-arguments-for-further-assertions) +/// for examples. Null captureThat(Matcher matcher, {String? named}) => _registerMatcher(matcher, true, named: named, argumentMatcher: 'captureThat'); From 825e213cf977bcd4b3aa3f427b14b527c58edad9 Mon Sep 17 00:00:00 2001 From: Fabio Scopel Date: Fri, 28 May 2021 16:22:05 -0500 Subject: [PATCH 357/607] Follow Dart file conventions | Add more detail to the description field of pubspec.yaml. Use 60 to 180 characters to describe the package, what it does, and its target use case. --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 0d719cd60..6ef8609f7 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,7 +1,7 @@ name: mockito version: 5.0.9 -description: A mock framework inspired by Mockito. +description: A mock framework inspired by Mockito. With APIs for Fakes, Mocks, behavior verification, stubbing, and much more. homepage: https://github.com/dart-lang/mockito environment: From eb921b51ce061b6098d500d3873b5fc9254d1851 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 27 May 2021 14:04:41 -0400 Subject: [PATCH 358/607] Stop expecting that DartObject.type is ParameterizedType. It is just DartType, specifically because we want to make FunctionType just a DartType. PiperOrigin-RevId: 376206358 --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 18641942a..b980cdc7e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -400,7 +400,7 @@ class _MockTargetGatherer { final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { for (var mockSpec in customMocksField.toListValue()!) { - final mockSpecType = mockSpec.type!; + final mockSpecType = mockSpec.type as analyzer.InterfaceType; assert(mockSpecType.typeArguments.length == 1); final typeToMock = mockSpecType.typeArguments.single; if (typeToMock.isDynamic) { From c3eb1c7d999776c989a49593c79a5890a8d5d0f2 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 27 May 2021 14:33:59 -0400 Subject: [PATCH 359/607] Fix test about fallback generator signature. A fallback generator must have _approximately_ the same signature, but parameter types must all be nullable. Fixes https://github.com/dart-lang/mockito/issues/417 PiperOrigin-RevId: 376213042 --- pkgs/mockito/NULL_SAFETY_README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 971aa4544..e427a70eb 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -164,10 +164,10 @@ values. For example, given this class and test: ```dart abstract class Foo { - T m(T a); + T m(T a, int b); } -@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo, {#m: mShim})]) +@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) void testFoo(Foo foo) { when( foo.m(7) ).thenReturn(42); // ^^^^^^^^ @@ -176,15 +176,17 @@ void testFoo(Foo foo) { ``` In order to generate a mock for such a class, pass a `fallbackGenerators` -argument. Specify a mapping from the method to a top level function with the -same signature as the method: +argument. Specify a mapping from the method to a top level function with +_almost_ the same signature as the method. The function must have the same +return type as the method, and it must have the same positional and named +parameters as the method, except that each parameter must be made nullable: ```dart abstract class Foo { - T m(T a); + T m(T a, int b); } -T mShim(T a) { +T mShim(T a, int? b) { if (a is int) return 1; throw 'unknown'; } From a7401a34c4b137f9b168e4d39b6bce815e2a96c7 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 1 Jun 2021 15:37:37 -0400 Subject: [PATCH 360/607] Generate a proper mock class when the mocked class overrides `toString`, `hashCode`, or `operator==`. Calling `super.noSuchMethod` in any of these situations triggers the "missing stub" checks, and also conflicts with mockito behavior which relies on being able to use the [Mock] implementations. PiperOrigin-RevId: 376895132 --- pkgs/mockito/CHANGELOG.md | 6 ++++ pkgs/mockito/lib/src/builder.dart | 16 +++++++++ .../mockito/test/builder/auto_mocks_test.dart | 36 +++++++++++++++++-- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index af40c6a1c..f98811525 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.10-dev + +* Generate a proper mock class when the mocked class overrides `toString`, + `hashCode`, or `operator==`. + [#420](https://github.com/dart-lang/mockito/issues/420) + ## 5.0.9 * Mock classes now implement a type's nested type arguments properly. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b980cdc7e..4d7654903 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -848,6 +848,10 @@ class _MockLibraryInfo { if (overriddenFields.contains(accessor.name)) { continue; } + if (accessor.name == 'hashCode') { + // Never override this getter; user code cannot narrow the return type. + continue; + } overriddenFields.add(accessor.name); if (accessor.isGetter && _returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); @@ -895,6 +899,11 @@ class _MockLibraryInfo { if (methodName == 'noSuchMethod') { continue; } + if (methodName == '==') { + // Never override this operator; user code cannot add parameters or + // narrow the return type. + continue; + } if (_returnTypeIsNonNullable(method) || _hasNonNullableParameter(method) || _needsOverrideForVoidStub(method)) { @@ -986,6 +995,13 @@ class _MockLibraryInfo { } } + if (name == 'toString') { + // We cannot call `super.noSuchMethod` here; we must use [Mock]'s + // implementation. + builder.body = refer('super').property('toString').call([]).code; + return; + } + final invocation = refer('Invocation').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 764700be3..4eddf527c 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -615,7 +615,7 @@ void main() { ); }); - test('contains methods of implemented classes', () async { + test('overrides methods of implemented classes', () async { await expectSingleNonNullableOutput( dedent(r''' class Interface { @@ -628,7 +628,7 @@ void main() { ); }); - test('contains fields of implemented classes', () async { + test('overrides fields of implemented classes', () async { await expectSingleNonNullableOutput( dedent(r''' class Interface { @@ -688,6 +688,38 @@ void main() { ); }); + test('overrides `toString` with correct signature if the class overrides it', + () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class Foo { + String toString({bool a = false}); + } + '''), + _containsAllOf('String toString({bool? a = false}) => super.toString()'), + ); + }); + + test('does not override `operator==`, even if the class overrides it', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + class Foo { + bool operator==(Object? other); + } + ''')); + expect(mocksContent, isNot(contains('=='))); + }); + + test('does not override `hashCode`, even if the class overrides it', + () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + class Foo { + final int hashCode = 7; + } + ''')); + expect(mocksContent, isNot(contains('hashCode'))); + }); + test('generates mock classes from part files', () async { var mocksOutput = await buildWithNonNullable({ ...annotationsAsset, From e0326160082507bfa0941593caba1b7151748248 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 1 Jun 2021 16:02:25 -0400 Subject: [PATCH 361/607] Migrate off deprecated analyzer API: nonSubtypableClasses. PiperOrigin-RevId: 376900671 --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 4d7654903..1bc5918f5 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -457,7 +457,7 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'Mockito cannot mock an enum: ${elementToMock.displayName}'); } - if (typeProvider.nonSubtypableClasses.contains(elementToMock)) { + if (typeProvider.isNonSubtypableClass(elementToMock)) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock a non-subtypable type: ' '${elementToMock.displayName}. It is illegal to subtype this ' From 1208c23e581e5a0d2c3ae6ba9fdc872a02b49f61 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 3 Jun 2021 18:40:35 -0400 Subject: [PATCH 362/607] Override `toString` implementation on generated Fakes in order to match the signature of an overriding method which adds optional parameters. Fixes https://github.com/dart-lang/mockito/issues/371 PiperOrigin-RevId: 377388828 --- pkgs/mockito/CHANGELOG.md | 5 +- pkgs/mockito/lib/src/builder.dart | 72 +++++++++++-------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 20 ++++++ 5 files changed, 68 insertions(+), 33 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f98811525..7554929f4 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,11 @@ -## 5.0.10-dev +## 5.0.10 * Generate a proper mock class when the mocked class overrides `toString`, `hashCode`, or `operator==`. [#420](https://github.com/dart-lang/mockito/issues/420) +* Override `toString` implementation on generated Fakes in order to match the + signature of an overriding method which adds optional parameters. + [#371](https://github.com/dart-lang/mockito/issues/371) ## 5.0.9 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 1bc5918f5..a4d64a929 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -907,8 +907,7 @@ class _MockLibraryInfo { if (_returnTypeIsNonNullable(method) || _hasNonNullableParameter(method) || _needsOverrideForVoidStub(method)) { - yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method, - className: type.getDisplayString(withNullability: true))); + yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } } if (type.mixins != null) { @@ -960,8 +959,7 @@ class _MockLibraryInfo { /// /// This new method just calls `super.noSuchMethod`, optionally passing a /// return value for methods with a non-nullable return type. - void _buildOverridingMethod(MethodBuilder builder, MethodElement method, - {required String className}) { + void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { var name = method.displayName; if (method.isOperator) name = 'operator$name'; builder @@ -1161,11 +1159,7 @@ class _MockLibraryInfo { } Expression _dummyValueImplementing(analyzer.InterfaceType dartType) { - // For each type parameter on [dartType], the Mock class needs a type - // parameter with same type variables, and a mirrored type argument for the - // "implements" clause. - var typeParameters = []; - var elementToFake = dartType.element; + final elementToFake = dartType.element; if (elementToFake.isEnum) { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); @@ -1173,29 +1167,12 @@ class _MockLibraryInfo { // There is a potential for these names to collide. If one mock class // requires a fake for a certain Foo, and another mock class requires a // fake for a different Foo, they will collide. - var fakeName = '_Fake${elementToFake.name}'; + final fakeName = '_Fake${elementToFake.name}'; // Only make one fake class for each class that needs to be faked. if (!fakedClassElements.contains(elementToFake)) { - fakeClasses.add(Class((cBuilder) { - cBuilder - ..name = fakeName - ..extend = referImported('Fake', 'package:mockito/mockito.dart'); - if (elementToFake.typeParameters != null) { - for (var typeParameter in elementToFake.typeParameters) { - cBuilder.types.add(_typeParameterReference(typeParameter)); - typeParameters.add(refer(typeParameter.name)); - } - } - cBuilder.implements.add(TypeReference((b) { - b - ..symbol = elementToFake.name - ..url = _typeImport(elementToFake) - ..types.addAll(typeParameters); - })); - })); - fakedClassElements.add(elementToFake); - } - var typeArguments = dartType.typeArguments; + _addFakeClass(fakeName, elementToFake); + } + final typeArguments = dartType.typeArguments; return TypeReference((b) { b ..symbol = fakeName @@ -1204,6 +1181,41 @@ class _MockLibraryInfo { } } + /// Adds a [Fake] implementation of [elementToFake], named [fakeName]. + void _addFakeClass(String fakeName, ClassElement elementToFake) { + fakeClasses.add(Class((cBuilder) { + // For each type parameter on [elementToFake], the Fake class needs a type + // parameter with same type variables, and a mirrored type argument for + // the "implements" clause. + final typeParameters = []; + cBuilder + ..name = fakeName + ..extend = referImported('Fake', 'package:mockito/mockito.dart'); + if (elementToFake.typeParameters != null) { + for (var typeParameter in elementToFake.typeParameters) { + cBuilder.types.add(_typeParameterReference(typeParameter)); + typeParameters.add(refer(typeParameter.name)); + } + } + cBuilder.implements.add(TypeReference((b) { + b + ..symbol = elementToFake.name + ..url = _typeImport(elementToFake) + ..types.addAll(typeParameters); + })); + + final toStringMethod = elementToFake.methods + .firstWhereOrNull((method) => method.name == 'toString'); + if (toStringMethod != null) { + // If [elementToFake] includes an overriding `toString` implementation, + // we need to include an implementation which matches the signature. + cBuilder.methods.add(Method( + (mBuilder) => _buildOverridingMethod(mBuilder, toStringMethod))); + } + })); + fakedClassElements.add(elementToFake); + } + /// Returns a [Parameter] which matches [parameter]. /// /// If [parameter] is unnamed (like a positional parameter in a function diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index e141d2dd6..1b9e3c1a8 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.9'; +const packageVersion = '5.0.10'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 6ef8609f7..1324447ed 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.9 +version: 5.0.10 description: A mock framework inspired by Mockito. With APIs for Fakes, Mocks, behavior verification, stubbing, and much more. homepage: https://github.com/dart-lang/mockito diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 4eddf527c..5ff3693f6 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2224,6 +2224,26 @@ void main() { ); }); + test('generates a fake class with an overridden `toString` implementation', + () async { + await expectSingleNonNullableOutput( + dedent(''' + class Foo { + Bar m1() => Bar('name1'); + } + class Bar { + String toString({bool a = true}) => ''; + } + '''), + _containsAllOf(dedent(''' + class _FakeBar extends _i1.Fake implements _i2.Bar { + @override + String toString({bool? a = true}) => super.toString(); + } + ''')), + ); + }); + test('deduplicates fake classes', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { From eea032460ca2d170fc3d3d342d770492255b9728 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 4 Jun 2021 16:00:50 -0400 Subject: [PATCH 363/607] Use proper generic types in a generated mock class which comes from a "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`), mockito will now correctly reference `T` in generated code. Fixes https://github.com/dart-lang/mockito/issues/422 PiperOrigin-RevId: 377571931 --- pkgs/mockito/CHANGELOG.md | 5 ++ pkgs/mockito/lib/src/builder.dart | 83 +++++++++++-------- .../test/builder/custom_mocks_test.dart | 18 ++++ 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 7554929f4..297568231 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,6 +6,11 @@ * Override `toString` implementation on generated Fakes in order to match the signature of an overriding method which adds optional parameters. [#371](https://github.com/dart-lang/mockito/issues/371) + Properly type methods in a generated mock class which comes from a "custom + mock" annotation referencing an implicit type. Given a method which references + type variables defined on their enclosing class (for example, `T` in + `class Foo`), mockito will now correctly reference `T` in generated code. + [#422](https://github.com/dart-lang/mockito/issues/422) ## 5.0.9 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a4d64a929..9d29c311a 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -389,8 +389,10 @@ class _MockTargetGatherer { 'Mockito cannot mock `dynamic`'); } final type = _determineDartType(typeToMock, entryLib.typeProvider); - // [type] is `Foo` for generic classes. Switch to declaration, - // which will yield `Foo`. + // For a generic class like `Foo` or `Foo`, a type + // literal (`Foo`) cannot express type arguments. The type argument(s) on + // `type` have been instantiated to bounds here. Switch to the + // declaration, which will be an uninstantiated type. final declarationType = (type.element.declaration as ClassElement).thisType; final mockName = 'Mock${declarationType.element.name}'; @@ -409,6 +411,14 @@ class _MockTargetGatherer { 'arguments on MockSpec(), in @GenerateMocks.'); } var type = _determineDartType(typeToMock, entryLib.typeProvider); + + if (!type.hasExplicitTypeArguments) { + // We assume the type was given without explicit type arguments. In + // this case the type argument(s) on `type` have been instantiated to + // bounds. Switch to the declaration, which will be an uninstantiated + // type. + type = (type.element.declaration as ClassElement).thisType; + } final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? 'Mock${type.element.name}'; final returnNullOnMissingStub = @@ -734,39 +744,6 @@ class _MockLibraryInfo { } } - bool _hasExplicitTypeArguments(analyzer.InterfaceType type) { - if (type.typeArguments == null) return false; - - // If it appears that one type argument was given, then they all were. This - // returns the wrong result when the type arguments given are all `dynamic`, - // or are each equal to the bound of the corresponding type parameter. There - // may not be a way to get around this. - for (var i = 0; i < type.typeArguments.length; i++) { - var typeArgument = type.typeArguments[i]; - // If [typeArgument] is a type parameter, this indicates that no type - // arguments were passed. This likely came from the 'classes' argument of - // GenerateMocks, and [type] is the declaration type (`Foo` vs - // `Foo`). - if (typeArgument is analyzer.TypeParameterType) return false; - - // If [type] was given to @GenerateMocks as a Type, and no explicit type - // argument is given, [typeArgument] is `dynamic` (_not_ the bound, as one - // might think). We determine that an explicit type argument was given if - // it is not `dynamic`. - if (typeArgument.isDynamic) continue; - - // If, on the other hand, [type] was given to @GenerateMock as a type - // argument to `Of()`, and no type argument is given, [typeArgument] is - // the bound of the corresponding type paramter (dynamic or otherwise). We - // determine that an explicit type argument was given if [typeArgument] is - // not [bound]. - var bound = - type.element.typeParameters[i].bound ?? typeProvider.dynamicType; - if (!typeArgument.isDynamic && typeArgument != bound) return true; - } - return false; - } - Class _buildMockClass(_MockTarget mockTarget) { final typeToMock = mockTarget.classType; final classToMock = mockTarget.classElement; @@ -790,7 +767,7 @@ class _MockLibraryInfo { // parameter with same type variables, and a mirrored type argument for // the "implements" clause. var typeArguments = []; - if (_hasExplicitTypeArguments(typeToMock)) { + if (typeToMock.hasExplicitTypeArguments) { // [typeToMock] is a reference to a type with type arguments (for // example: `Foo`). Generate a non-generic mock class which // implements the mock target with said type arguments. For example: @@ -1561,3 +1538,37 @@ extension on analyzer.DartType { name == 'Uint64List'; } } + +extension on analyzer.InterfaceType { + bool get hasExplicitTypeArguments { + if (typeArguments == null) return false; + + // If it appears that one type argument was given, then they all were. This + // returns the wrong result when the type arguments given are all `dynamic`, + // or are each equal to the bound of the corresponding type parameter. There + // may not be a way to get around this. + for (var i = 0; i < typeArguments.length; i++) { + final typeArgument = typeArguments[i]; + // If [typeArgument] is a type parameter, this indicates that no type + // arguments were passed. This likely came from the 'classes' argument of + // GenerateMocks, and [type] is the declaration type (`Foo` vs + // `Foo`). + if (typeArgument is analyzer.TypeParameterType) return false; + + // If [type] was given to @GenerateMocks as a Type, and no explicit type + // argument is given, [typeArgument] is `dynamic` (_not_ the bound, as one + // might think). We determine that an explicit type argument was given if + // it is not `dynamic`. + if (typeArgument.isDynamic) continue; + + // If, on the other hand, [type] was given to @GenerateMock as a type + // argument to `MockSpec()`, and no type argument is given, [typeArgument] + // is the bound of the corresponding type paramter (`dynamic` or + // otherwise). We determine that an explicit type argument was given if + // [typeArgument] is not equal to [bound]. + final bound = element.typeParameters[i].bound; + if (!typeArgument.isDynamic && typeArgument != bound) return true; + } + return false; + } +} diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index f4c6340a5..1bf1c5433 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -121,6 +121,24 @@ void main() { contains('class MockFoo extends _i1.Mock implements _i2.Foo')); }); + test('without type arguments, generates generic method types', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + List f; + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) + void main() {} + ''' + }); + expect(mocksContent, contains('List get f =>')); + }); + test('generates a generic mock class with type arguments', () async { var mocksContent = await buildWithNonNullable({ ...annotationsAsset, From bf0d7ec349e0e68ccefce45422b1b285fe87f488 Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Fri, 18 Jun 2021 09:18:15 +0200 Subject: [PATCH 364/607] Delete travis.sh This uses the commands we're deprecating, and I don't think it's being used anymore after we migrated to Github Actions? --- pkgs/mockito/tool/travis.sh | 74 ------------------------------------- 1 file changed, 74 deletions(-) delete mode 100755 pkgs/mockito/tool/travis.sh diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh deleted file mode 100755 index 9a4cb7251..000000000 --- a/pkgs/mockito/tool/travis.sh +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2016 Dart Mockito authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#!/bin/bash - -if [ "$#" == "0" ]; then - echo -e '\033[31mAt least one task argument must be provided!\033[0m' - exit 1 -fi - -EXIT_CODE=0 - -while (( "$#" )); do - TASK=$1 - case $TASK in - dartfmt) echo - echo -e '\033[1mTASK: dartfmt\033[22m' - echo -e 'dartfmt -n --set-exit-if-changed .' - dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? - ;; - dartanalyzer) echo - echo -e '\033[1mTASK: dartanalyzer\033[22m' - echo -e 'dartanalyzer --fatal-warnings lib' - dartanalyzer --fatal-warnings lib || EXIT_CODE=$? - ;; - vm_test) echo - echo -e '\033[1mTASK: vm_test\033[22m' - echo -e 'pub run build_runner test -- -p vm' - pub run build_runner test -- -p vm || EXIT_CODE=$? - ;; - dartdevc_build) echo - echo -e '\033[1mTASK: build\033[22m' - echo -e 'pub run build_runner build --fail-on-severe' - pub run build_runner build --fail-on-severe || EXIT_CODE=$? - ;; - dartdevc_test) echo - echo -e '\033[1mTASK: dartdevc_test\033[22m' - echo -e 'pub run build_runner test -- -p chrome' - xvfb-run pub run build_runner test -- -p chrome || EXIT_CODE=$? - ;; - coverage) echo - echo -e '\033[1mTASK: coverage\033[22m' - if [ "$REPO_TOKEN" ]; then - echo -e 'pub run dart_coveralls report test/all.dart' - pub global activate dart_coveralls - pub global run dart_coveralls report \ - --token $REPO_TOKEN \ - --retry 2 \ - --exclude-test-files \ - test/all.dart - else - echo -e "\033[33mNo token for coveralls. Skipping.\033[0m" - fi - ;; - *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" - EXIT_CODE=1 - ;; - esac - - shift -done - -exit $EXIT_CODE From cc1b0c84a55fd459c95032e6c204b20c6965ebfa Mon Sep 17 00:00:00 2001 From: Jonathan Lau Date: Mon, 28 Jun 2021 21:01:19 +0800 Subject: [PATCH 365/607] Fix spelling and grammar typos in README.md --- pkgs/mockito/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 91ea624f5..fd8fce1f1 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -166,7 +166,7 @@ when(cat.walk(["roof","tree"])).thenReturn(2); when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); when(cat.eatFood(any)).thenReturn(false); -// ... or mix aguments with matchers +// ... or mix arguments with matchers when(cat.eatFood(argThat(startsWith("dry")), hungry: true)).thenReturn(true); expect(cat.eatFood("fish"), isTrue); expect(cat.walk(["roof","tree"]), equals(2)); @@ -380,8 +380,8 @@ tests. Finally an object which `extends Fake` using manually overridden methods is preferred over an object which `extends Mock` used as either a stub or a mock. -A class which `extends Mock` should _never_ stub out it's own responses with -`when` in it's constructor or anywhere else. Stubbed responses should be defined +A class which `extends Mock` should _never_ stub out its own responses with +`when` in its constructor or anywhere else. Stubbed responses should be defined in the tests where they are used. For responses controlled outside of the test use `@override` methods for either the entire interface, or with `extends Fake` to skip some parts of the interface. From ccdf50a72d689bb13a8ddfc93daa924f60e98cd5 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 8 Jun 2021 23:14:10 -0400 Subject: [PATCH 366/607] Improve fallback generator support in a few cases: * Allow two mocks of the same class (with different type arguments) to be specified with different fallback generators. * Allow fallback generators on super types of a mocked class. This change strongly suggested a code restructuring, moving the bulk of the code-building into a new "MockClassInfo" class, extracted from MockLibraryInfo. I've incorporated this change; most of the fields on MockLibraryInfo is moved to MockClassInfo; only a few fields remain, which are fields important to be shared across class-building code. PiperOrigin-RevId: 378307281 --- pkgs/mockito/CHANGELOG.md | 6 ++ pkgs/mockito/README.md | 6 +- pkgs/mockito/lib/src/builder.dart | 78 +++++++++------ pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 3 +- .../test/builder/custom_mocks_test.dart | 99 +++++++++++++++++-- pkgs/mockito/tool/travis.sh | 74 ++++++++++++++ 7 files changed, 221 insertions(+), 47 deletions(-) create mode 100755 pkgs/mockito/tool/travis.sh diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 297568231..ac0bc1d86 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.11-dev + +* Allow two mocks of the same class (with different type arguments) to be + specified with different fallback generators. +* Allow fallback generators on super types of a mocked class. + ## 5.0.10 * Generate a proper mock class when the mocked class overrides `toString`, diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index fd8fce1f1..91ea624f5 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -166,7 +166,7 @@ when(cat.walk(["roof","tree"])).thenReturn(2); when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); when(cat.eatFood(any)).thenReturn(false); -// ... or mix arguments with matchers +// ... or mix aguments with matchers when(cat.eatFood(argThat(startsWith("dry")), hungry: true)).thenReturn(true); expect(cat.eatFood("fish"), isTrue); expect(cat.walk(["roof","tree"]), equals(2)); @@ -380,8 +380,8 @@ tests. Finally an object which `extends Fake` using manually overridden methods is preferred over an object which `extends Mock` used as either a stub or a mock. -A class which `extends Mock` should _never_ stub out its own responses with -`when` in its constructor or anywhere else. Stubbed responses should be defined +A class which `extends Mock` should _never_ stub out it's own responses with +`when` in it's constructor or anywhere else. Stubbed responses should be defined in the tests where they are used. For responses controlled outside of the test use `@override` methods for either the entire interface, or with `extends Fake` to skip some parts of the interface. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 9d29c311a..484f5f245 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -693,14 +693,6 @@ class _MockTargetGatherer { } class _MockLibraryInfo { - final bool sourceLibIsNonNullable; - - /// The type provider which applies to the source library. - final TypeProvider typeProvider; - - /// The type system which applies to the source library. - final TypeSystem typeSystem; - /// Mock classes to be added to the generated library. final mockClasses = []; @@ -721,30 +713,55 @@ class _MockLibraryInfo { /// Asset-resolving while building the mock library. final Map assetUris; - /// A mapping of any fallback generators specified for the classes-to-mock. - /// - /// Each value is another mapping from method names to the generator - /// function elements. - final Map> fallbackGenerators; - /// Build mock classes for [mockTargets]. _MockLibraryInfo( Iterable<_MockTarget> mockTargets, { required this.assetUris, required LibraryElement entryLib, - }) : sourceLibIsNonNullable = entryLib.isNonNullableByDefault, - typeProvider = entryLib.typeProvider, - typeSystem = entryLib.typeSystem, - fallbackGenerators = { - for (final mockTarget in mockTargets) - mockTarget.classElement: mockTarget.fallbackGenerators - } { + }) { for (final mockTarget in mockTargets) { - mockClasses.add(_buildMockClass(mockTarget)); + final fallbackGenerators = mockTarget.fallbackGenerators; + mockClasses.add(_MockClassInfo( + mockTarget: mockTarget, + sourceLibIsNonNullable: entryLib.isNonNullableByDefault, + typeProvider: entryLib.typeProvider, + typeSystem: entryLib.typeSystem, + mockLibraryInfo: this, + fallbackGenerators: fallbackGenerators, + )._buildMockClass()); } } +} + +class _MockClassInfo { + final _MockTarget mockTarget; + + final bool sourceLibIsNonNullable; + + /// The type provider which applies to the source library. + final TypeProvider typeProvider; + + /// The type system which applies to the source library. + final TypeSystem typeSystem; + + final _MockLibraryInfo mockLibraryInfo; + + /// A mapping of any fallback generators specified for the classes-to-mock. + /// + /// Each value is another mapping from method names to the generator + /// function elements. + final Map fallbackGenerators; + + _MockClassInfo({ + required this.mockTarget, + required this.sourceLibIsNonNullable, + required this.typeProvider, + required this.typeSystem, + required this.mockLibraryInfo, + required this.fallbackGenerators, + }); - Class _buildMockClass(_MockTarget mockTarget) { + Class _buildMockClass() { final typeToMock = mockTarget.classType; final classToMock = mockTarget.classElement; final classIsImmutable = classToMock.metadata.any((it) => it.isImmutable); @@ -989,10 +1006,7 @@ class _MockLibraryInfo { } else if (method.returnType.isFutureOfVoid) { returnValueForMissingStub = _futureReference().property('value').call([]); } - final class_ = method.enclosingElement; - final fallbackGenerator = fallbackGenerators.containsKey(class_) - ? fallbackGenerators[class_]![method.name] - : null; + final fallbackGenerator = fallbackGenerators[method.name]; final namedArgs = { if (fallbackGenerator != null) 'returnValue': _fallbackGeneratorCode(method, fallbackGenerator) @@ -1146,7 +1160,7 @@ class _MockLibraryInfo { // fake for a different Foo, they will collide. final fakeName = '_Fake${elementToFake.name}'; // Only make one fake class for each class that needs to be faked. - if (!fakedClassElements.contains(elementToFake)) { + if (!mockLibraryInfo.fakedClassElements.contains(elementToFake)) { _addFakeClass(fakeName, elementToFake); } final typeArguments = dartType.typeArguments; @@ -1160,7 +1174,7 @@ class _MockLibraryInfo { /// Adds a [Fake] implementation of [elementToFake], named [fakeName]. void _addFakeClass(String fakeName, ClassElement elementToFake) { - fakeClasses.add(Class((cBuilder) { + mockLibraryInfo.fakeClasses.add(Class((cBuilder) { // For each type parameter on [elementToFake], the Fake class needs a type // parameter with same type variables, and a mirrored type argument for // the "implements" clause. @@ -1190,7 +1204,7 @@ class _MockLibraryInfo { (mBuilder) => _buildOverridingMethod(mBuilder, toStringMethod))); } })); - fakedClassElements.add(elementToFake); + mockLibraryInfo.fakedClassElements.add(elementToFake); } /// Returns a [Parameter] which matches [parameter]. @@ -1458,10 +1472,10 @@ class _MockLibraryInfo { // For types like `dynamic`, return null; no import needed. if (element?.library == null) return null; - assert(assetUris.containsKey(element), + assert(mockLibraryInfo.assetUris.containsKey(element), 'An element, "$element", is missing from the asset URI mapping'); - return assetUris[element]!; + return mockLibraryInfo.assetUris[element]!; } /// Returns a [Reference] to [symbol] with [url]. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1324447ed..0f30078cc 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,7 +1,7 @@ name: mockito version: 5.0.10 -description: A mock framework inspired by Mockito. With APIs for Fakes, Mocks, behavior verification, stubbing, and much more. +description: A mock framework inspired by Mockito. homepage: https://github.com/dart-lang/mockito environment: diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 5ff3693f6..4b132dacc 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -120,8 +120,7 @@ void main() { /// Test [MockBuilder] on a single source file, in a package which has opted /// into null safety, and with the non-nullable experiment enabled. - Future expectSingleNonNullableOutput( - String sourceAssetText, + Future expectSingleNonNullableOutput(String sourceAssetText, /*String|Matcher>*/ Object output) async { await testWithNonNullable({ ...metaAssets, diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 1bf1c5433..99e14f777 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -305,8 +305,8 @@ void main() { }); test( - 'generates mock classes including a dummy builder for a generic method ' - 'with positional parameters', () async { + 'generates mock classes including a fallback generator for a generic ' + 'method with positional parameters', () async { var mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -325,7 +325,9 @@ void main() { @GenerateMocks( [], - customMocks: [MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim})], + customMocks: [ + MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim}), + ], ) void main() {} ''' @@ -338,8 +340,87 @@ void main() { }); test( - 'generates mock classes including a dummy builder for a generic method ' - 'with positional parameters returning a Future of the generic', () async { + 'generates mock classes including a fallback generator for a generic ' + 'method on a super class', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class FooBase { + T m(T a); + } + abstract class Foo extends FooBase {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T mShim(T a) { + if (a is int) return 1; + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [ + MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim}), + ], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' + ' returnValue: _i3.mShim(a)) as T)')); + }); + + test('generates mock classes including two fallback generators', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + T m(T a); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T mShimA(T a) { + throw 'unknown'; + } + + T mShimB(T a) { + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [ + MockSpec>(as: #MockFooA, fallbackGenerators: {#m: mShimA}), + MockSpec>(as: #MockFooB, fallbackGenerators: {#m: mShimB}), + ], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' + ' returnValue: _i3.mShimA(a)) as T)')); + expect( + mocksContent, + contains( + 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' + ' returnValue: _i3.mShimB(a)) as T)')); + }); + + test( + 'generates mock classes including a fallback generator for a generic ' + 'method with positional parameters returning a Future of the generic', + () async { var mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -371,8 +452,8 @@ void main() { }); test( - 'generates mock classes including a dummy builder for a generic method ' - 'with named parameters', () async { + 'generates mock classes including a fallback generator for a generic ' + 'method with named parameters', () async { var mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -404,8 +485,8 @@ void main() { }); test( - 'generates mock classes including a dummy builder for a bounded generic ' - 'method with named parameters', () async { + 'generates mock classes including a fallback generator for a bounded ' + 'generic method with named parameters', () async { var mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh new file mode 100755 index 000000000..9a4cb7251 --- /dev/null +++ b/pkgs/mockito/tool/travis.sh @@ -0,0 +1,74 @@ +# Copyright 2016 Dart Mockito authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#!/bin/bash + +if [ "$#" == "0" ]; then + echo -e '\033[31mAt least one task argument must be provided!\033[0m' + exit 1 +fi + +EXIT_CODE=0 + +while (( "$#" )); do + TASK=$1 + case $TASK in + dartfmt) echo + echo -e '\033[1mTASK: dartfmt\033[22m' + echo -e 'dartfmt -n --set-exit-if-changed .' + dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? + ;; + dartanalyzer) echo + echo -e '\033[1mTASK: dartanalyzer\033[22m' + echo -e 'dartanalyzer --fatal-warnings lib' + dartanalyzer --fatal-warnings lib || EXIT_CODE=$? + ;; + vm_test) echo + echo -e '\033[1mTASK: vm_test\033[22m' + echo -e 'pub run build_runner test -- -p vm' + pub run build_runner test -- -p vm || EXIT_CODE=$? + ;; + dartdevc_build) echo + echo -e '\033[1mTASK: build\033[22m' + echo -e 'pub run build_runner build --fail-on-severe' + pub run build_runner build --fail-on-severe || EXIT_CODE=$? + ;; + dartdevc_test) echo + echo -e '\033[1mTASK: dartdevc_test\033[22m' + echo -e 'pub run build_runner test -- -p chrome' + xvfb-run pub run build_runner test -- -p chrome || EXIT_CODE=$? + ;; + coverage) echo + echo -e '\033[1mTASK: coverage\033[22m' + if [ "$REPO_TOKEN" ]; then + echo -e 'pub run dart_coveralls report test/all.dart' + pub global activate dart_coveralls + pub global run dart_coveralls report \ + --token $REPO_TOKEN \ + --retry 2 \ + --exclude-test-files \ + test/all.dart + else + echo -e "\033[33mNo token for coveralls. Skipping.\033[0m" + fi + ;; + *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" + EXIT_CODE=1 + ;; + esac + + shift +done + +exit $EXIT_CODE From edfba5e4557818197e11fa4649c8215453e4a4da Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Tue, 29 Jun 2021 14:39:36 -0400 Subject: [PATCH 367/607] Prepare to make LibraryElement.name non-nullable. https://dart-review.googlesource.com/c/sdk/+/205140 PiperOrigin-RevId: 382130899 --- pkgs/mockito/lib/src/builder.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 484f5f245..87c33de4e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -148,6 +148,7 @@ $rawOutput for (final element in elements) { final elementLibrary = element.library!; if (elementLibrary.isInSdk) { + // ignore:unnecessary_non_null_assertion if (elementLibrary.name!.startsWith('dart._')) { typeUris[element] = _findPublicExportOf( Queue.of(librariesWithTypes), elementLibrary)!; From f4e9a6a10e2b9362090091193ff337d650eacd93 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 1 Jul 2021 18:43:27 -0400 Subject: [PATCH 368/607] Import cc1b0c84a55fd459c95032e6c204b20c6965ebfa from GitHub https://github.com/dart-lang/mockito/commit/cc1b0c84a55fd459c95032e6c204b20c6965ebfa Fix spelling and grammar typos in README.md PiperOrigin-RevId: 382620480 --- pkgs/mockito/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 91ea624f5..fd8fce1f1 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -166,7 +166,7 @@ when(cat.walk(["roof","tree"])).thenReturn(2); when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); when(cat.eatFood(any)).thenReturn(false); -// ... or mix aguments with matchers +// ... or mix arguments with matchers when(cat.eatFood(argThat(startsWith("dry")), hungry: true)).thenReturn(true); expect(cat.eatFood("fish"), isTrue); expect(cat.walk(["roof","tree"]), equals(2)); @@ -380,8 +380,8 @@ tests. Finally an object which `extends Fake` using manually overridden methods is preferred over an object which `extends Mock` used as either a stub or a mock. -A class which `extends Mock` should _never_ stub out it's own responses with -`when` in it's constructor or anywhere else. Stubbed responses should be defined +A class which `extends Mock` should _never_ stub out its own responses with +`when` in its constructor or anywhere else. Stubbed responses should be defined in the tests where they are used. For responses controlled outside of the test use `@override` methods for either the entire interface, or with `extends Fake` to skip some parts of the interface. From c3d042ca8fb87d9edc23700cbca036f7dbbd52e4 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 1 Jul 2021 19:58:33 -0400 Subject: [PATCH 369/607] Import 825e213cf977bcd4b3aa3f427b14b527c58edad9 from GitHub https://github.com/dart-lang/mockito/commit/825e213cf977bcd4b3aa3f427b14b527c58edad9 Follow Dart file conventions | Add more detail to the description field of pubspec.yaml. Use 60 to 180 characters to describe the package, what it does, and its target use case. PiperOrigin-RevId: 382634050 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 0f30078cc..bb6f5678b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,7 +1,7 @@ name: mockito version: 5.0.10 -description: A mock framework inspired by Mockito. +description: A mock framework inspired by Mockito. With APIs for Fakes, Mocks, behavior verification, stubbing, and much more. homepage: https://github.com/dart-lang/mockito environment: From cbd969b9ba3a391186b146287af47a08c31722c5 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 2 Jul 2021 12:26:23 -0400 Subject: [PATCH 370/607] Wrap and improve package description. PiperOrigin-RevId: 382755039 --- pkgs/mockito/pubspec.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index bb6f5678b..0f1b805f1 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,7 +1,9 @@ name: mockito version: 5.0.10 -description: A mock framework inspired by Mockito. With APIs for Fakes, Mocks, behavior verification, stubbing, and much more. +description: >- + A mock framework inspired by Mockito with APIs for Fakes, Mocks, + behavior verification, and stubbing. homepage: https://github.com/dart-lang/mockito environment: From 0bfd05130adbc83127980a578a4946702c052ea8 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 2 Jul 2021 12:33:29 -0400 Subject: [PATCH 371/607] Ignore implementation_imports Generated code may import 'src' files. PiperOrigin-RevId: 382756565 --- pkgs/mockito/lib/src/builder.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 87c33de4e..fea64b00f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -78,6 +78,8 @@ class MockBuilder implements Builder { Code('\n\n// ignore_for_file: avoid_redundant_argument_values\n')); // We don't properly prefix imported class names in doc comments. b.body.add(Code('// ignore_for_file: comment_references\n')); + // We might import a package's 'src' directory. + b.body.add(Code('// ignore_for_file: implementation_imports\n')); // `Mock.noSuchMethod` is `@visibleForTesting`, but the generated code is // not always in a test directory; the Mockito `example/iss` tests, for // example. From a079974be79fa448de86c5b2ccf337d6c70a5505 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 2 Jul 2021 13:40:52 -0400 Subject: [PATCH 372/607] Explicitly declare a Future when using Future.value The inference_failure_on_instance_creation error will trigger on code like ``` returnValueForMissingStub: Future.value() ``` because, even though no value has been given to `value()`, that does not affect the Future's type (it still defaults to `void`). Explicitly using `Future.value()` satisfies the check. PiperOrigin-RevId: 382770034 --- pkgs/mockito/lib/src/builder.dart | 3 ++- pkgs/mockito/test/builder/auto_mocks_test.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index fea64b00f..0b235890b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1007,7 +1007,8 @@ class _MockClassInfo { if (method.returnType.isVoid) { returnValueForMissingStub = refer('null'); } else if (method.returnType.isFutureOfVoid) { - returnValueForMissingStub = _futureReference().property('value').call([]); + returnValueForMissingStub = + _futureReference(refer('void')).property('value').call([]); } final fallbackGenerator = fallbackGenerators[method.name]; final namedArgs = { diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 4b132dacc..a1aaa6098 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -526,7 +526,7 @@ void main() { _containsAllOf(dedent2(''' _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); + returnValueForMissingStub: Future.value()) as _i3.Future); ''')), ); }); From f409901cea80d273c9d16d3eb06599af4bff6fde Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 15 Jul 2021 01:04:58 -0400 Subject: [PATCH 373/607] Support Function and Future return types Without this support, mockito tried to create a FakeFunction which implemented Function, which is not allowed. Fixes https://github.com/dart-lang/mockito/issues/442 PiperOrigin-RevId: 384852004 --- pkgs/mockito/CHANGELOG.md | 5 ++++- pkgs/mockito/lib/src/builder.dart | 2 ++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/builder/auto_mocks_test.dart | 16 ++++++++++++++++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ac0bc1d86..a9263a912 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,11 @@ -## 5.0.11-dev +## 5.0.11 * Allow two mocks of the same class (with different type arguments) to be specified with different fallback generators. * Allow fallback generators on super types of a mocked class. +* Avoid `inference_failure_on_instance_creation` errors in generated code. +* Ignore `implementation_imports` lint in generated code. +* Support methods with `Function` and `Future` return types. ## 5.0.10 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0b235890b..5e23b4284 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1062,6 +1062,8 @@ class _MockClassInfo { return literalFalse; } else if (type.isDartCoreDouble) { return literalNum(0.0); + } else if (type.isDartCoreFunction) { + return refer('() {}'); } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { final typeArgument = typeArguments.first; final futureValueArguments = diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 1b9e3c1a8..546ddda86 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.10'; +const packageVersion = '5.0.11'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 0f1b805f1..ee9b64325 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.10 +version: 5.0.11 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index a1aaa6098..35fa05ce4 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1898,6 +1898,22 @@ void main() { ); }); + test( + 'creates dummy non-null return values for Futures of core Function class', + () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class Foo { + Future m(); + } + '''), + _containsAllOf(dedent2(''' + _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: Future.value(() {})) as _i3.Future); + ''')), + ); + }); + test('creates dummy non-null return values for Futures of nullable types', () async { await expectSingleNonNullableOutput( From b51fc96729fe5524c1efdbd98a634f185016d732 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 15 Jul 2021 14:02:14 -0400 Subject: [PATCH 374/607] Include type argument when returning a fallback value for an `Iterable`. Before this fix, we would only return `[]` which had an implicit dynamic: `[]`. The proper return value should explicitly include the expected type argument. Existing test cases cover the desired behavior. Also replace `var` with `final` in surrounding code. Fixes https://github.com/dart-lang/mockito/issues/445 PiperOrigin-RevId: 384965296 --- pkgs/mockito/CHANGELOG.md | 6 ++++++ pkgs/mockito/lib/src/builder.dart | 14 ++++++-------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/builder/auto_mocks_test.dart | 11 ++++++----- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a9263a912..e6fae8701 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.12-dev + +* Use an empty list with a correct type argument for a fallback value for a + method which returns Iterable. + [#445](https://github.com/dart-lang/mockito/issues/445) + ## 5.0.11 * Allow two mocks of the same class (with different type arguments) to be diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 5e23b4284..4a4ed2aa8 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1075,26 +1075,24 @@ class _MockClassInfo { .call(futureValueArguments); } else if (type.isDartCoreInt) { return literalNum(0); - } else if (type.isDartCoreIterable) { - return literalList([]); - } else if (type.isDartCoreList) { + } else if (type.isDartCoreIterable || type.isDartCoreList) { assert(typeArguments.length == 1); - var elementType = _typeReference(typeArguments[0]); + final elementType = _typeReference(typeArguments[0]); return literalList([], elementType); } else if (type.isDartCoreMap) { assert(typeArguments.length == 2); - var keyType = _typeReference(typeArguments[0]); - var valueType = _typeReference(typeArguments[1]); + final keyType = _typeReference(typeArguments[0]); + final valueType = _typeReference(typeArguments[1]); return literalMap({}, keyType, valueType); } else if (type.isDartCoreNum) { return literalNum(0); } else if (type.isDartCoreSet) { assert(typeArguments.length == 1); - var elementType = _typeReference(typeArguments[0]); + final elementType = _typeReference(typeArguments[0]); return literalSet({}, elementType); } else if (type.element.declaration == typeProvider.streamElement) { assert(typeArguments.length == 1); - var elementType = _typeReference(typeArguments[0]); + final elementType = _typeReference(typeArguments[0]); return TypeReference((b) { b ..symbol = 'Stream' diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 546ddda86..32a586d7f 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.11'; +const packageVersion = '5.0.12-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index ee9b64325..f1668b254 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.11 +version: 5.0.12-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 35fa05ce4..7550357d7 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -120,7 +120,8 @@ void main() { /// Test [MockBuilder] on a single source file, in a package which has opted /// into null safety, and with the non-nullable experiment enabled. - Future expectSingleNonNullableOutput(String sourceAssetText, + Future expectSingleNonNullableOutput( + String sourceAssetText, /*String|Matcher>*/ Object output) async { await testWithNonNullable({ ...metaAssets, @@ -554,7 +555,7 @@ void main() { '''), _containsAllOf(dedent2(''' Iterable m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: []) + (super.noSuchMethod(Invocation.method(#m, []), returnValue: []) as Iterable); ''')), ); @@ -1950,8 +1951,8 @@ void main() { }); test( - 'creates dummy non-null return values for Futures of known generic core classes', - () async { + 'creates dummy non-null return values for Futures of known generic core ' + 'classes', () async { await expectSingleNonNullableOutput( dedent(r''' class Foo { @@ -1961,7 +1962,7 @@ void main() { _containsAllOf(dedent2(''' _i3.Future> m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future>.value([])) + returnValue: Future>.value([])) as _i3.Future>); ''')), ); From 96c0929b8c3bf4a2d1e497c7398c70f106d45fc1 Mon Sep 17 00:00:00 2001 From: srawlins Date: Sat, 17 Jul 2021 20:26:40 -0400 Subject: [PATCH 375/607] Support analyzer >=1.0.0 <3.0.0 PiperOrigin-RevId: 385356711 --- pkgs/mockito/CHANGELOG.md | 3 ++- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index e6fae8701..5adf0bbbf 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,9 @@ -## 5.0.12-dev +## 5.0.12 * Use an empty list with a correct type argument for a fallback value for a method which returns Iterable. [#445](https://github.com/dart-lang/mockito/issues/445) +* Support analyzer 2.0.0 ## 5.0.11 diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 32a586d7f..7f8e03aa6 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.12-dev'; +const packageVersion = '5.0.12'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index f1668b254..76893587c 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.12-dev +version: 5.0.12 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: ^1.0.0 + analyzer: '>=1.0.0 <3.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 From 221903a613cb6360a06385708b9635fa6b154745 Mon Sep 17 00:00:00 2001 From: srawlins Date: Sun, 18 Jul 2021 12:30:02 -0400 Subject: [PATCH 376/607] Prefer to import libraries which export types that need referencing. If the generated code needs to represent a type, `T`, and that type is declared in library `L`, then we now prefer to import a library which _exports_ `L` (if one exists), over importing `L` directly. To find such a library, we look at the type, `U` which references `T`. Perhaps `U` is a class to mocked, and `U` has a method with a return type `T`, or `U` is a supertype of a class to be mocked, which has a method with a parameter type `T`, etc. We examine all of the import libraries, `IL`, of the library in which `U` is declared, and all of the libraries which are exported by the libraries `IL`. If the type `T` is declared in a library which is exported as a conditional export, this strategy avoids complications with the conditional export. Additionally, as a heuristic, it generally leads to public libraries which export private implementation, avoiding importing the private implementation directly. Fixes https://github.com/dart-lang/mockito/issues/443 PiperOrigin-RevId: 385425794 --- pkgs/mockito/CHANGELOG.md | 4 ++ pkgs/mockito/lib/src/builder.dart | 45 +++++++++---------- .../mockito/test/builder/auto_mocks_test.dart | 29 +++++++++++- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 5adf0bbbf..6538c1561 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,10 @@ * Use an empty list with a correct type argument for a fallback value for a method which returns Iterable. [#445](https://github.com/dart-lang/mockito/issues/445) +* When selecting the library that should be imported in order to reference a + type, prefer a library which exports the library in which the type is + declared. This avoids some confusion with conditional exports. + [#443](https://github.com/dart-lang/mockito/issues/443) * Support analyzer 2.0.0 ## 5.0.11 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 4a4ed2aa8..397534fb6 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -149,19 +149,15 @@ $rawOutput for (final element in elements) { final elementLibrary = element.library!; - if (elementLibrary.isInSdk) { - // ignore:unnecessary_non_null_assertion - if (elementLibrary.name!.startsWith('dart._')) { - typeUris[element] = _findPublicExportOf( - Queue.of(librariesWithTypes), elementLibrary)!; - } else { - typeUris[element] = elementLibrary.source.uri.toString(); - } + if (elementLibrary.isInSdk && !elementLibrary.name.startsWith('dart._')) { + // For public SDK libraries, just use the source URI. + typeUris[element] = elementLibrary.source.uri.toString(); continue; } + final exportingLibrary = _findExportOf(librariesWithTypes, element); try { - final typeAssetId = await resolver.assetIdForElement(elementLibrary); + final typeAssetId = await resolver.assetIdForElement(exportingLibrary); if (typeAssetId.path.startsWith('lib/')) { typeUris[element] = typeAssetId.uri.toString(); @@ -171,32 +167,35 @@ $rawOutput } } on UnresolvableAssetException { // Asset may be in a summary. - typeUris[element] = elementLibrary.source.uri.toString(); - continue; + typeUris[element] = exportingLibrary.source.uri.toString(); } } return typeUris; } - /// Returns the String import path of the correct public library which - /// exports [privateLibrary], selecting from the imports of [inputLibraries]. - static String? _findPublicExportOf( - Queue inputLibraries, LibraryElement privateLibrary) { + /// Returns a library which exports [element], selecting from the imports of + /// [inputLibraries] (and all exported libraries). + /// + /// If [element] is not exported by any libraries in this set, then + /// [element]'s declaring library is returned. + static LibraryElement _findExportOf( + Iterable inputLibraries, Element element) { + final elementName = element.name; + if (elementName == null) { + return element.library!; + } + final libraries = Queue.of([ for (final library in inputLibraries) ...library.importedLibraries, ]); - while (libraries.isNotEmpty) { - final library = libraries.removeFirst(); - if (library.exportedLibraries.contains(privateLibrary)) { - return library.source.uri.toString(); + for (final library in libraries) { + if (library.exportNamespace.get(elementName) == element) { + return library; } - // A library may provide [privateLibrary] by exporting a library which - // provides it (directly or via further exporting). - libraries.addAll(library.exportedLibraries); } - return null; + return element.library!; } @override diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 7550357d7..1735c90b7 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -120,8 +120,7 @@ void main() { /// Test [MockBuilder] on a single source file, in a package which has opted /// into null safety, and with the non-nullable experiment enabled. - Future expectSingleNonNullableOutput( - String sourceAssetText, + Future expectSingleNonNullableOutput(String sourceAssetText, /*String|Matcher>*/ Object output) async { await testWithNonNullable({ ...metaAssets, @@ -1140,6 +1139,32 @@ void main() { expect(mocksContent, contains('_i2.HttpStatus f() =>')); }); + test('imports libraries which export external class types', () async { + await testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'types.dart'; + abstract class Foo { + void m(Bar a); + } + ''', + 'foo|lib/types.dart': ''' + export 'base.dart' if (dart.library.html) 'html.dart'; + ''', + 'foo|lib/base.dart': ''' + class Bar {} + ''', + 'foo|lib/html.dart': ''' + class Bar {} + ''', + }); + final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); + final mocksContent = utf8.decode(writer.assets[mocksAsset]!); + expect(mocksContent, contains("import 'package:foo/types.dart' as _i3;")); + expect(mocksContent, contains('m(_i3.Bar? a)')); + }); + test('prefixes parameter type on generic function-typed parameter', () async { await expectSingleNonNullableOutput( dedent(r''' From 8a6de13a348738bf38f98d97908320dc8adb0bc5 Mon Sep 17 00:00:00 2001 From: srawlins Date: Sun, 18 Jul 2021 16:04:36 -0400 Subject: [PATCH 377/607] Fix null check required for analyzer <2.0.0 PiperOrigin-RevId: 385440655 --- pkgs/mockito/lib/src/builder.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 397534fb6..0ea450adc 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -149,7 +149,10 @@ $rawOutput for (final element in elements) { final elementLibrary = element.library!; - if (elementLibrary.isInSdk && !elementLibrary.name.startsWith('dart._')) { + if (elementLibrary.isInSdk && + // TODO(srawlins): Remove this when analyzer dep is >=2.0.0. + // ignore: unnecessary_non_null_assertion + !elementLibrary.name!.startsWith('dart._')) { // For public SDK libraries, just use the source URI. typeUris[element] = elementLibrary.source.uri.toString(); continue; From 59019a64a34051f6d6f696b225d75b8c7b5f973d Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 20 Jul 2021 12:20:08 -0400 Subject: [PATCH 378/607] Ignore avoid_setters_without_getters lint in generated code. We might generate setters without getters. PiperOrigin-RevId: 385802287 --- pkgs/mockito/lib/src/builder.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0ea450adc..575a41446 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -76,6 +76,8 @@ class MockBuilder implements Builder { // are necessary. b.body.add( Code('\n\n// ignore_for_file: avoid_redundant_argument_values\n')); + // We might generate a setter without a corresponding getter. + b.body.add(Code('// ignore_for_file: avoid_setters_without_getters\n')); // We don't properly prefix imported class names in doc comments. b.body.add(Code('// ignore_for_file: comment_references\n')); // We might import a package's 'src' directory. From f1c784c60e844bfd31f4f64a74e79b3e3001762f Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 20 Jul 2021 15:59:50 -0400 Subject: [PATCH 379/607] Fix two related bugs affected by overriding toString methods. * Properly reference types in overridden `toString` implementations. Fixes dart-lang/mockito#438 * Override `toString` in a Fake implementation when the class-to-be-faked has a superclass which overrides `toString` with additional parameters. Fixes dart-lang/mockito#371 PiperOrigin-RevId: 385852381 --- pkgs/mockito/CHANGELOG.md | 5 +++ pkgs/mockito/lib/src/builder.dart | 24 ++++++++++-- .../mockito/test/builder/auto_mocks_test.dart | 39 +++++++++++++++++-- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 6538c1561..fc7271885 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -7,6 +7,11 @@ type, prefer a library which exports the library in which the type is declared. This avoids some confusion with conditional exports. [#443](https://github.com/dart-lang/mockito/issues/443) +* Properly reference types in overridden `toString` implementations. + [#438](https://github.com/dart-lang/mockito/issues/438) +* Override `toString` in a Fake implementation when the class-to-be-faked has + a superclass which overrides `toString` with additional parameters. + [#371](https://github.com/dart-lang/mockito/issues/371) * Support analyzer 2.0.0 ## 5.0.11 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 575a41446..d25d9e140 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -257,6 +257,21 @@ class _TypeVisitor extends RecursiveElementVisitor { type.typeArguments.forEach(_addType); if (!alreadyVisitedElement) { type.element.typeParameters.forEach(visitTypeParameterElement); + + final toStringMethod = + type.element.lookUpMethod('toString', type.element.library); + if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { + // In a Fake class which implements a class which overrides `toString` + // with additional (optional) parameters, we must also override + // `toString` and reference the same types referenced in those + // parameters. + for (final parameter in toStringMethod.parameters) { + final parameterType = parameter.type; + if (parameterType is analyzer.InterfaceType) { + parameterType.element.accept(this); + } + } + } } } else if (type is analyzer.FunctionType) { _addType(type.returnType); @@ -1203,9 +1218,9 @@ class _MockClassInfo { ..types.addAll(typeParameters); })); - final toStringMethod = elementToFake.methods - .firstWhereOrNull((method) => method.name == 'toString'); - if (toStringMethod != null) { + final toStringMethod = + elementToFake.lookUpMethod('toString', elementToFake.library); + if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { // If [elementToFake] includes an overriding `toString` implementation, // we need to include an implementation which matches the signature. cBuilder.methods.add(Method( @@ -1483,7 +1498,8 @@ class _MockClassInfo { assert(mockLibraryInfo.assetUris.containsKey(element), 'An element, "$element", is missing from the asset URI mapping'); - return mockLibraryInfo.assetUris[element]!; + return mockLibraryInfo.assetUris[element] ?? + (throw StateError('Asset URI is missing for $element')); } /// Returns a [Reference] to [symbol] with [url]. diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 1735c90b7..717ed235a 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -687,13 +687,31 @@ void main() { ); }); - test('overrides `toString` with correct signature if the class overrides it', - () async { + test( + 'overrides `toString` with a correct signature if the class overrides ' + 'it', () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class Foo { + String toString({bool a = false}); + } + '''), + _containsAllOf('String toString({bool? a = false}) => super.toString()'), + ); + }); + + test( + 'overrides `toString` with a correct signature if a mixed in class ' + 'overrides it, in a Fake', () async { await expectSingleNonNullableOutput( dedent(''' abstract class Foo { + Bar m(); + } + abstract class BarBase { String toString({bool a = false}); } + abstract class Bar extends BarBase {} '''), _containsAllOf('String toString({bool? a = false}) => super.toString()'), ); @@ -1060,7 +1078,7 @@ void main() { }); test( - 'imports libraries for external class types found in an inherited method' + 'imports libraries for external class types found in an inherited method ' 'via a generic instantiation', () async { await testWithNonNullable({ ...annotationsAsset, @@ -2285,6 +2303,21 @@ void main() { ); }); + test('imports libraries for types used in generated fake classes', () async { + await expectSingleNonNullableOutput( + dedent(''' + class Foo { + Bar m1() => Bar('name1'); + } + class Bar { + String toString({Baz? baz}) => ''; + } + class Baz {} + '''), + _containsAllOf('String toString({_i2.Baz? baz}) => super.toString();'), + ); + }); + test('deduplicates fake classes', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { From 9b7b4f34006ea098a80eb07d41d648a2ac06c472 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 30 Jul 2021 01:53:57 -0400 Subject: [PATCH 380/607] Properly override methods which have been overridden in the mixin hierarchy. Previously, mixins were being applied in the wrong order. By using analyzer's InheritanceManager3, instead of my homespun inheritance-walking algorithm, we solve this problem, and perhaps other unreported issues. Fixes https://github.com/dart-lang/mockito/issues/456 PiperOrigin-RevId: 387741287 --- pkgs/mockito/CHANGELOG.md | 7 ++ pkgs/mockito/lib/src/builder.dart | 74 +++++++------------ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 19 +++++ 5 files changed, 53 insertions(+), 51 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index fc7271885..ea6293af4 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.0.13 + +* Implement methods which have been overridden in the mixin hierarchy properly. + Previously, mixins were being applied in the wrong order, which could skip + over one method that overrides another with a different signature. + [#456](https://github.com/dart-lang/mockito/issues/456) + ## 5.0.12 * Use an empty list with a correct type argument for a fallback value for a diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index d25d9e140..cd2084805 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -20,6 +20,10 @@ import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/dart/element/type_system.dart'; import 'package:analyzer/dart/element/visitor.dart'; +import 'package:analyzer/src/dart/element/inheritance_manager3.dart' + show InheritanceManager3; +import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember; +import 'package:analyzer/src/dart/element/type_algebra.dart' show Substitution; import 'package:build/build.dart'; // Do not expose [refer] in the default namespace. // @@ -842,33 +846,40 @@ class _MockClassInfo { if (!sourceLibIsNonNullable) { return; } - cBuilder.methods.addAll(fieldOverrides(typeToMock, {})); - cBuilder.methods.addAll(methodOverrides(typeToMock, {})); + final inheritanceManager = InheritanceManager3(); + final substitution = Substitution.fromInterfaceType(typeToMock); + final members = + inheritanceManager.getInterface(classToMock).map.values.map((member) { + return ExecutableMember.from2(member, substitution); + }); + cBuilder.methods + .addAll(fieldOverrides(members.whereType())); + cBuilder.methods + .addAll(methodOverrides(members.whereType())); }); } /// Yields all of the field overrides required for [type]. /// - /// This includes fields of supertypes and mixed in types. [overriddenFields] - /// is used to track which fields have already been yielded. + /// This includes fields of supertypes and mixed in types. /// /// Only public instance fields which have either a potentially non-nullable /// return type (for getters) or a parameter with a potentially non-nullable /// type (for setters) are yielded. Iterable fieldOverrides( - analyzer.InterfaceType type, Set overriddenFields) sync* { - for (final accessor in type.accessors) { - if (accessor.isPrivate || accessor.isStatic) { + Iterable accessors) sync* { + for (final accessor in accessors) { + if (accessor.isPrivate) { continue; } - if (overriddenFields.contains(accessor.name)) { + if (accessor.name == 'hashCode') { + // Never override this getter; user code cannot narrow the return type. continue; } - if (accessor.name == 'hashCode') { + if (accessor.name == 'runtimeType') { // Never override this getter; user code cannot narrow the return type. continue; } - overriddenFields.add(accessor.name); if (accessor.isGetter && _returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } @@ -876,42 +887,21 @@ class _MockClassInfo { yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); } } - if (type.mixins != null) { - for (var mixin in type.mixins) { - yield* fieldOverrides(mixin, overriddenFields); - } - } - if (type.interfaces != null) { - for (var interface in type.interfaces) { - yield* fieldOverrides(interface, overriddenFields); - } - } - var superclass = type.superclass; - if (superclass != null && !superclass.isDartCoreObject) { - yield* fieldOverrides(superclass, overriddenFields); - } } /// Yields all of the method overrides required for [type]. /// /// This includes methods of supertypes and mixed in types. - /// [overriddenMethods] is used to track which methods have already been - /// yielded. /// /// Only public instance methods which have either a potentially non-nullable /// return type or a parameter with a potentially non-nullable type are /// yielded. - Iterable methodOverrides( - analyzer.InterfaceType type, Set overriddenMethods) sync* { - for (final method in type.methods) { - if (method.isPrivate || method.isStatic) { - continue; - } - var methodName = method.name; - if (overriddenMethods.contains(methodName)) { + Iterable methodOverrides(Iterable methods) sync* { + for (final method in methods) { + if (method.isPrivate) { continue; } - overriddenMethods.add(methodName); + final methodName = method.name; if (methodName == 'noSuchMethod') { continue; } @@ -926,20 +916,6 @@ class _MockClassInfo { yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } } - if (type.mixins != null) { - for (var mixin in type.mixins) { - yield* methodOverrides(mixin, overriddenMethods); - } - } - if (type.interfaces != null) { - for (var interface in type.interfaces) { - yield* methodOverrides(interface, overriddenMethods); - } - } - var superclass = type.superclass; - if (superclass != null && !superclass.isDartCoreObject) { - yield* methodOverrides(superclass, overriddenMethods); - } } /// The default behavior of mocks is to return null for unstubbed methods. To diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 7f8e03aa6..5e2f1b99b 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.12'; +const packageVersion = '5.0.13'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 76893587c..02d700b33 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.12 +version: 5.0.13 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 717ed235a..87698feb4 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -614,6 +614,25 @@ void main() { ); }); + test('overrides mixed in methods, using correct overriding signature', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Base { + void m(int a) {} + } + mixin MixinConstraint implements Base {} + mixin Mixin on MixinConstraint { + @override + void m(num a) {} + } + class Foo with MixinConstraint, Mixin {} + '''), + _containsAllOf( + 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + test('overrides methods of implemented classes', () async { await expectSingleNonNullableOutput( dedent(r''' From 5195e49b3f5b33448da754a27c02c41bcb7d98b8 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 5 Aug 2021 13:00:28 -0400 Subject: [PATCH 381/607] Ensure that generated Fake classes have unique names. Without this fix, the generator could generate multiple classes with the same name. Fixes dart-lang/mockito#441 PiperOrigin-RevId: 388959500 --- pkgs/mockito/CHANGELOG.md | 5 ++ pkgs/mockito/lib/src/builder.dart | 13 +++-- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 51 ++++++++++++++----- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ea6293af4..8f7b8a615 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.0.14 + +* Generate Fake classes with unique names. + [#441](https://github.com/dart-lang/mockito/issues/441) + ## 5.0.13 * Implement methods which have been overridden in the mixin hierarchy properly. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index cd2084805..2a6989d20 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -757,6 +757,14 @@ class _MockLibraryInfo { )._buildMockClass()); } } + + var _fakeNameCounter = 0; + + final _fakeNames = {}; + + /// Generates a unique name for a fake class representing [element]. + String _fakeNameFor(Element element) => _fakeNames.putIfAbsent( + element, () => '_Fake${element.name}_${_fakeNameCounter++}'); } class _MockClassInfo { @@ -1154,10 +1162,7 @@ class _MockClassInfo { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); } else { - // There is a potential for these names to collide. If one mock class - // requires a fake for a certain Foo, and another mock class requires a - // fake for a different Foo, they will collide. - final fakeName = '_Fake${elementToFake.name}'; + final fakeName = mockLibraryInfo._fakeNameFor(elementToFake); // Only make one fake class for each class that needs to be faked. if (!mockLibraryInfo.fakedClassElements.contains(elementToFake)) { _addFakeClass(fakeName, elementToFake); diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 5e2f1b99b..124c794e0 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.13'; +const packageVersion = '5.0.14'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 02d700b33..12bdb3285 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.13 +version: 5.0.14 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 87698feb4..4ec4f9698 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2057,7 +2057,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i2.Bar m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: _FakeBar()) + (super.noSuchMethod(Invocation.method(#m, []), returnValue: _FakeBar_0()) as _i2.Bar); ''')), ); @@ -2073,7 +2073,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _FakeBar()) as _i2.Bar); + returnValue: _FakeBar_0()) as _i2.Bar); ''')), ); }); @@ -2144,7 +2144,7 @@ void main() { '''), _containsAllOf( '_i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []),\n' - ' returnValue: () => _FakeFoo()) as _i2.Foo Function());'), + ' returnValue: () => _FakeFoo_0()) as _i2.Foo Function());'), ); }); @@ -2160,7 +2160,7 @@ void main() { '''), _containsAllOf( '_i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []),\n' - ' returnValue: () => _FakeFoo()) as _i2.Foo Function());'), + ' returnValue: () => _FakeFoo_0()) as _i2.Foo Function());'), ); }); @@ -2226,7 +2226,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i2.File Function() m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: () => _FakeFile()) as _i2.File Function()); + returnValue: () => _FakeFile_0()) as _i2.File Function()); ''')), ); }); @@ -2239,10 +2239,35 @@ void main() { } class Bar {} '''), - _containsAllOf('class _FakeBar extends _i1.Fake implements _i2.Bar {}'), + _containsAllOf('class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}'), ); }); + test('generates fake classes with unique names', () async { + final mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'bar1.dart' as one; + import 'bar2.dart' as two; + abstract class Foo { + one.Bar m1(); + two.Bar m2(); + } + ''', + 'foo|lib/bar1.dart': ''' + class Bar {} + ''', + 'foo|lib/bar2.dart': ''' + class Bar {} + ''', + }); + expect(mocksOutput, + contains('class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}')); + expect(mocksOutput, + contains('class _FakeBar_1 extends _i1.Fake implements _i3.Bar {}')); + }); + test('generates a fake generic class used in return values', () async { await expectSingleNonNullableOutput( dedent(r''' @@ -2252,7 +2277,7 @@ void main() { class Bar {} '''), _containsAllOf( - 'class _FakeBar extends _i1.Fake implements _i2.Bar {}'), + 'class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}'), ); }); @@ -2267,7 +2292,7 @@ void main() { } '''), _containsAllOf( - 'class _FakeBar extends _i2.Fake implements _i1.Bar {}'), + 'class _FakeBar_0 extends _i2.Fake implements _i1.Bar {}'), ); }); @@ -2282,7 +2307,7 @@ void main() { } '''), _containsAllOf( - 'class _FakeBar extends _i2.Fake implements _i1.Bar {}'), + 'class _FakeBar_0 extends _i2.Fake implements _i1.Bar {}'), ); }); @@ -2298,7 +2323,7 @@ void main() { } '''), _containsAllOf( - 'class _FakeBar extends _i1.Fake implements _i2.Bar {}'), + 'class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}'), ); }); @@ -2314,7 +2339,7 @@ void main() { } '''), _containsAllOf(dedent(''' - class _FakeBar extends _i1.Fake implements _i2.Bar { + class _FakeBar_0 extends _i1.Fake implements _i2.Bar { @override String toString({bool? a = true}) => super.toString(); } @@ -2349,8 +2374,8 @@ void main() { } ''')); var mocksContentLines = mocksContent.split('\n'); - // The _FakeBar class should be generated exactly once. - expect(mocksContentLines.where((line) => line.contains('class _FakeBar')), + // The _FakeBar_0 class should be generated exactly once. + expect(mocksContentLines.where((line) => line.contains('class _FakeBar_0')), hasLength(1)); }); From 70ecfa8a8c48e5aa8c0e0cded359fe6144cdceb3 Mon Sep 17 00:00:00 2001 From: Peter Leibiger Date: Tue, 10 Aug 2021 05:13:44 +0200 Subject: [PATCH 382/607] Fix wrong import for method parameters with default value factories (dart-lang/mockito#460) --- pkgs/mockito/.gitignore | 4 ++++ pkgs/mockito/lib/src/builder.dart | 16 +++++++++++----- pkgs/mockito/test/end2end/foo.dart | 11 +++++++++-- pkgs/mockito/test/end2end/foo_sub.dart | 9 +++++++++ .../test/end2end/generated_mocks_test.dart | 19 +++++++++++++++++++ 5 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 pkgs/mockito/test/end2end/foo_sub.dart diff --git a/pkgs/mockito/.gitignore b/pkgs/mockito/.gitignore index 7621c584a..b3bfe9921 100644 --- a/pkgs/mockito/.gitignore +++ b/pkgs/mockito/.gitignore @@ -5,5 +5,9 @@ .packages .pub pubspec.lock +build/ coverage/ + +# ignore generated mocks +**/*.mocks.dart \ No newline at end of file diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 2a6989d20..b950046b2 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1233,8 +1233,9 @@ class _MockClassInfo { if (parameter.isNamed) pBuilder.named = true; if (parameter.defaultValueCode != null) { try { - pBuilder.defaultTo = - _expressionFromDartObject(parameter.computeConstantValue()!).code; + pBuilder.defaultTo = _expressionFromDartObject( + parameter.computeConstantValue()!, parameter) + .code; } on _ReviveException catch (e) { final method = parameter.enclosingElement!; final clazz = method.enclosingElement!; @@ -1248,11 +1249,13 @@ class _MockClassInfo { } /// Creates a code_builder [Expression] from [object], a constant object from - /// analyzer. + /// analyzer and [parameter], an optional [ParameterElement], when the + /// expression is created for a method parameter. /// /// This is very similar to Angular's revive code, in /// angular_compiler/analyzer/di/injector.dart. - Expression _expressionFromDartObject(DartObject object) { + Expression _expressionFromDartObject(DartObject object, + [ParameterElement? parameter]) { final constant = ConstantReader(object); if (constant.isNull) { return literalNull; @@ -1318,7 +1321,10 @@ class _MockClassInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final type = referImported(name, _typeImport(object.type!.element)); + final element = parameter != null && name != object.type!.element!.name + ? parameter.type.element + : object.type!.element; + final type = referImported(name, _typeImport(element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( revivable.accessor, diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index fa6e6a4de..1ba22ff0f 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -1,9 +1,18 @@ +import 'foo_sub.dart'; + class Foo { + const Foo(); + + const factory Foo.sub() = FooSub2; + String positionalParameter(int x) => 'Real'; String namedParameter({required int x}) => 'Real'; String get getter => 'Real'; int operator +(int arg) => arg + 1; String parameterWithDefault([int x = 0]) => 'Real'; + String parameterWithDefault2([Foo x = const FooSub()]) => 'Real'; + String parameterWithDefaultFactoryRedirect([Foo x = const Foo.sub()]) => + 'Real'; String? nullableMethod(int x) => 'Real'; String? get nullableGetter => 'Real'; String methodWithBarArg(Bar bar) => 'result'; @@ -13,8 +22,6 @@ class Foo { Future? returnsNullableFutureVoid() => Future.value(); } -class FooSub extends Foo {} - class Bar {} abstract class Baz { diff --git a/pkgs/mockito/test/end2end/foo_sub.dart b/pkgs/mockito/test/end2end/foo_sub.dart new file mode 100644 index 000000000..9ded55841 --- /dev/null +++ b/pkgs/mockito/test/end2end/foo_sub.dart @@ -0,0 +1,9 @@ +import 'foo.dart'; + +class FooSub extends Foo { + const FooSub(); +} + +class FooSub2 extends Foo { + const FooSub2(); +} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 39e45b5bb..cb2f1b1fc 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -3,6 +3,7 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'foo.dart'; +import 'foo_sub.dart'; import 'generated_mocks_test.mocks.dart'; T dummyMethod() => [1, 1.5].whereType().first!; @@ -66,6 +67,24 @@ void main() { when(foo.parameterWithDefault()).thenReturn('Default'); expect(foo.parameterWithDefault(), equals('Default')); + + const foo2 = FooSub(); + when(foo.parameterWithDefault2(foo2)).thenReturn('Stubbed'); + expect(foo.parameterWithDefault2(foo2), equals('Stubbed')); + + when(foo.parameterWithDefault2()).thenReturn('Default'); + expect(foo.parameterWithDefault2(), equals('Default')); + }); + + test( + 'a method with a parameter with a default value factory redirect can be stubbed', + () { + const foo2 = FooSub2(); + when(foo.parameterWithDefaultFactoryRedirect(foo2)).thenReturn('Stubbed'); + expect(foo.parameterWithDefaultFactoryRedirect(foo2), equals('Stubbed')); + + when(foo.parameterWithDefaultFactoryRedirect()).thenReturn('Default'); + expect(foo.parameterWithDefaultFactoryRedirect(), equals('Default')); }); test('an inherited method can be stubbed', () { From d949d359f3643268c59ef90b954fd7708d34a36d Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 10 Aug 2021 19:24:44 -0400 Subject: [PATCH 383/607] Default value w/ redirecting constructor fix followups: * comment fix, long string fix * bump version and add to CHANGELOG PiperOrigin-RevId: 390005036 --- pkgs/mockito/CHANGELOG.md | 6 ++++++ pkgs/mockito/lib/src/builder.dart | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/end2end/generated_mocks_test.dart | 4 ++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 8f7b8a615..a515908dd 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.15-dev + +* Fix an issue generating the correct parameter default value given a + constructor which redirects to a constructor declared in a separate library. + [#459](https://github.com/dart-lang/mockito/issues/459) + ## 5.0.14 * Generate Fake classes with unique names. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b950046b2..f1ec81260 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1250,7 +1250,7 @@ class _MockClassInfo { /// Creates a code_builder [Expression] from [object], a constant object from /// analyzer and [parameter], an optional [ParameterElement], when the - /// expression is created for a method parameter. + /// expression is created for a method parameter default value. /// /// This is very similar to Angular's revive code, in /// angular_compiler/analyzer/di/injector.dart. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 124c794e0..841bdf7e0 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.14'; +const packageVersion = '5.0.15-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 12bdb3285..8262553e8 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.14 +version: 5.0.15-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index cb2f1b1fc..6aa4f44ef 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -77,8 +77,8 @@ void main() { }); test( - 'a method with a parameter with a default value factory redirect can be stubbed', - () { + 'a method with a parameter with a default value factory redirect can ' + 'be stubbed', () { const foo2 = FooSub2(); when(foo.parameterWithDefaultFactoryRedirect(foo2)).thenReturn('Stubbed'); expect(foo.parameterWithDefaultFactoryRedirect(foo2), equals('Stubbed')); From 3f06f87aa7a8545aee3d0457c46cad9a1f99a96b Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 20 Aug 2021 16:54:04 -0400 Subject: [PATCH 384/607] Bump to version 5.0.15. PiperOrigin-RevId: 392063226 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a515908dd..bd95ac6b6 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.15-dev +## 5.0.15 * Fix an issue generating the correct parameter default value given a constructor which redirects to a constructor declared in a separate library. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 841bdf7e0..cb009c75e 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.15-dev'; +const packageVersion = '5.0.15'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8262553e8..8ea9a5476 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.15-dev +version: 5.0.15 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, From 72f20f42c29ca19cc26f7fa5a5ee1ad0a935b099 Mon Sep 17 00:00:00 2001 From: Phil Quitslund Date: Mon, 23 Aug 2021 11:13:52 -0700 Subject: [PATCH 385/607] typo fix --- pkgs/mockito/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index 4a361b78b..53ca02195 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -10,7 +10,7 @@ they are. One idea to consider is "Do I need to use mocks to test this code?" For example, the [test_descriptor package] allows testing file system concepts using real files, and the [test_process package] supports testing subprocesses using -real subprocesses. `dart:io` also includes an [IOOVerides] class and a +real subprocesses. `dart:io` also includes an [IOOverrides] class and a [runWithIOOverrides] function that can be used to mock out the filesystem. [IOOverrides]: https://api.dart.dev/stable/2.7.2/dart-io/IOOverrides-class.html From f8aa89ba84582a23adb071259bd547917245fdf2 Mon Sep 17 00:00:00 2001 From: Chinky Sight <53231016+chinkysight@users.noreply.github.com> Date: Fri, 16 Jul 2021 08:57:18 +0545 Subject: [PATCH 386/607] Update README.md --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index fd8fce1f1..adcd31a40 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -107,7 +107,7 @@ The [`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a stubbing mechanism to override this behavior. Once stubbed, the method will always return stubbed value regardless of how many times it is called. If a method invocation matches multiple stubs, the one which was declared last will -be used. +be used. It is worth noting that stubbing only works on methods of a mocked class e.g `MockCat` in this case. ### A quick word on async stubbing From 58dcd289694a675f6d796a5abdc39a2b3b77e8cc Mon Sep 17 00:00:00 2001 From: Chinky Sight <53231016+chinkysight@users.noreply.github.com> Date: Sun, 29 Aug 2021 20:27:35 +0545 Subject: [PATCH 387/607] Update README.md --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index adcd31a40..8948def1a 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -107,7 +107,7 @@ The [`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a stubbing mechanism to override this behavior. Once stubbed, the method will always return stubbed value regardless of how many times it is called. If a method invocation matches multiple stubs, the one which was declared last will -be used. It is worth noting that stubbing only works on methods of a mocked class e.g `MockCat` in this case. +be used. It is worth noting that stubbing and verifying only works on methods of a mocked class e.g `MockCat` in this case. ### A quick word on async stubbing From 87a5c0eb9a19b6ede705dc3eedeb2c6a64486f48 Mon Sep 17 00:00:00 2001 From: Renato Quinhoneiro Todorov Date: Tue, 7 Sep 2021 21:19:33 -0300 Subject: [PATCH 388/607] Add ignore_for_file: camel_case_types to generated file Fixes dart-lang/mockito#463 --- pkgs/mockito/lib/src/builder.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index f1ec81260..b58b253fb 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -93,7 +93,9 @@ class MockBuilder implements Builder { '// ignore_for_file: invalid_use_of_visible_for_testing_member\n')); b.body.add(Code('// ignore_for_file: prefer_const_constructors\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. - b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n\n')); + b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n')); + // The generator appends a suffix to fake classes + b.body.add(Code('// ignore_for_file: camel_case_types\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); From fa24bb61fa256238f3a47bd5d42efd5f81d267db Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 2 Sep 2021 12:37:15 -0400 Subject: [PATCH 389/607] Fix pre-existing HintCode.UNNECESSARY_TYPE_CHECK_TRUE This prepares for landing https://dart-review.googlesource.com/c/sdk/+/190360 into the analyzer. PiperOrigin-RevId: 394480352 --- pkgs/mockito/FAQ.md | 2 +- pkgs/mockito/README.md | 2 +- pkgs/mockito/lib/src/mock.dart | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index 53ca02195..4a361b78b 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -10,7 +10,7 @@ they are. One idea to consider is "Do I need to use mocks to test this code?" For example, the [test_descriptor package] allows testing file system concepts using real files, and the [test_process package] supports testing subprocesses using -real subprocesses. `dart:io` also includes an [IOOverrides] class and a +real subprocesses. `dart:io` also includes an [IOOVerides] class and a [runWithIOOverrides] function that can be used to mock out the filesystem. [IOOverrides]: https://api.dart.dev/stable/2.7.2/dart-io/IOOverrides-class.html diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 8948def1a..fd8fce1f1 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -107,7 +107,7 @@ The [`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a stubbing mechanism to override this behavior. Once stubbed, the method will always return stubbed value regardless of how many times it is called. If a method invocation matches multiple stubs, the one which was declared last will -be used. It is worth noting that stubbing and verifying only works on methods of a mocked class e.g `MockCat` in this case. +be used. ### A quick word on async stubbing diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index f2db8112a..31b78a8fa 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -502,10 +502,8 @@ class InvocationMatcher { for (var roleKey in roleInvocation.namedArguments.keys) { var roleArg = roleInvocation.namedArguments[roleKey]; var actArg = invocation.namedArguments[roleKey]; - if (roleArg is ArgMatcher) { - if (roleArg is ArgMatcher && roleArg._capture) { - _capturedArgs.add(actArg); - } + if (roleArg is ArgMatcher && roleArg._capture) { + _capturedArgs.add(actArg); } } } From 32510060a9f9c50db24dc446a0c0885287990572 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Fri, 10 Sep 2021 12:30:02 -0400 Subject: [PATCH 390/607] Stop using deprecated DartType.aliasElement/aliasArguments We would like to remove them. Use `DartType.alias` instead. PiperOrigin-RevId: 395945062 --- pkgs/mockito/lib/src/builder.dart | 21 +++++++++------------ pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b58b253fb..96405fca7 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -157,10 +157,7 @@ $rawOutput for (final element in elements) { final elementLibrary = element.library!; - if (elementLibrary.isInSdk && - // TODO(srawlins): Remove this when analyzer dep is >=2.0.0. - // ignore: unnecessary_non_null_assertion - !elementLibrary.name!.startsWith('dart._')) { + if (elementLibrary.isInSdk && !elementLibrary.name.startsWith('dart._')) { // For public SDK libraries, just use the source URI. typeUris[element] = elementLibrary.source.uri.toString(); continue; @@ -293,7 +290,7 @@ class _TypeVisitor extends RecursiveElementVisitor { for (var parameter in type.parameters) { parameter.accept(this); } - var aliasElement = type.aliasElement; + var aliasElement = type.alias?.element; if (aliasElement != null) { _elements.add(aliasElement); } @@ -519,7 +516,7 @@ class _MockTargetGatherer { return typeToMock; } - var aliasElement = typeToMock.aliasElement; + var aliasElement = typeToMock.alias?.element; if (aliasElement != null) { throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: ' '${aliasElement.displayName}'); @@ -664,7 +661,7 @@ class _MockTargetGatherer { errorMessages .addAll(_checkTypeParameters(function.typeFormals, enclosingElement)); - var aliasArguments = function.aliasArguments; + var aliasArguments = function.alias?.typeArguments; if (aliasArguments != null) { errorMessages .addAll(_checkTypeArguments(aliasArguments, enclosingElement)); @@ -1430,8 +1427,8 @@ class _MockClassInfo { ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { - final element = type.aliasElement; - if (element == null || element.isPrivate) { + final alias = type.alias; + if (alias == null || alias.element.isPrivate) { // [type] does not refer to a type alias, or it refers to a private type // alias; we must instead write out its signature. return FunctionType((b) { @@ -1453,10 +1450,10 @@ class _MockClassInfo { } return TypeReference((b) { b - ..symbol = element.name - ..url = _typeImport(element) + ..symbol = alias.element.name + ..url = _typeImport(alias.element) ..isNullable = forceNullable || typeSystem.isNullable(type); - for (var typeArgument in type.aliasArguments!) { + for (var typeArgument in alias.typeArguments) { b.types.add(_typeReference(typeArgument)); } }); diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8ea9a5476..e6b918eb8 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=1.0.0 <3.0.0' + analyzer: '>=2.1.0 <3.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 From ca7b0134b1ca02b7552d2857e06b58d4637fcea2 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 10 Sep 2021 13:56:31 -0400 Subject: [PATCH 391/607] Import https://github.com/dart-lang/mockito/commit/72f20f42c29ca19cc26f7fa5a5ee1ad0a935b099 from GitHub. typo fix PiperOrigin-RevId: 395962959 --- pkgs/mockito/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index 4a361b78b..53ca02195 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -10,7 +10,7 @@ they are. One idea to consider is "Do I need to use mocks to test this code?" For example, the [test_descriptor package] allows testing file system concepts using real files, and the [test_process package] supports testing subprocesses using -real subprocesses. `dart:io` also includes an [IOOVerides] class and a +real subprocesses. `dart:io` also includes an [IOOverrides] class and a [runWithIOOverrides] function that can be used to mock out the filesystem. [IOOverrides]: https://api.dart.dev/stable/2.7.2/dart-io/IOOverrides-class.html From 9bf75d1e0f3563a1cc6361c92e67f7d1c2e174c7 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 10 Sep 2021 13:59:29 -0400 Subject: [PATCH 392/607] Import https://github.com/dart-lang/mockito/commit/58dcd289694a675f6d796a5abdc39a2b3b77e8cc from GitHub. Update README.md. Tweak new text. PiperOrigin-RevId: 395963615 --- pkgs/mockito/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index fd8fce1f1..5e9f17c2c 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -107,7 +107,9 @@ The [`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a stubbing mechanism to override this behavior. Once stubbed, the method will always return stubbed value regardless of how many times it is called. If a method invocation matches multiple stubs, the one which was declared last will -be used. +be used. It is worth noting that stubbing and verifying only works on methods of +a mocked class; in this case, an instance of `MockCat` must be used, not an +instance of `Cat`. ### A quick word on async stubbing From 6b6b4085e637de481200b58628c3962e1d498d1f Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 10 Sep 2021 15:40:15 -0400 Subject: [PATCH 393/607] Properly reference types in type arguments on a class-to-mock. Fixes https://github.com/dart-lang/mockito/issues/469 PiperOrigin-RevId: 395985920 --- pkgs/mockito/CHANGELOG.md | 6 ++++++ pkgs/mockito/lib/src/builder.dart | 9 ++------ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 21 +++++++++++++++++++ 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index bd95ac6b6..30881679e 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.16-dev + +* Fix type reference for nested, imported types found in type arguments on a + custom class-to-mock. + [#469](https://github.com/dart-lang/mockito/issues/469) + ## 5.0.15 * Fix an issue generating the correct parameter default value given a diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 96405fca7..ded82053d 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -93,9 +93,7 @@ class MockBuilder implements Builder { '// ignore_for_file: invalid_use_of_visible_for_testing_member\n')); b.body.add(Code('// ignore_for_file: prefer_const_constructors\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. - b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n')); - // The generator appends a suffix to fake classes - b.body.add(Code('// ignore_for_file: camel_case_types\n\n')); + b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); @@ -823,10 +821,7 @@ class _MockClassInfo { // implements the mock target with said type arguments. For example: // `class MockFoo extends Mock implements Foo {}` for (var typeArgument in typeToMock.typeArguments) { - typeArguments.add(referImported( - typeArgument.getDisplayString( - withNullability: sourceLibIsNonNullable), - _typeImport(typeArgument.element))); + typeArguments.add(_typeReference(typeArgument)); } } else if (classToMock.typeParameters != null) { // [typeToMock] is a simple reference to a generic type (for example: diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index cb009c75e..49bf991c2 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.15'; +const packageVersion = '5.0.16-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index e6b918eb8..8073a6f42 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.15 +version: 5.0.16-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 99e14f777..e1c3fb029 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -139,6 +139,27 @@ void main() { expect(mocksContent, contains('List get f =>')); }); + test('generates a generic mock class with deep type arguments', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks( + [], customMocks: [MockSpec>>(as: #MockFoo)]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock implements _i2.Foo>')); + }); + test('generates a generic mock class with type arguments', () async { var mocksContent = await buildWithNonNullable({ ...annotationsAsset, From 9c7e179b5f644721eba2a17023c0c0b3782beb37 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 10 Sep 2021 16:14:39 -0400 Subject: [PATCH 394/607] Import https://github.com/dart-lang/mockito/commit/87a5c0eb9a19b6ede705dc3eedeb2c6a64486f48 from GitHub Add ignore_for_file: camel_case_types to generated file PiperOrigin-RevId: 395993046 --- pkgs/mockito/lib/src/builder.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index ded82053d..cb72ef69f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -93,7 +93,9 @@ class MockBuilder implements Builder { '// ignore_for_file: invalid_use_of_visible_for_testing_member\n')); b.body.add(Code('// ignore_for_file: prefer_const_constructors\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. - b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n\n')); + b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n')); + // The generator appends a suffix to fake classes + b.body.add(Code('// ignore_for_file: camel_case_types\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); From c4c81443159e8d7f496e165941ed9d515195235b Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 13 Sep 2021 16:44:31 -0400 Subject: [PATCH 395/607] Bump to 5.0.16 with two fixes. PiperOrigin-RevId: 396433598 --- pkgs/mockito/CHANGELOG.md | 4 +++- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 30881679e..66a6e757b 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,10 @@ -## 5.0.16-dev +## 5.0.16 * Fix type reference for nested, imported types found in type arguments on a custom class-to-mock. [#469](https://github.com/dart-lang/mockito/issues/469) +* Bump minimum analyzer dependency to version 2.1.0. +* Ignore `camel_case_types` lint in generated code. ## 5.0.15 diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 49bf991c2..30075021e 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.16-dev'; +const packageVersion = '5.0.16'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 8073a6f42..1065f39db 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.16-dev +version: 5.0.16 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, From 838916348bd162e087957641fed6ee73b739e48c Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 1 Oct 2021 16:02:00 -0700 Subject: [PATCH 396/607] Change "dartfmt" to "dart format". (dart-lang/mockito#480) I'm not sure if this script is even used anymore since the root .travis.yml file is gone, but in case it is used, this fixes it to work with Dart 2.15, which won't contain "dartfmt". --- pkgs/mockito/tool/travis.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh index 9a4cb7251..7baa067fc 100755 --- a/pkgs/mockito/tool/travis.sh +++ b/pkgs/mockito/tool/travis.sh @@ -26,8 +26,8 @@ while (( "$#" )); do case $TASK in dartfmt) echo echo -e '\033[1mTASK: dartfmt\033[22m' - echo -e 'dartfmt -n --set-exit-if-changed .' - dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? + echo -e 'dart format -o none --set-exit-if-changed .' + dart format -o none --set-exit-if-changed . || EXIT_CODE=$? ;; dartanalyzer) echo echo -e '\033[1mTASK: dartanalyzer\033[22m' From f271814c277d75d366f9fb43e4f105ae38fe4c97 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 15 Sep 2021 14:42:11 -0400 Subject: [PATCH 397/607] Report when a class-to-be-mocked has an _inherited_ member with a private type. Without this change, mockito can generate code like: `implements _i3._InternalFinalCallback {}` which is clearly illegal. PiperOrigin-RevId: 396885483 --- pkgs/mockito/CHANGELOG.md | 6 + pkgs/mockito/lib/src/builder.dart | 104 ++++++++++++------ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 63 +++++++++++ 5 files changed, 140 insertions(+), 37 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 66a6e757b..240438c65 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.17-dev + +* Report when a class cannot be mocked because an inherited method or property + accessor requires a private type. + [#446](https://github.com/dart-lang/mockito/issues/446) + ## 5.0.16 * Fix type reference for nested, imported types found in type arguments on a diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index cb72ef69f..d739bb577 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -60,14 +60,18 @@ class MockBuilder implements Builder { if (entryLib == null) return; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); - final mockTargetGatherer = _MockTargetGatherer(entryLib); + final inheritanceManager = InheritanceManager3(); + final mockTargetGatherer = + _MockTargetGatherer(entryLib, inheritanceManager); var entryAssetId = await buildStep.resolver.assetIdForElement(entryLib); final assetUris = await _resolveAssetUris(buildStep.resolver, mockTargetGatherer._mockTargets, entryAssetId.path, entryLib); final mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets, - assetUris: assetUris, entryLib: entryLib); + assetUris: assetUris, + entryLib: entryLib, + inheritanceManager: inheritanceManager); if (mockLibraryInfo.fakeClasses.isEmpty && mockLibraryInfo.mockClasses.isEmpty) { @@ -365,14 +369,20 @@ class _MockTargetGatherer { final List<_MockTarget> _mockTargets; - _MockTargetGatherer._(this._entryLib, this._mockTargets) { + final InheritanceManager3 _inheritanceManager; + + _MockTargetGatherer._( + this._entryLib, this._mockTargets, this._inheritanceManager) { _checkClassesToMockAreValid(); } /// Searches the top-level elements of [entryLib] for `GenerateMocks` /// annotations and creates a [_MockTargetGatherer] with all of the classes /// identified as mocking targets. - factory _MockTargetGatherer(LibraryElement entryLib) { + factory _MockTargetGatherer( + LibraryElement entryLib, + InheritanceManager3 inheritanceManager, + ) { final mockTargets = <_MockTarget>{}; for (final element in entryLib.topLevelElements) { @@ -390,7 +400,8 @@ class _MockTargetGatherer { } } - return _MockTargetGatherer._(entryLib, mockTargets.toList()); + return _MockTargetGatherer._( + entryLib, mockTargets.toList(), inheritanceManager); } static Iterable<_MockTarget> _mockTargetsFromGenerateMocks( @@ -586,15 +597,29 @@ class _MockTargetGatherer { void _checkMethodsToStubAreValid(_MockTarget mockTarget) { final classElement = mockTarget.classElement; final className = classElement.name; - final unstubbableErrorMessages = classElement.methods + final substitution = Substitution.fromInterfaceType(mockTarget.classType); + final relevantMembers = _inheritanceManager + .getInterface(classElement) + .map + .values .where((m) => !m.isPrivate && !m.isStatic) - .expand((m) => _checkFunction(m.type, m, + .map((member) => ExecutableMember.from2(member, substitution)); + final unstubbableErrorMessages = relevantMembers.expand((member) { + if (_entryLib.typeSystem._returnTypeIsNonNullable(member) || + _entryLib.typeSystem._hasNonNullableParameter(member) || + _needsOverrideForVoidStub(member)) { + return _checkFunction(member.type, member, hasDummyGenerator: - mockTarget.fallbackGenerators.containsKey(m.name))) - .toList(); + mockTarget.fallbackGenerators.containsKey(member.name)); + } else { + // Mockito is not going to override this method, so the types do not + // need to be checked. + return []; + } + }).toList(); if (unstubbableErrorMessages.isNotEmpty) { - var joinedMessages = + final joinedMessages = unstubbableErrorMessages.map((m) => ' $m').join('\n'); throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid mock class which implements ' @@ -743,6 +768,7 @@ class _MockLibraryInfo { Iterable<_MockTarget> mockTargets, { required this.assetUris, required LibraryElement entryLib, + required InheritanceManager3 inheritanceManager, }) { for (final mockTarget in mockTargets) { final fallbackGenerators = mockTarget.fallbackGenerators; @@ -753,6 +779,7 @@ class _MockLibraryInfo { typeSystem: entryLib.typeSystem, mockLibraryInfo: this, fallbackGenerators: fallbackGenerators, + inheritanceManager: inheritanceManager, )._buildMockClass()); } } @@ -785,6 +812,8 @@ class _MockClassInfo { /// function elements. final Map fallbackGenerators; + final InheritanceManager3 inheritanceManager; + _MockClassInfo({ required this.mockTarget, required this.sourceLibIsNonNullable, @@ -792,6 +821,7 @@ class _MockClassInfo { required this.typeSystem, required this.mockLibraryInfo, required this.fallbackGenerators, + required this.inheritanceManager, }); Class _buildMockClass() { @@ -850,7 +880,6 @@ class _MockClassInfo { if (!sourceLibIsNonNullable) { return; } - final inheritanceManager = InheritanceManager3(); final substitution = Substitution.fromInterfaceType(typeToMock); final members = inheritanceManager.getInterface(classToMock).map.values.map((member) { @@ -884,7 +913,7 @@ class _MockClassInfo { // Never override this getter; user code cannot narrow the return type. continue; } - if (accessor.isGetter && _returnTypeIsNonNullable(accessor)) { + if (accessor.isGetter && typeSystem._returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } if (accessor.isSetter) { @@ -914,8 +943,8 @@ class _MockClassInfo { // narrow the return type. continue; } - if (_returnTypeIsNonNullable(method) || - _hasNonNullableParameter(method) || + if (typeSystem._returnTypeIsNonNullable(method) || + typeSystem._hasNonNullableParameter(method) || _needsOverrideForVoidStub(method)) { yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } @@ -930,26 +959,6 @@ class _MockClassInfo { referImported('throwOnMissingStub', 'package:mockito/mockito.dart') .call([refer('this').expression]).statement); - bool _returnTypeIsNonNullable(ExecutableElement method) => - typeSystem.isPotentiallyNonNullable(method.returnType); - - bool _needsOverrideForVoidStub(ExecutableElement method) => - method.returnType.isVoid || method.returnType.isFutureOfVoid; - - // Returns whether [method] has at least one parameter whose type is - // potentially non-nullable. - // - // A parameter whose type uses a type variable may be non-nullable on certain - // instances. For example: - // - // class C { - // void m(T a) {} - // } - // final c1 = C(); // m's parameter's type is nullable. - // final c2 = C(); // m's parameter's type is non-nullable. - bool _hasNonNullableParameter(ExecutableElement method) => - method.parameters.any((p) => typeSystem.isPotentiallyNonNullable(p.type)); - /// Build a method which overrides [method], with all non-nullable /// parameter types widened to be nullable. /// @@ -1013,7 +1022,7 @@ class _MockClassInfo { final namedArgs = { if (fallbackGenerator != null) 'returnValue': _fallbackGeneratorCode(method, fallbackGenerator) - else if (_returnTypeIsNonNullable(method)) + else if (typeSystem._returnTypeIsNonNullable(method)) 'returnValue': _dummyValue(method.returnType), if (returnValueForMissingStub != null) 'returnValueForMissingStub': returnValueForMissingStub, @@ -1528,6 +1537,9 @@ extension on Element { } else if (this is MethodElement) { var className = enclosingElement!.name; return "The method '$className.$name'"; + } else if (this is PropertyAccessorElement) { + var className = enclosingElement!.name; + return "The property accessor '$className.$name'"; } else { return 'unknown element'; } @@ -1593,3 +1605,25 @@ extension on analyzer.InterfaceType { return false; } } + +extension on TypeSystem { + bool _returnTypeIsNonNullable(ExecutableElement method) => + isPotentiallyNonNullable(method.returnType); + + // Returns whether [method] has at least one parameter whose type is + // potentially non-nullable. + // + // A parameter whose type uses a type variable may be non-nullable on certain + // instances. For example: + // + // class C { + // void m(T a) {} + // } + // final c1 = C(); // m's parameter's type is nullable. + // final c2 = C(); // m's parameter's type is non-nullable. + bool _hasNonNullableParameter(ExecutableElement method) => + method.parameters.any((p) => isPotentiallyNonNullable(p.type)); +} + +bool _needsOverrideForVoidStub(ExecutableElement method) => + method.returnType.isVoid || method.returnType.isFutureOfVoid; diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 30075021e..3bfa6f705 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.16'; +const packageVersion = '5.0.17-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1065f39db..c22e62d12 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.16 +version: 5.0.17-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 4ec4f9698..8c4619441 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2400,6 +2400,48 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a getter with a private ' + 'return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo with FooMixin {} + mixin FooMixin { + _Bar get f => _Bar(); + } + class _Bar {} + '''), + }, + message: contains( + "The property accessor 'FooMixin.f' features a private return type, " + 'and cannot be stubbed.'), + ); + }); + + test( + 'throws when GenerateMocks is given a class with a setter with a private ' + 'return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo with FooMixin {} + mixin FooMixin { + void set f(_Bar value) {} + } + class _Bar {} + '''), + }, + message: contains( + "The property accessor 'FooMixin.f=' features a private parameter " + "type, '_Bar', and cannot be stubbed."), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a ' 'private return type', () async { @@ -2420,6 +2462,27 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with an inherited method ' + 'with a private return type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo with FooMixin {} + mixin FooMixin { + _Bar m(int a); + } + class _Bar {} + '''), + }, + message: contains( + "The method 'FooMixin.m' features a private return type, and cannot " + 'be stubbed.'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a ' 'type alias return type which refers to private types', () async { From 71b714276a186549faffe9ae11982d5f5a1a22aa Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 29 Sep 2021 13:36:59 -0400 Subject: [PATCH 398/607] Do not needlessly implement `toString` unless additional parameters are needed. PiperOrigin-RevId: 399719555 --- pkgs/mockito/CHANGELOG.md | 3 +++ pkgs/mockito/lib/src/builder.dart | 5 +++++ pkgs/mockito/test/builder/auto_mocks_test.dart | 11 +++++++++++ 3 files changed, 19 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 240438c65..f385c7e50 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,9 @@ * Report when a class cannot be mocked because an inherited method or property accessor requires a private type. [#446](https://github.com/dart-lang/mockito/issues/446) +* Do not needlessly implement `toString` unless the class-to-mock implements + `toString` with additional parameters. + [#461](https://github.com/dart-lang/mockito/issues/461) ## 5.0.16 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index d739bb577..83e8dfa9f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -938,6 +938,11 @@ class _MockClassInfo { if (methodName == 'noSuchMethod') { continue; } + if (methodName == 'toString' && method.parameters.isEmpty) { + // Do not needlessly override this method with a simple call to + // `super.toString`, unless the class has added parameters. + continue; + } if (methodName == '==') { // Never override this operator; user code cannot add parameters or // narrow the return type. diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 8c4619441..8870ac710 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -719,6 +719,17 @@ void main() { ); }); + test( + 'does not override `toString` if the class does not override `toString` ' + 'with additional parameters', () async { + var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + abstract class Foo { + String toString() => 'Foo'; + } + ''')); + expect(mocksContent, isNot(contains('toString'))); + }); + test( 'overrides `toString` with a correct signature if a mixed in class ' 'overrides it, in a Fake', () async { From cfdeaaaca75bd51b60058cc481f9dd7204a06a21 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 20 Dec 2021 09:50:18 -0800 Subject: [PATCH 399/607] Support analyzer 3.0.0 --- pkgs/mockito/CHANGELOG.md | 3 ++- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f385c7e50..9db170fd7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.17-dev +## 5.0.17 * Report when a class cannot be mocked because an inherited method or property accessor requires a private type. @@ -6,6 +6,7 @@ * Do not needlessly implement `toString` unless the class-to-mock implements `toString` with additional parameters. [#461](https://github.com/dart-lang/mockito/issues/461) +* Support analyzer 3.x ## 5.0.16 diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 3bfa6f705..e142f1dcf 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.17-dev'; +const packageVersion = '5.0.17'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c22e62d12..035792cb6 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.17-dev +version: 5.0.17 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=2.1.0 <3.0.0' + analyzer: '>=2.1.0 <4.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 From c9aa82d02be0f7a5c84d9a77709550008122577a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Dec 2021 13:14:37 +0800 Subject: [PATCH 400/607] Add ignore all lints comment to generated code --- pkgs/mockito/lib/src/builder.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 83e8dfa9f..6b2cbeacf 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -81,9 +81,10 @@ class MockBuilder implements Builder { final mockLibrary = Library((b) { // These comments are added after import directives; leading newlines - // are necessary. - b.body.add( - Code('\n\n// ignore_for_file: avoid_redundant_argument_values\n')); + // are necessary. Individual rules are still ignored to preserve backwards + // compatibility with older versions of Dart. + b.body.add(Code('\n\n// // ignore_for_file: type=lint\n')); + b.body.add(Code('// ignore_for_file: avoid_redundant_argument_values\n')); // We might generate a setter without a corresponding getter. b.body.add(Code('// ignore_for_file: avoid_setters_without_getters\n')); // We don't properly prefix imported class names in doc comments. From 45149967fae38d25e6f119f116f066589439ebc6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Dec 2021 13:17:55 +0800 Subject: [PATCH 401/607] Fix typo --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 6b2cbeacf..602d3b712 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -83,7 +83,7 @@ class MockBuilder implements Builder { // These comments are added after import directives; leading newlines // are necessary. Individual rules are still ignored to preserve backwards // compatibility with older versions of Dart. - b.body.add(Code('\n\n// // ignore_for_file: type=lint\n')); + b.body.add(Code('\n\n// ignore_for_file: type=lint\n')); b.body.add(Code('// ignore_for_file: avoid_redundant_argument_values\n')); // We might generate a setter without a corresponding getter. b.body.add(Code('// ignore_for_file: avoid_setters_without_getters\n')); From 4282f7eeffa5140869c894adce61a5121c6030b7 Mon Sep 17 00:00:00 2001 From: Tijo Jose Date: Fri, 12 Nov 2021 00:43:21 -0800 Subject: [PATCH 402/607] Fix a typo --- pkgs/mockito/NULL_SAFETY_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index e427a70eb..77963a077 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -19,7 +19,7 @@ Mockito uses to stub methods. -Here is the standard way of defining a mock for the Foo class: +Here is the standard way of defining a mock for the HttpServer class: ```dart class MockHttpServer extends Mock implements HttpServer {} From 634a0e3f7ab9535c7e40945ae3e865b795d640ba Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 18 Jan 2022 14:45:58 -0800 Subject: [PATCH 403/607] Remove old Travis script --- pkgs/mockito/tool/travis.sh | 74 ------------------------------------- 1 file changed, 74 deletions(-) delete mode 100755 pkgs/mockito/tool/travis.sh diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh deleted file mode 100755 index 7baa067fc..000000000 --- a/pkgs/mockito/tool/travis.sh +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2016 Dart Mockito authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#!/bin/bash - -if [ "$#" == "0" ]; then - echo -e '\033[31mAt least one task argument must be provided!\033[0m' - exit 1 -fi - -EXIT_CODE=0 - -while (( "$#" )); do - TASK=$1 - case $TASK in - dartfmt) echo - echo -e '\033[1mTASK: dartfmt\033[22m' - echo -e 'dart format -o none --set-exit-if-changed .' - dart format -o none --set-exit-if-changed . || EXIT_CODE=$? - ;; - dartanalyzer) echo - echo -e '\033[1mTASK: dartanalyzer\033[22m' - echo -e 'dartanalyzer --fatal-warnings lib' - dartanalyzer --fatal-warnings lib || EXIT_CODE=$? - ;; - vm_test) echo - echo -e '\033[1mTASK: vm_test\033[22m' - echo -e 'pub run build_runner test -- -p vm' - pub run build_runner test -- -p vm || EXIT_CODE=$? - ;; - dartdevc_build) echo - echo -e '\033[1mTASK: build\033[22m' - echo -e 'pub run build_runner build --fail-on-severe' - pub run build_runner build --fail-on-severe || EXIT_CODE=$? - ;; - dartdevc_test) echo - echo -e '\033[1mTASK: dartdevc_test\033[22m' - echo -e 'pub run build_runner test -- -p chrome' - xvfb-run pub run build_runner test -- -p chrome || EXIT_CODE=$? - ;; - coverage) echo - echo -e '\033[1mTASK: coverage\033[22m' - if [ "$REPO_TOKEN" ]; then - echo -e 'pub run dart_coveralls report test/all.dart' - pub global activate dart_coveralls - pub global run dart_coveralls report \ - --token $REPO_TOKEN \ - --retry 2 \ - --exclude-test-files \ - test/all.dart - else - echo -e "\033[33mNo token for coveralls. Skipping.\033[0m" - fi - ;; - *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" - EXIT_CODE=1 - ;; - esac - - shift -done - -exit $EXIT_CODE From 27ac4ef371aab535d7677867b3b5d368b2be5049 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 1 Oct 2021 17:07:24 -0400 Subject: [PATCH 404/607] Remove dead null checks and improve doc references. These are checks that don't show up in the internal build system, but do show up with `dart analyze` and in an IDE, externally. PiperOrigin-RevId: 400287111 --- pkgs/mockito/CHANGELOG.md | 3 +- pkgs/mockito/NULL_SAFETY_README.md | 2 +- pkgs/mockito/lib/src/builder.dart | 50 ++++++---------- pkgs/mockito/lib/src/mock.dart | 5 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 +- pkgs/mockito/test/nnbd_support_test.dart | 2 - pkgs/mockito/tool/travis.sh | 74 ++++++++++++++++++++++++ 8 files changed, 97 insertions(+), 45 deletions(-) create mode 100755 pkgs/mockito/tool/travis.sh diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9db170fd7..f385c7e50 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.17 +## 5.0.17-dev * Report when a class cannot be mocked because an inherited method or property accessor requires a private type. @@ -6,7 +6,6 @@ * Do not needlessly implement `toString` unless the class-to-mock implements `toString` with additional parameters. [#461](https://github.com/dart-lang/mockito/issues/461) -* Support analyzer 3.x ## 5.0.16 diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 77963a077..e427a70eb 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -19,7 +19,7 @@ Mockito uses to stub methods. -Here is the standard way of defining a mock for the HttpServer class: +Here is the standard way of defining a mock for the Foo class: ```dart class MockHttpServer extends Mock implements HttpServer {} diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 602d3b712..f7b4069c7 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -57,7 +57,6 @@ class MockBuilder implements Builder { Future build(BuildStep buildStep) async { if (!await buildStep.resolver.isLibrary(buildStep.inputId)) return; final entryLib = await buildStep.inputLibrary; - if (entryLib == null) return; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); final inheritanceManager = InheritanceManager3(); @@ -81,10 +80,9 @@ class MockBuilder implements Builder { final mockLibrary = Library((b) { // These comments are added after import directives; leading newlines - // are necessary. Individual rules are still ignored to preserve backwards - // compatibility with older versions of Dart. - b.body.add(Code('\n\n// ignore_for_file: type=lint\n')); - b.body.add(Code('// ignore_for_file: avoid_redundant_argument_values\n')); + // are necessary. + b.body.add( + Code('\n\n// ignore_for_file: avoid_redundant_argument_values\n')); // We might generate a setter without a corresponding getter. b.body.add(Code('// ignore_for_file: avoid_setters_without_getters\n')); // We don't properly prefix imported class names in doc comments. @@ -287,10 +285,8 @@ class _TypeVisitor extends RecursiveElementVisitor { // [RecursiveElementVisitor] does not "step out of" the element model, // into types, while traversing, so we must explicitly traverse [type] // here, in order to visit contained elements. - if (type.typeFormals != null) { - for (var typeParameter in type.typeFormals) { - typeParameter.accept(this); - } + for (var typeParameter in type.typeFormals) { + typeParameter.accept(this); } for (var parameter in type.parameters) { parameter.accept(this); @@ -390,7 +386,6 @@ class _MockTargetGatherer { // TODO(srawlins): Re-think the idea of multiple @GenerateMocks // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { - if (annotation == null) continue; if (annotation.element is! ConstructorElement) continue; final annotationClass = annotation.element!.enclosingElement!.name; // TODO(srawlins): check library as well. @@ -586,8 +581,8 @@ class _MockTargetGatherer { }); } - /// Throws if any public instance methods of [classElement] are not valid - /// stubbing candidates. + /// Throws if any public instance methods of [mockTarget]'s class are not + /// valid stubbing candidates. /// /// A method is not valid for stubbing if: /// - It has a private type anywhere in its signature; Mockito cannot override @@ -856,7 +851,7 @@ class _MockClassInfo { for (var typeArgument in typeToMock.typeArguments) { typeArguments.add(_typeReference(typeArgument)); } - } else if (classToMock.typeParameters != null) { + } else { // [typeToMock] is a simple reference to a generic type (for example: // `Foo`, a reference to `class Foo {}`). Generate a generic mock // class which perfectly mirrors the type parameters on [typeToMock], @@ -893,7 +888,7 @@ class _MockClassInfo { }); } - /// Yields all of the field overrides required for [type]. + /// Yields all of the field overrides required for [accessors]. /// /// This includes fields of supertypes and mixed in types. /// @@ -923,7 +918,7 @@ class _MockClassInfo { } } - /// Yields all of the method overrides required for [type]. + /// Yields all of the method overrides required for [methods]. /// /// This includes methods of supertypes and mixed in types. /// @@ -976,11 +971,8 @@ class _MockClassInfo { builder ..name = name ..annotations.addAll([refer('override')]) - ..returns = _typeReference(method.returnType); - - if (method.typeParameters != null) { - builder.types.addAll(method.typeParameters.map(_typeParameterReference)); - } + ..returns = _typeReference(method.returnType) + ..types.addAll(method.typeParameters.map(_typeParameterReference)); // These two variables store the arguments that will be passed to the // [Invocation] built for `noSuchMethod`. @@ -1143,9 +1135,7 @@ class _MockClassInfo { // The positional parameters in a FunctionType have no names. This // counter lets us create unique dummy names. var counter = 0; - if (type.typeFormals != null) { - b.types.addAll(type.typeFormals.map(_typeParameterReference)); - } + b.types.addAll(type.typeFormals.map(_typeParameterReference)); for (final parameter in type.parameters) { if (parameter.isRequiredPositional) { b.requiredParameters @@ -1197,11 +1187,9 @@ class _MockClassInfo { cBuilder ..name = fakeName ..extend = referImported('Fake', 'package:mockito/mockito.dart'); - if (elementToFake.typeParameters != null) { - for (var typeParameter in elementToFake.typeParameters) { - cBuilder.types.add(_typeParameterReference(typeParameter)); - typeParameters.add(refer(typeParameter.name)); - } + for (var typeParameter in elementToFake.typeParameters) { + cBuilder.types.add(_typeParameterReference(typeParameter)); + typeParameters.add(refer(typeParameter.name)); } cBuilder.implements.add(TypeReference((b) { b @@ -1455,9 +1443,7 @@ class _MockClassInfo { for (var parameter in type.namedParameterTypes.entries) { b.namedParameters[parameter.key] = _typeReference(parameter.value); } - if (type.typeFormals != null) { - b.types.addAll(type.typeFormals.map(_typeParameterReference)); - } + b.types.addAll(type.typeFormals.map(_typeParameterReference)); }); } return TypeReference((b) { @@ -1580,8 +1566,6 @@ extension on analyzer.DartType { extension on analyzer.InterfaceType { bool get hasExplicitTypeArguments { - if (typeArguments == null) return false; - // If it appears that one type argument was given, then they all were. This // returns the wrong result when the type arguments given are all `dynamic`, // or are each equal to the bound of the corresponding type parameter. There diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 31b78a8fa..ca33cf791 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -835,9 +835,6 @@ class VerificationResult { /// /// Named arguments are listed in the order they are captured in, not the /// order in which they were passed. - // TODO(https://github.com/dart-lang/linter/issues/1992): Remove ignore - // comments below when google3 has linter with this bug fixed. - // ignore: unnecessary_getters_setters List get captured => _captured; @Deprecated( @@ -863,7 +860,7 @@ class VerificationResult { void _checkTestApiMismatch() { try { Invoker.current; - } on CastError catch (e) { + } on TypeError catch (e) { if (!e .toString() .contains("type 'Invoker' is not a subtype of type 'Invoker'")) { diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index e142f1dcf..3bfa6f705 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.17'; +const packageVersion = '5.0.17-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 035792cb6..c22e62d12 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.17 +version: 5.0.17-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=2.1.0 <4.0.0' + analyzer: '>=2.1.0 <3.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 diff --git a/pkgs/mockito/test/nnbd_support_test.dart b/pkgs/mockito/test/nnbd_support_test.dart index dc0724a45..9a83582e5 100644 --- a/pkgs/mockito/test/nnbd_support_test.dart +++ b/pkgs/mockito/test/nnbd_support_test.dart @@ -18,8 +18,6 @@ import 'package:test/test.dart'; class Foo { String? returnsNullableString() => 'Hello'; - // TODO(srawlins): When it becomes available, opt this test library into NNBD, - // and make this method really return a non-nullable String. String returnsNonNullableString() => 'Hello'; } diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh new file mode 100755 index 000000000..9a4cb7251 --- /dev/null +++ b/pkgs/mockito/tool/travis.sh @@ -0,0 +1,74 @@ +# Copyright 2016 Dart Mockito authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#!/bin/bash + +if [ "$#" == "0" ]; then + echo -e '\033[31mAt least one task argument must be provided!\033[0m' + exit 1 +fi + +EXIT_CODE=0 + +while (( "$#" )); do + TASK=$1 + case $TASK in + dartfmt) echo + echo -e '\033[1mTASK: dartfmt\033[22m' + echo -e 'dartfmt -n --set-exit-if-changed .' + dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? + ;; + dartanalyzer) echo + echo -e '\033[1mTASK: dartanalyzer\033[22m' + echo -e 'dartanalyzer --fatal-warnings lib' + dartanalyzer --fatal-warnings lib || EXIT_CODE=$? + ;; + vm_test) echo + echo -e '\033[1mTASK: vm_test\033[22m' + echo -e 'pub run build_runner test -- -p vm' + pub run build_runner test -- -p vm || EXIT_CODE=$? + ;; + dartdevc_build) echo + echo -e '\033[1mTASK: build\033[22m' + echo -e 'pub run build_runner build --fail-on-severe' + pub run build_runner build --fail-on-severe || EXIT_CODE=$? + ;; + dartdevc_test) echo + echo -e '\033[1mTASK: dartdevc_test\033[22m' + echo -e 'pub run build_runner test -- -p chrome' + xvfb-run pub run build_runner test -- -p chrome || EXIT_CODE=$? + ;; + coverage) echo + echo -e '\033[1mTASK: coverage\033[22m' + if [ "$REPO_TOKEN" ]; then + echo -e 'pub run dart_coveralls report test/all.dart' + pub global activate dart_coveralls + pub global run dart_coveralls report \ + --token $REPO_TOKEN \ + --retry 2 \ + --exclude-test-files \ + test/all.dart + else + echo -e "\033[33mNo token for coveralls. Skipping.\033[0m" + fi + ;; + *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" + EXIT_CODE=1 + ;; + esac + + shift +done + +exit $EXIT_CODE From 3975abd7dae69150c7c1fa61727c89d9857555a6 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 6 Jan 2022 12:08:00 -0500 Subject: [PATCH 405/607] Exclude tool/travis.sh from mockito's copybara. PiperOrigin-RevId: 420074914 --- pkgs/mockito/tool/travis.sh | 74 ------------------------------------- 1 file changed, 74 deletions(-) delete mode 100755 pkgs/mockito/tool/travis.sh diff --git a/pkgs/mockito/tool/travis.sh b/pkgs/mockito/tool/travis.sh deleted file mode 100755 index 9a4cb7251..000000000 --- a/pkgs/mockito/tool/travis.sh +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2016 Dart Mockito authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#!/bin/bash - -if [ "$#" == "0" ]; then - echo -e '\033[31mAt least one task argument must be provided!\033[0m' - exit 1 -fi - -EXIT_CODE=0 - -while (( "$#" )); do - TASK=$1 - case $TASK in - dartfmt) echo - echo -e '\033[1mTASK: dartfmt\033[22m' - echo -e 'dartfmt -n --set-exit-if-changed .' - dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? - ;; - dartanalyzer) echo - echo -e '\033[1mTASK: dartanalyzer\033[22m' - echo -e 'dartanalyzer --fatal-warnings lib' - dartanalyzer --fatal-warnings lib || EXIT_CODE=$? - ;; - vm_test) echo - echo -e '\033[1mTASK: vm_test\033[22m' - echo -e 'pub run build_runner test -- -p vm' - pub run build_runner test -- -p vm || EXIT_CODE=$? - ;; - dartdevc_build) echo - echo -e '\033[1mTASK: build\033[22m' - echo -e 'pub run build_runner build --fail-on-severe' - pub run build_runner build --fail-on-severe || EXIT_CODE=$? - ;; - dartdevc_test) echo - echo -e '\033[1mTASK: dartdevc_test\033[22m' - echo -e 'pub run build_runner test -- -p chrome' - xvfb-run pub run build_runner test -- -p chrome || EXIT_CODE=$? - ;; - coverage) echo - echo -e '\033[1mTASK: coverage\033[22m' - if [ "$REPO_TOKEN" ]; then - echo -e 'pub run dart_coveralls report test/all.dart' - pub global activate dart_coveralls - pub global run dart_coveralls report \ - --token $REPO_TOKEN \ - --retry 2 \ - --exclude-test-files \ - test/all.dart - else - echo -e "\033[33mNo token for coveralls. Skipping.\033[0m" - fi - ;; - *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" - EXIT_CODE=1 - ;; - esac - - shift -done - -exit $EXIT_CODE From 8f373b1d01d4e9110728db781899fd3abef9542c Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 6 Jan 2022 12:20:16 -0500 Subject: [PATCH 406/607] Import https://github.com/dart-lang/mockito/commit/cfdeaaaca75bd51b60058cc481f9dd7204a06a21 Support analyzer 3.0.0 PiperOrigin-RevId: 420077068 --- pkgs/mockito/CHANGELOG.md | 3 ++- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f385c7e50..9db170fd7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.17-dev +## 5.0.17 * Report when a class cannot be mocked because an inherited method or property accessor requires a private type. @@ -6,6 +6,7 @@ * Do not needlessly implement `toString` unless the class-to-mock implements `toString` with additional parameters. [#461](https://github.com/dart-lang/mockito/issues/461) +* Support analyzer 3.x ## 5.0.16 diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 3bfa6f705..e142f1dcf 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.17-dev'; +const packageVersion = '5.0.17'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c22e62d12..035792cb6 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.17-dev +version: 5.0.17 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=2.1.0 <3.0.0' + analyzer: '>=2.1.0 <4.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 From efec10d1ed5b6f5c0873d0672cdbbbb3d6602a21 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 6 Jan 2022 12:53:14 -0500 Subject: [PATCH 407/607] Import https://github.com/dart-lang/mockito/commit/4282f7eeffa5140869c894adce61a5121c6030b7 Fix a typo PiperOrigin-RevId: 420083841 --- pkgs/mockito/NULL_SAFETY_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index e427a70eb..77963a077 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -19,7 +19,7 @@ Mockito uses to stub methods. -Here is the standard way of defining a mock for the Foo class: +Here is the standard way of defining a mock for the HttpServer class: ```dart class MockHttpServer extends Mock implements HttpServer {} From 45e23422a085c99d6185859301ca3d3de03d9152 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 6 Jan 2022 13:20:15 -0500 Subject: [PATCH 408/607] Import https://github.com/dart-lang/mockito/commit/c9aa82d02be0f7a5c84d9a77709550008122577a Add ignore all lints comment to generated code PiperOrigin-RevId: 420090624 --- pkgs/mockito/lib/src/builder.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index f7b4069c7..b8a7f8beb 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -80,9 +80,10 @@ class MockBuilder implements Builder { final mockLibrary = Library((b) { // These comments are added after import directives; leading newlines - // are necessary. - b.body.add( - Code('\n\n// ignore_for_file: avoid_redundant_argument_values\n')); + // are necessary. Individual rules are still ignored to preserve backwards + // compatibility with older versions of Dart. + b.body.add(Code('\n\n// ignore_for_file: type=lint\n')); + b.body.add(Code('// ignore_for_file: avoid_redundant_argument_values\n')); // We might generate a setter without a corresponding getter. b.body.add(Code('// ignore_for_file: avoid_setters_without_getters\n')); // We don't properly prefix imported class names in doc comments. From eae2e0ff94cdaa904efee1cb3e5457edb9025628 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 6 Jan 2022 18:19:39 -0500 Subject: [PATCH 409/607] Remove check for test/test_api incompatibility. This check is long unnecessary, as the test/test_api/test_core split occurred long ago, and before the null safety migration. PiperOrigin-RevId: 420154856 --- pkgs/mockito/lib/src/mock.dart | 40 ---------------------------------- 1 file changed, 40 deletions(-) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index ca33cf791..130f8513d 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -21,10 +21,6 @@ import 'package:mockito/src/invocation_matcher.dart'; import 'package:test_api/fake.dart'; // ignore: deprecated_member_use import 'package:test_api/test_api.dart'; -// TODO(srawlins): Remove this when we no longer need to check for an -// incompatiblity between test_api and test. -// https://github.com/dart-lang/mockito/issues/175 -import 'package:test_api/src/backend/invoker.dart'; /// Whether a [when] call is "in progress." /// @@ -850,41 +846,6 @@ class VerificationResult { VerificationResult._(this.callCount, this._captured); - /// Check for a version incompatibility between mockito, test, and test_api. - /// - /// This incompatibility results in an inscrutible error for users. Catching - /// it here allows us to give some steps to fix. - // TODO(srawlins): Remove this when we don't need to check for an - // incompatiblity between test_api and test any more. - // https://github.com/dart-lang/mockito/issues/175 - void _checkTestApiMismatch() { - try { - Invoker.current; - } on TypeError catch (e) { - if (!e - .toString() - .contains("type 'Invoker' is not a subtype of type 'Invoker'")) { - // Hmm. This is a different CastError from the one we're trying to - // protect against. Let it go. - return; - } - print('Error: Package incompatibility between mockito, test, and ' - 'test_api packages:'); - print(''); - print('* mockito ^4.0.0 is incompatible with test <1.4.0'); - print('* mockito <4.0.0 is incompatible with test ^1.4.0'); - print(''); - print('As mockito does not have a dependency on the test package, ' - 'nothing stopped you from landing in this situation. :( ' - 'Apologies.'); - print(''); - print('To fix: bump your dependency on the test package to something ' - 'like: ^1.4.0, or downgrade your dependency on mockito to something ' - 'like: ^3.0.0'); - rethrow; - } - } - /// Assert that the number of calls matches [matcher]. /// /// Examples: @@ -899,7 +860,6 @@ class VerificationResult { // Only execute the check below once. `Invoker.current` may look like a // cheap getter, but it involves Zones and casting. _testApiMismatchHasBeenChecked = true; - _checkTestApiMismatch(); } expect(callCount, wrapMatcher(matcher), reason: 'Unexpected number of calls'); From 29b227974d3afcbd12e3933cc7646816e634de67 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 17 Jan 2022 22:03:45 -0500 Subject: [PATCH 410/607] Comply with package:lints lint rules Changes are to comply with: * implementation_imports * prefer_function_declarations_over_variables * prefer_void_to_null PiperOrigin-RevId: 422445330 --- pkgs/mockito/lib/src/builder.dart | 3 +++ pkgs/mockito/lib/src/mock.dart | 5 +++++ pkgs/mockito/test/mockito_test.dart | 5 +++-- pkgs/mockito/test/until_called_test.dart | 2 +- pkgs/mockito/test/verify_test.dart | 3 ++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b8a7f8beb..51dddc115 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -20,9 +20,12 @@ import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/dart/element/type_system.dart'; import 'package:analyzer/dart/element/visitor.dart'; +// ignore: implementation_imports import 'package:analyzer/src/dart/element/inheritance_manager3.dart' show InheritanceManager3; +// ignore: implementation_imports import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember; +// ignore: implementation_imports import 'package:analyzer/src/dart/element/type_algebra.dart' show Substitution; import 'package:build/build.dart'; // Do not expose [refer] in the default namespace. diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 130f8513d..1f97b0504 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// The API in this file includes functions that return `void`, but are intended +// to be passed as arguments to method stubs, so they must be declared to return +// `Null` in order to not trigger `use_of_void_result` warnings in user code. +// ignore_for_file: prefer_void_to_null + import 'dart:async'; import 'package:meta/meta.dart'; diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index fc985b558..0f7f1dd7f 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -215,11 +215,12 @@ void main() { // Error path tests. test('should throw if `when` is called while stubbing', () { expect(() { - var responseHelper = () { + _MockedClass responseHelper() { var mock2 = _MockedClass(); when(mock2.getter).thenReturn('A'); return mock2; - }; + } + when(mock.innerObj).thenReturn(responseHelper()); }, throwsStateError); }); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index c35a65fce..0adca2146 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -48,7 +48,7 @@ class _RealClassController { streamController.stream.listen(_callAllMethods); } - Future _callAllMethods(_) async { + Future _callAllMethods(_) async { _realClass ..methodWithoutArgs() ..methodWithNormalArgs(1) diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index 6358ebaef..e095611e3 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -184,7 +184,8 @@ void main() { }); test('should throw meaningful errors when verification is interrupted', () { - var badHelper = () => throw 'boo'; + int badHelper() => throw 'boo'; + try { verify(mock.methodWithNamedArgs(42, y: badHelper())); } catch (_) {} From c0f3722c14d296cc1e6ff518a9c07f54ea815108 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 20 Jan 2022 13:16:40 -0500 Subject: [PATCH 411/607] In creating mocks for a pre-null-safe library, opt out generated code. This is required for internal strict migration ordering, and would also fix generating mocks in a partially migrated package. PiperOrigin-RevId: 423098944 --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/lib/src/builder.dart | 6 ++++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/builder/auto_mocks_test.dart | 16 ++++++++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9db170fd7..aef79c9ac 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.0.18-dev + +* In creating mocks for a pre-null-safe library, opt out of null safety in the + generated code. + ## 5.0.17 * Report when a class cannot be mocked because an inherited method or property diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 51dddc115..7275e24f6 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -110,11 +110,17 @@ class MockBuilder implements Builder { final emitter = DartEmitter.scoped( orderDirectives: true, useNullSafetySyntax: sourceLibIsNonNullable); final rawOutput = mockLibrary.accept(emitter).toString(); + // The source lib may be pre-null-safety because of an explicit opt-out + // (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To + // allow for this situation, we must also add an opt-out comment here. + final dartVersionComment = sourceLibIsNonNullable ? '' : '// @dart=2.9'; final mockLibraryContent = DartFormatter().format(''' // Mocks generated by Mockito $packageVersion from annotations // in ${entryLib.definingCompilationUnit.source.uri.path}. // Do not manually edit this file. +$dartVersionComment + $rawOutput '''); diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index e142f1dcf..dd2b00b9a 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.17'; +const packageVersion = '5.0.18-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 035792cb6..31fcc8a79 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.17 +version: 5.0.18-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 8870ac710..238c300dc 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2938,6 +2938,22 @@ void main() { ); }); + test('given a pre-non-nullable library, includes an opt-out comment', + () async { + await testPreNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + int f(int a); + } + '''), + }, + outputs: {'foo|test/foo_test.mocks.dart': _containsAllOf('// @dart=2.9')}, + ); + }); + test('given a pre-non-nullable library, does not override any members', () async { await testPreNonNullable( From 0309843632a5df25004bd8183a71681c2224acf8 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 21 Jan 2022 17:40:57 -0500 Subject: [PATCH 412/607] Properly override methods with covariant parameters. Fixes https://github.com/dart-lang/mockito/issues/506 A covariant parameter may tighten its type. In this case, mockito must consider the parameter types in overridden methods and choose the LUB as the type of the parameter it is implementing. PiperOrigin-RevId: 423409713 --- pkgs/mockito/CHANGELOG.md | 2 + pkgs/mockito/lib/src/builder.dart | 119 ++++++++++++++--- .../mockito/test/builder/auto_mocks_test.dart | 121 ++++++++++++++++++ 3 files changed, 224 insertions(+), 18 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index aef79c9ac..1b236b57f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,8 @@ * In creating mocks for a pre-null-safe library, opt out of null safety in the generated code. +* Properly generate method overrides for methods with covariant parameters. + [#506](https://github.com/dart-lang/mockito/issues/506) ## 5.0.17 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7275e24f6..0aef11696 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -22,7 +22,7 @@ import 'package:analyzer/dart/element/type_system.dart'; import 'package:analyzer/dart/element/visitor.dart'; // ignore: implementation_imports import 'package:analyzer/src/dart/element/inheritance_manager3.dart' - show InheritanceManager3; + show InheritanceManager3, Name; // ignore: implementation_imports import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember; // ignore: implementation_imports @@ -989,20 +989,34 @@ class _MockClassInfo { final invocationPositionalArgs = []; final invocationNamedArgs = {}; + var position = 0; for (final parameter in method.parameters) { if (parameter.isRequiredPositional) { - builder.requiredParameters - .add(_matchingParameter(parameter, forceNullable: true)); + final superParameterType = + _escapeCovariance(parameter, position: position); + final matchingParameter = _matchingParameter(parameter, + superParameterType: superParameterType, forceNullable: true); + builder.requiredParameters.add(matchingParameter); invocationPositionalArgs.add(refer(parameter.displayName)); + position++; } else if (parameter.isOptionalPositional) { - builder.optionalParameters - .add(_matchingParameter(parameter, forceNullable: true)); + final superParameterType = + _escapeCovariance(parameter, position: position); + final matchingParameter = _matchingParameter(parameter, + superParameterType: superParameterType, forceNullable: true); + builder.optionalParameters.add(matchingParameter); invocationPositionalArgs.add(refer(parameter.displayName)); + position++; } else if (parameter.isNamed) { - builder.optionalParameters - .add(_matchingParameter(parameter, forceNullable: true)); + final superParameterType = _escapeCovariance(parameter, isNamed: true); + final matchingParameter = _matchingParameter(parameter, + superParameterType: superParameterType, forceNullable: true); + builder.optionalParameters.add(matchingParameter); invocationNamedArgs[refer('#${parameter.displayName}')] = refer(parameter.displayName); + } else { + throw StateError('Parameter ${parameter.name} on method ${method.name} ' + 'is not required-positional, nor optional-positional, nor named'); } } @@ -1144,19 +1158,23 @@ class _MockClassInfo { return Method((b) { // The positional parameters in a FunctionType have no names. This // counter lets us create unique dummy names. - var counter = 0; + var position = 0; b.types.addAll(type.typeFormals.map(_typeParameterReference)); for (final parameter in type.parameters) { if (parameter.isRequiredPositional) { - b.requiredParameters - .add(_matchingParameter(parameter, defaultName: '__p$counter')); - counter++; + final matchingParameter = _matchingParameter(parameter, + superParameterType: parameter.type, defaultName: '__p$position'); + b.requiredParameters.add(matchingParameter); + position++; } else if (parameter.isOptionalPositional) { - b.optionalParameters - .add(_matchingParameter(parameter, defaultName: '__p$counter')); - counter++; + final matchingParameter = _matchingParameter(parameter, + superParameterType: parameter.type, defaultName: '__p$position'); + b.optionalParameters.add(matchingParameter); + position++; } else if (parameter.isNamed) { - b.optionalParameters.add(_matchingParameter(parameter)); + final matchingParameter = + _matchingParameter(parameter, superParameterType: parameter.type); + b.optionalParameters.add(matchingParameter); } } if (type.returnType.isVoid) { @@ -1228,17 +1246,20 @@ class _MockClassInfo { /// If the type needs to be nullable, rather than matching the nullability of /// [parameter], use [forceNullable]. Parameter _matchingParameter(ParameterElement parameter, - {String? defaultName, bool forceNullable = false}) { + {required analyzer.DartType superParameterType, + String? defaultName, + bool forceNullable = false}) { assert( parameter.name.isNotEmpty || defaultName != null, 'parameter must have a non-empty name, or non-null defaultName must be ' 'passed, but parameter name is "${parameter.name}" and defaultName is ' '$defaultName'); - var name = parameter.name.isEmpty ? defaultName! : parameter.name; + final name = parameter.name.isEmpty ? defaultName! : parameter.name; return Parameter((pBuilder) { pBuilder ..name = name - ..type = _typeReference(parameter.type, forceNullable: forceNullable); + ..type = + _typeReference(superParameterType, forceNullable: forceNullable); if (parameter.isNamed) pBuilder.named = true; if (parameter.defaultValueCode != null) { try { @@ -1257,6 +1278,68 @@ class _MockClassInfo { }); } + /// Determines the most narrow legal type for a parameter which overrides + /// [parameter]. + /// + /// Without covariant parameters, each parameter in a method which overrides + /// a super-method with a corresponding super-parameter must be the same type + /// or a supertype of the super-parameter's type, so a generated overriding + /// method can use the same type as the type of the corresponding parameter. + /// + /// However, if a parameter is covariant, the supertype relationship is no + /// longer guaranteed, and we must look around at the types of of the + /// corresponding parameters in all of the overridden methods in order to + /// determine a legal type for a generated overridding method. + analyzer.DartType _escapeCovariance(ParameterElement parameter, + {int? position, bool isNamed = false}) { + assert(position != null || isNamed); + assert(position == null || !isNamed); + var type = parameter.type; + if (!parameter.isCovariant) { + return type; + } + final method = parameter.enclosingElement as MethodElement; + final class_ = method.enclosingElement as ClassElement; + final name = Name(method.librarySource.uri, method.name); + final overriddenMethods = inheritanceManager.getOverridden2(class_, name); + if (overriddenMethods == null) { + return type; + } + final allOverriddenMethods = Queue.of(overriddenMethods); + while (allOverriddenMethods.isNotEmpty) { + final overriddenMethod = allOverriddenMethods.removeFirst(); + final secondaryOverrides = inheritanceManager.getOverridden2( + overriddenMethod.enclosingElement as ClassElement, name); + if (secondaryOverrides != null) { + allOverriddenMethods.addAll(secondaryOverrides); + } + final parameters = overriddenMethod.parameters; + if (position != null) { + if (position >= parameters.length) { + // [parameter] has been _added_ in [method], and has no corresponding + // parameter in [overriddenMethod]. + // TODO(srawlins): Assert that [parameter] is optional. + continue; + } + final overriddenParameter = parameters[position]; + print('LUB of $type and ${overriddenParameter.type} is'); + // TODO(srawlins): Assert that [overriddenParameter] is not named. + type = typeSystem.leastUpperBound(type, overriddenParameter.type); + } else { + final overriddenParameter = + parameters.firstWhereOrNull((p) => p.name == parameter.name); + if (overriddenParameter == null) { + // [parameter] has been _added_ in [method], and has no corresponding + // parameter in [overriddenMethod]. + continue; + } + // TODO(srawlins): Assert that [overriddenParameter] is named. + type = typeSystem.leastUpperBound(type, overriddenParameter.type); + } + } + return type; + } + /// Creates a code_builder [Expression] from [object], a constant object from /// analyzer and [parameter], an optional [ParameterElement], when the /// expression is created for a method parameter default value. diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 238c300dc..7f38ad19d 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1291,6 +1291,127 @@ void main() { ); }); + test('widens the type of covariant parameters to be nullable', () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBase { + void m(num a); + } + abstract class Foo extends FooBase { + void m(covariant int a); + } + '''), + _containsAllOf( + 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + + test( + 'widens the type of covariant parameters with default values to be nullable', + () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBase { + void m([num a = 0]); + } + abstract class Foo extends FooBase { + void m([covariant int a = 0]); + } + '''), + _containsAllOf( + 'void m([num? a = 0]) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + + test( + 'widens the type of covariant parameters (declared covariant in a ' + 'superclass) to be nullable', () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBase { + void m(covariant num a); + } + abstract class Foo extends FooBase { + void m(int a); + } + '''), + _containsAllOf( + 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + + test('widens the type of successively covariant parameters to be nullable', + () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBaseBase { + void m(Object a); + } + abstract class FooBase extends FooBaseBase { + void m(covariant num a); + } + abstract class Foo extends FooBase { + void m(covariant int a); + } + '''), + _containsAllOf( + 'void m(Object? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }, solo: true); + + test( + 'widens the type of covariant parameters, overriding a mixin, to be nullable', + () async { + await expectSingleNonNullableOutput( + dedent(''' + mixin FooMixin { + void m(num a); + } + abstract class Foo with FooMixin { + void m(covariant int a); + } + '''), + _containsAllOf( + 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + + test( + "widens the type of covariant parameters, which don't have corresponding " + 'parameters in all overridden methods, to be nullable', () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBaseBase { + void m(); + } + abstract class FooBase extends FooBaseBase { + void m([num a]); + } + abstract class Foo extends FooBase { + void m([covariant int a]); + } + '''), + _containsAllOf( + 'void m([num? a]) => super.noSuchMethod(Invocation.method(#m, [a])'), + ); + }); + + test('widens the type of covariant named parameters to be nullable', + () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBase extends FooBaseBase { + void m({required num a}); + } + abstract class Foo extends FooBase { + void m({required covariant int a}); + } + '''), + _containsAllOf( + 'void m({num? a}) => super.noSuchMethod(Invocation.method(#m, [], {#a: a})'), + ); + }); + test('matches nullability of type arguments of a parameter', () async { await expectSingleNonNullableOutput( dedent(r''' From 0e67f184747147f93fa9f7b6751ec02fdde33a42 Mon Sep 17 00:00:00 2001 From: nbosch Date: Mon, 24 Jan 2022 18:35:09 -0500 Subject: [PATCH 413/607] Mention mixins in best practices The intent of the guideline is all instance members go through `noSuchMethod`. Make the wording stronger by saying that the class should not have any implementations it defines itself or gets through a mixin. PiperOrigin-RevId: 423927379 --- pkgs/mockito/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 5e9f17c2c..013a8a81b 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -388,11 +388,12 @@ in the tests where they are used. For responses controlled outside of the test use `@override` methods for either the entire interface, or with `extends Fake` to skip some parts of the interface. -Similarly, a class which `extends Mock` should _never_ have any `@override` -methods. These can't be stubbed by tests and can't be tracked and verified by -Mockito. A mix of test defined stubbed responses and mock defined overrides will -lead to confusion. It is OK to define _static_ utilities on a class which -`extends Mock` if it helps with code structure. +Similarly, a class which `extends Mock` should _never_ have any implementation. +It should not define any `@override` methods, and it should not mixin any +implementations. Actual member definitions can't be stubbed by tests and can't +be tracked and verified by Mockito. A mix of test defined stubbed responses and +mock defined overrides will lead to confusion. It is OK to define *static* +utilities on a class which `extends Mock` if it helps with code structure. ## Frequently asked questions From 270f19193f9de0e69465c145064d0387ef13889f Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 26 Jan 2022 10:10:49 -0500 Subject: [PATCH 414/607] Remove stray print statement PiperOrigin-RevId: 424337956 --- pkgs/mockito/lib/src/builder.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0aef11696..caeba477c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1322,7 +1322,6 @@ class _MockClassInfo { continue; } final overriddenParameter = parameters[position]; - print('LUB of $type and ${overriddenParameter.type} is'); // TODO(srawlins): Assert that [overriddenParameter] is not named. type = typeSystem.leastUpperBound(type, overriddenParameter.type); } else { From 9fb304f8b0b106145fce80a3d09f509b3197e930 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 26 Jan 2022 19:57:47 -0500 Subject: [PATCH 415/607] Generate toString override in pre-null safe code if a new signature is needed. This is already generated in null safe code. PiperOrigin-RevId: 424474801 --- pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/builder.dart | 27 ++++++++++++------- .../mockito/test/builder/auto_mocks_test.dart | 21 ++++++++++++++- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 1b236b57f..cb8003b29 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -4,6 +4,8 @@ generated code. * Properly generate method overrides for methods with covariant parameters. [#506](https://github.com/dart-lang/mockito/issues/506) +* Correctly generate a `toString` override method for pre-null safe libraries, + for which the class-to-mock implements `toString` with additional parameters. ## 5.0.17 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index caeba477c..041ac39d3 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -881,20 +881,29 @@ class _MockClassInfo { cBuilder.constructors.add(_constructorWithThrowOnMissingStub); } - // Only override members of a class declared in a library which uses the - // non-nullable type system. - if (!sourceLibIsNonNullable) { - return; - } final substitution = Substitution.fromInterfaceType(typeToMock); final members = inheritanceManager.getInterface(classToMock).map.values.map((member) { return ExecutableMember.from2(member, substitution); }); - cBuilder.methods - .addAll(fieldOverrides(members.whereType())); - cBuilder.methods - .addAll(methodOverrides(members.whereType())); + + if (sourceLibIsNonNullable) { + cBuilder.methods.addAll( + fieldOverrides(members.whereType())); + cBuilder.methods + .addAll(methodOverrides(members.whereType())); + } else { + // For a pre-null safe library, we do not need to re-implement any + // members for the purpose of expanding their parameter types. However, + // we may need to include an implementation of `toString()`, if the + // class-to-mock has added optional parameters. + var toStringMethod = members + .whereType() + .firstWhereOrNull((m) => m.name == 'toString'); + if (toStringMethod != null) { + cBuilder.methods.addAll(methodOverrides([toStringMethod])); + } + } }); } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 7f38ad19d..36eeb2ca8 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1357,7 +1357,7 @@ void main() { _containsAllOf( 'void m(Object? a) => super.noSuchMethod(Invocation.method(#m, [a])'), ); - }, solo: true); + }); test( 'widens the type of covariant parameters, overriding a mixin, to be nullable', @@ -3099,6 +3099,25 @@ void main() { ); }); + test('given a pre-non-nullable library, overrides toString if necessary', + () async { + await testPreNonNullable( + { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + String toString({bool a = false}); + } + '''), + }, + outputs: { + 'foo|test/foo_test.mocks.dart': _containsAllOf( + 'String toString({bool a = false}) => super.toString();') + }, + ); + }); + test( 'adds ignore: must_be_immutable analyzer comment if mocked class is ' 'immutable', () async { From 0a9869cc9dfaf1dce463b5c48b8de1d3f284e545 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 27 Jan 2022 13:17:11 -0500 Subject: [PATCH 416/607] Improve messaging in MissingStubError. This message was pretty cryptic if you're new to the generated mock API. PiperOrigin-RevId: 424645124 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/mock.dart | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index cb8003b29..a70056422 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,6 +6,7 @@ [#506](https://github.com/dart-lang/mockito/issues/506) * Correctly generate a `toString` override method for pre-null safe libraries, for which the class-to-mock implements `toString` with additional parameters. +* Improve messaging in a MissingStubError, directing to the docs for MockSpec. ## 5.0.17 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 1f97b0504..c6ccf2f05 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -232,7 +232,9 @@ class MissingStubError extends Error { 'No stub was found which matches the arguments of this method call:\n' '${invocation.toPrettyString()}\n\n' "Add a stub for this method using Mockito's 'when' API, or generate the " - "mock for ${receiver.runtimeType} with 'returnNullOnMissingStub: true'."; + '${receiver.runtimeType} mock with a MockSpec with ' + "'returnNullOnMissingStub: true' (see " + 'https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).'; } typedef _ReturnsCannedResponse = CallPair Function(); From aae454ccc34ed81093cb35b41007fc146a5c2c17 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 28 Jan 2022 12:32:18 -0500 Subject: [PATCH 417/607] Fix incorrect error when mocking a parameter with non-nullable inner function types Fixes https://github.com/dart-lang/mockito/issues/476 PiperOrigin-RevId: 424888004 --- pkgs/mockito/CHANGELOG.md | 3 ++ pkgs/mockito/lib/src/builder.dart | 31 +++++++++++----- .../test/builder/custom_mocks_test.dart | 35 +++++++++++++++++++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a70056422..e771bb1e0 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -7,6 +7,9 @@ * Correctly generate a `toString` override method for pre-null safe libraries, for which the class-to-mock implements `toString` with additional parameters. * Improve messaging in a MissingStubError, directing to the docs for MockSpec. +* Fix incorrect error when trying to mock a method with a parameter with inner + function types (like in type arguments) which are potentially non-nullable. + [#476](https://github.com/dart-lang/mockito/issues/476) ## 5.0.17 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 041ac39d3..549c605b0 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -639,7 +639,14 @@ class _MockTargetGatherer { /// - return type /// - parameter types /// - bounds of type parameters - /// - type arguments + /// - type arguments on types in the above three positions + /// + /// If any type in the above positions is private, [function] is un-stubbable. + /// If the return type is potentially non-nnullable, + /// [function] is un-stubbable, unless [isParamter] is true (indicating that + /// [function] is found in a parameter of a method-to-be-stubbed) or + /// [hasDummyCenerator] is true (indicating that a dummy generator, which can + /// return dummy values, has been provided). List _checkFunction( analyzer.FunctionType function, Element enclosingElement, { @@ -654,8 +661,9 @@ class _MockTargetGatherer { '${enclosingElement.fullName} features a private return type, and ' 'cannot be stubbed.'); } - errorMessages.addAll( - _checkTypeArguments(returnType.typeArguments, enclosingElement)); + errorMessages.addAll(_checkTypeArguments( + returnType.typeArguments, enclosingElement, + isParameter: isParameter)); } else if (returnType is analyzer.FunctionType) { errorMessages.addAll(_checkFunction(returnType, enclosingElement)); } else if (returnType is analyzer.TypeParameterType) { @@ -681,8 +689,9 @@ class _MockTargetGatherer { '${enclosingElement.fullName} features a private parameter type, ' "'${parameterTypeElement.name}', and cannot be stubbed."); } - errorMessages.addAll( - _checkTypeArguments(parameterType.typeArguments, enclosingElement)); + errorMessages.addAll(_checkTypeArguments( + parameterType.typeArguments, enclosingElement, + isParameter: true)); } else if (parameterType is analyzer.FunctionType) { errorMessages.addAll( _checkFunction(parameterType, enclosingElement, isParameter: true)); @@ -694,8 +703,8 @@ class _MockTargetGatherer { var aliasArguments = function.alias?.typeArguments; if (aliasArguments != null) { - errorMessages - .addAll(_checkTypeArguments(aliasArguments, enclosingElement)); + errorMessages.addAll(_checkTypeArguments(aliasArguments, enclosingElement, + isParameter: isParameter)); } return errorMessages; @@ -726,7 +735,10 @@ class _MockTargetGatherer { /// See [_checkMethodsToStubAreValid] for what properties make a function /// un-stubbable. List _checkTypeArguments( - List typeArguments, Element enclosingElement) { + List typeArguments, + Element enclosingElement, { + bool isParameter = false, + }) { var errorMessages = []; for (var typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { @@ -736,7 +748,8 @@ class _MockTargetGatherer { 'and cannot be stubbed.'); } } else if (typeArgument is analyzer.FunctionType) { - errorMessages.addAll(_checkFunction(typeArgument, enclosingElement)); + errorMessages.addAll(_checkFunction(typeArgument, enclosingElement, + isParameter: isParameter)); } } return errorMessages; diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index e1c3fb029..932a86854 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -538,6 +538,41 @@ void main() { ' returnValue: _i3.mShim(a: a)) as T);')); }); + test( + 'generates mock classes including a fallback generator for a generic ' + 'method with a parameter with a function-typed type argument with ' + 'unknown return type', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + T m({List a}); + } + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T mShim({List a}) { + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [ + MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim}), + ], + ) + void main() {} + ''') + }); + expect( + mocksContent, + contains('T m({List? a}) =>\n' + ' (super.noSuchMethod(Invocation.method(#m, [], {#a: a}),\n' + ' returnValue: _i3.mShim(a: a)) as T);')); + }); + test( 'throws when GenerateMocks is given a class with a type parameter with a ' 'private bound', () async { From 2ea799b8df9ecf6ca69f87d8907c560881a3fc19 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 8 Feb 2022 12:39:31 -0500 Subject: [PATCH 418/607] Add overrideUnsupportedMembers option to MockSpec In migrating much mockito code to code generation, I've found that in the vast majority of fallbackGenerators use cases, a fallback generator is used exclusively to satisfy the type contract, and is never used at runtime, because the method is never used in the test into which it is generated. In these cases it seems silly to require users to carefully type out this fallback function and choose a body for it (throw this? throw that? try to account for return types and return things?). Also fix an issue with fallback generators and getters. PiperOrigin-RevId: 427214894 --- pkgs/mockito/CHANGELOG.md | 4 + pkgs/mockito/NULL_SAFETY_README.md | 57 +++++--- pkgs/mockito/lib/annotations.dart | 28 +++- pkgs/mockito/lib/src/builder.dart | 135 ++++++++++++++---- .../mockito/test/builder/auto_mocks_test.dart | 44 ++++-- .../test/builder/custom_mocks_test.dart | 66 +++++++++ pkgs/mockito/test/end2end/foo.dart | 3 +- .../test/end2end/generated_mocks_test.dart | 43 ++++-- 8 files changed, 306 insertions(+), 74 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index e771bb1e0..881d7c13c 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -10,6 +10,10 @@ * Fix incorrect error when trying to mock a method with a parameter with inner function types (like in type arguments) which are potentially non-nullable. [#476](https://github.com/dart-lang/mockito/issues/476) +* Allow fallback generators to be applied for getters. +* Support generating a mock class for a class with members with non-nullable + unknown return types via a new parameter on `MockSpec` called + `unsupportedMembers`. See [NULL_SAFETY_README][] for details. ## 5.0.17 diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 77963a077..a91fa3ec0 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -36,13 +36,11 @@ when(server.start(any)).thenReturn(uri); This code is, unfortunately, illegal under null safety in two ways. For details, see the section at the bottom, **Problems with typical mocking and stubbing**. -## Solutions - There are two ways to write a mock class that supports non-nullable types: we can use the [build_runner] package to _generate_ a mock class, or we can manually implement it, overriding specific methods to handle non-nullability. -### Code generation +## Solution 1: code generation Mockito provides a "one size fits all" code-generating solution for packages that use null safety which can generate a mock for any class. To direct Mockito @@ -156,11 +154,11 @@ sense in the Null safety type system), for legacy code, use @GenerateMocks([], customMocks: [MockSpec(returnNullOnMissingStub: true)]) ``` -#### Fallback generators +#### Mocking members with non-nullable type variable return types -If a class has a method with a type variable as a return type (for example, -`T get();`), mockito cannot generate code which will internally return valid -values. For example, given this class and test: +If a class has a member with a type variable as a return type (for example, +`T get();`), mockito cannot generate code which will internally return a +valid value. For example, given this class and test: ```dart abstract class Foo { @@ -169,17 +167,36 @@ abstract class Foo { @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) void testFoo(Foo foo) { - when( foo.m(7) ).thenReturn(42); - // ^^^^^^^^ - // mockito needs a valid value which this call to `foo.m` will return. + when(foo.m(7)).thenReturn(42); } ``` -In order to generate a mock for such a class, pass a `fallbackGenerators` -argument. Specify a mapping from the method to a top level function with -_almost_ the same signature as the method. The function must have the same -return type as the method, and it must have the same positional and named -parameters as the method, except that each parameter must be made nullable: +Mockito needs a valid _value_ which the above call, `foo.m(7)`, will return. + +##### Overriding unsupported members + +One way to generate a mock class with such members is to use +`unsupportedMembers`: + +```dart +@GenerateMocks([], customMocks: [ + MockSpec(unsupportedMembers: {#method}), +]) +``` + +Mockito uses this instruction to generate an override, which simply throws an +UnsupportedError, for each specified unsupported member. Such overrides are not +useful at runtime, but their presence makes the mock class a valid +implementation of the class-to-mock. + +##### Fallback generators + +In order to generate a mock class with a more useful implementation of a member +which returns a non-nullable type variable, use MockSpec's `fallbackGenerators` +parameter. Specify a mapping from the member to a top level function with +_almost_ the same signature as the member. The function must have the same +return type as the member, and it must have the same positional and named +parameters as the member, except that each parameter must be made nullable: ```dart abstract class Foo { @@ -196,11 +213,11 @@ T mShim(T a, int? b) { ]) ``` -The fallback values will never be returned from a real method call; these are +The fallback values will never be returned from a real member call; these are not stub return values. They are only used internally by mockito as valid return values. -### Manual mock implementaion +## Solution 2: manual mock implementation **In the general case, we strongly recommend generating mocks with the above method.** However, there may be cases where a manual mock implementation is more @@ -211,7 +228,7 @@ Perhaps we wish to mock just one class which has mostly nullable parameter types and return types. In this case, it may not be too onerous to implement the mock class manually. -#### The general process +### The general process Only public methods (including getters, setters, and operators) which either have a non-nullable return type, or a parameter with a non-nullable type, need @@ -245,7 +262,7 @@ the `noSuchMethod` method found in the Mock class. Under null safety, we need to override that implementation for every method which has one or more non-nullable parameters, or which has a non-nullable return type. -#### Manually override a method with a non-nullable parameter type. +### Manually override a method with a non-nullable parameter type. First let's override `start` with an implementation that expands the single parameter's type to be nullable, and call `super.noSuchMethod` to handle the @@ -279,7 +296,7 @@ That's it! The override implementation is all boilerplate. See the API for [Invocation] constructors to see how to implement an override for an operator or a setter. -#### Manually override a method with a non-nullable return type. +### Manually override a method with a non-nullable return type. Next let's override `get uri`. It would be illegal to override a getter which returns a non-nullable Uri with a getter which returns a _nullable_ Uri. diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index d68210c5f..280db404d 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -71,23 +71,39 @@ class MockSpec { final bool returnNullOnMissingStub; + final Set unsupportedMembers; + final Map fallbackGenerators; /// Constructs a custom mock specification. /// /// Specify a custom name with the [as] parameter. /// - /// If [returnNullOnMissingStub] is true, the mock class will return `null` - /// when a method is called and no stub could be found. This may result in a - /// runtime error, if the return type of the method is non-nullable. + /// If [returnNullOnMissingStub] is true, a real call to a mock method will + /// return `null` when no stub is found. This may result in a runtime error, + /// if the return type of the method is non-nullable. + /// + /// If the class-to-mock has a member with a non-nullable unknown return type + /// (such as a type variable, `T`), then mockito cannot generate a valid + /// override member, unless the member is specified in [unsupportedMembers], + /// or a fallback implementation is given in [fallbackGenerators]. + /// + /// If [unsupportedMembers] contains the name of each such member, the mock + /// class is generated with an override, with throws an exception, for each + /// member with a non-nullable unknown return type. Such an override cannot be + /// used with the mockito stubbing and verification APIs, but makes the mock + /// class a valid implementation of the class-to-mock. /// /// Each entry in [fallbackGenerators] specifies a mapping from a method name - /// to a function, with the same signature as the method. This function will - /// be used to generate fallback values when a non-null value needs to be - /// returned when stubbing or verifying. + /// to a function, with the same signature as the method. This function is + /// used to generate fallback values when a non-null value needs to be + /// returned when stubbing or verifying. A fallback value is not ever exposed + /// in stubbing or verifying; it is an object that mockito's internals can use + /// as a legal return value. const MockSpec({ Symbol? as, this.returnNullOnMissingStub = false, + this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, }) : mockName = as; } diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 549c605b0..9d6726267 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -357,12 +357,15 @@ class _MockTarget { final bool returnNullOnMissingStub; + final Set unsupportedMembers; + final Map fallbackGenerators; _MockTarget( this.classType, this.mockName, { required this.returnNullOnMissingStub, + required this.unsupportedMembers, required this.fallbackGenerators, }); @@ -438,8 +441,13 @@ class _MockTargetGatherer { final declarationType = (type.element.declaration as ClassElement).thisType; final mockName = 'Mock${declarationType.element.name}'; - mockTargets.add(_MockTarget(declarationType, mockName, - returnNullOnMissingStub: false, fallbackGenerators: {})); + mockTargets.add(_MockTarget( + declarationType, + mockName, + returnNullOnMissingStub: false, + unsupportedMembers: {}, + fallbackGenerators: {}, + )); } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { @@ -465,12 +473,21 @@ class _MockTargetGatherer { 'Mock${type.element.name}'; final returnNullOnMissingStub = mockSpec.getField('returnNullOnMissingStub')!.toBoolValue()!; + final unsupportedMembers = { + for (final m + in mockSpec.getField('unsupportedMembers')!.toSetValue()!) + m.toSymbolValue()!, + }; final fallbackGeneratorObjects = mockSpec.getField('fallbackGenerators')!.toMapValue()!; - mockTargets.add(_MockTarget(type, mockName, - returnNullOnMissingStub: returnNullOnMissingStub, - fallbackGenerators: - _extractFallbackGenerators(fallbackGeneratorObjects))); + mockTargets.add(_MockTarget( + type, + mockName, + returnNullOnMissingStub: returnNullOnMissingStub, + unsupportedMembers: unsupportedMembers, + fallbackGenerators: + _extractFallbackGenerators(fallbackGeneratorObjects), + )); } } return mockTargets; @@ -614,9 +631,14 @@ class _MockTargetGatherer { if (_entryLib.typeSystem._returnTypeIsNonNullable(member) || _entryLib.typeSystem._hasNonNullableParameter(member) || _needsOverrideForVoidStub(member)) { - return _checkFunction(member.type, member, - hasDummyGenerator: - mockTarget.fallbackGenerators.containsKey(member.name)); + return _checkFunction( + member.type, + member, + allowUnsupportedMember: + mockTarget.unsupportedMembers.contains(member.name), + hasDummyGenerator: + mockTarget.fallbackGenerators.containsKey(member.name), + ); } else { // Mockito is not going to override this method, so the types do not // need to be checked. @@ -642,15 +664,17 @@ class _MockTargetGatherer { /// - type arguments on types in the above three positions /// /// If any type in the above positions is private, [function] is un-stubbable. - /// If the return type is potentially non-nnullable, - /// [function] is un-stubbable, unless [isParamter] is true (indicating that - /// [function] is found in a parameter of a method-to-be-stubbed) or - /// [hasDummyCenerator] is true (indicating that a dummy generator, which can - /// return dummy values, has been provided). + /// If the return type is potentially non-nullable, [function] is + /// un-stubbable, unless [isParamter] is true (indicating that [function] is + /// found in a parameter of a method-to-be-stubbed) or + /// [allowUnsupportedMember] is true, or [hasDummyCenerator] is true + /// (indicating that a dummy generator, which can return dummy values, has + /// been provided). List _checkFunction( analyzer.FunctionType function, Element enclosingElement, { bool isParameter = false, + bool allowUnsupportedMember = false, bool hasDummyGenerator = false, }) { final errorMessages = []; @@ -668,12 +692,14 @@ class _MockTargetGatherer { errorMessages.addAll(_checkFunction(returnType, enclosingElement)); } else if (returnType is analyzer.TypeParameterType) { if (!isParameter && + !allowUnsupportedMember && !hasDummyGenerator && _entryLib.typeSystem.isPotentiallyNonNullable(returnType)) { - errorMessages - .add('${enclosingElement.fullName} features a non-nullable unknown ' - 'return type, and cannot be stubbed without a dummy generator ' - 'specified on the MockSpec.'); + errorMessages.add( + '${enclosingElement.fullName} features a non-nullable unknown ' + 'return type, and cannot be stubbed. Try generating this mock with ' + "a MockSpec with 'unsupportedMembers' or a dummy generator (see " + 'https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).'); } } @@ -1049,6 +1075,28 @@ class _MockClassInfo { return; } + final returnType = method.returnType; + final fallbackGenerator = fallbackGenerators[method.name]; + if (typeSystem.isPotentiallyNonNullable(returnType) && + returnType is analyzer.TypeParameterType && + fallbackGenerator == null) { + if (!mockTarget.unsupportedMembers.contains(name)) { + // We shouldn't get here as this is guarded against in + // [_MockTargetGatherer._checkFunction]. + throw InvalidMockitoAnnotationException( + "Mockito cannot generate a valid override for '$name', as it has a " + 'non-nullable unknown return type.'); + } + builder.body = refer('UnsupportedError') + .call([ + literalString( + "'$name' cannot be used without a mockito fallback generator.") + ]) + .thrown + .code; + return; + } + final invocation = refer('Invocation').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), @@ -1056,34 +1104,32 @@ class _MockClassInfo { ]); Expression? returnValueForMissingStub; - if (method.returnType.isVoid) { + if (returnType.isVoid) { returnValueForMissingStub = refer('null'); - } else if (method.returnType.isFutureOfVoid) { + } else if (returnType.isFutureOfVoid) { returnValueForMissingStub = _futureReference(refer('void')).property('value').call([]); } - final fallbackGenerator = fallbackGenerators[method.name]; final namedArgs = { if (fallbackGenerator != null) 'returnValue': _fallbackGeneratorCode(method, fallbackGenerator) else if (typeSystem._returnTypeIsNonNullable(method)) - 'returnValue': _dummyValue(method.returnType), + 'returnValue': _dummyValue(returnType), if (returnValueForMissingStub != null) 'returnValueForMissingStub': returnValueForMissingStub, }; var superNoSuchMethod = refer('super').property('noSuchMethod').call([invocation], namedArgs); - if (!method.returnType.isVoid && !method.returnType.isDynamic) { - superNoSuchMethod = - superNoSuchMethod.asA(_typeReference(method.returnType)); + if (!returnType.isVoid && !returnType.isDynamic) { + superNoSuchMethod = superNoSuchMethod.asA(_typeReference(returnType)); } builder.body = superNoSuchMethod.code; } Expression _fallbackGeneratorCode( - MethodElement method, ExecutableElement function) { + ExecutableElement method, ExecutableElement function) { final positionalArguments = []; final namedArguments = {}; for (final parameter in method.parameters) { @@ -1292,7 +1338,7 @@ class _MockClassInfo { final method = parameter.enclosingElement!; final clazz = method.enclosingElement!; throw InvalidMockitoAnnotationException( - 'Mockito cannot generate a valid stub for method ' + 'Mockito cannot generate a valid override for method ' "'${clazz.displayName}.${method.displayName}'; parameter " "'${parameter.displayName}' causes a problem: ${e.message}"); } @@ -1463,15 +1509,42 @@ class _MockClassInfo { ..type = MethodType.getter ..returns = _typeReference(getter.returnType); + final returnType = getter.returnType; + final fallbackGenerator = fallbackGenerators[getter.name]; + if (typeSystem.isPotentiallyNonNullable(returnType) && + returnType is analyzer.TypeParameterType && + fallbackGenerator == null) { + if (!mockTarget.unsupportedMembers.contains(getter.name)) { + // We shouldn't get here as this is guarded against in + // [_MockTargetGatherer._checkFunction]. + throw InvalidMockitoAnnotationException( + "Mockito cannot generate a valid override for '${getter.name}', as " + 'it has a non-nullable unknown type.'); + } + builder.body = refer('UnsupportedError') + .call([ + literalString( + "'${getter.name}' cannot be used without a mockito fallback " + 'generator.') + ]) + .thrown + .code; + return; + } + final invocation = refer('Invocation').property('getter').call([ refer('#${getter.displayName}'), ]); - final namedArgs = {'returnValue': _dummyValue(getter.returnType)}; + final namedArgs = { + if (fallbackGenerator != null) + 'returnValue': _fallbackGeneratorCode(getter, fallbackGenerator) + else if (typeSystem._returnTypeIsNonNullable(getter)) + 'returnValue': _dummyValue(returnType), + }; var superNoSuchMethod = refer('super').property('noSuchMethod').call([invocation], namedArgs); - if (!getter.returnType.isVoid && !getter.returnType.isDynamic) { - superNoSuchMethod = - superNoSuchMethod.asA(_typeReference(getter.returnType)); + if (!returnType.isVoid && !returnType.isDynamic) { + superNoSuchMethod = superNoSuchMethod.asA(_typeReference(returnType)); } builder.body = superNoSuchMethod.code; diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 36eeb2ca8..ecbf7c009 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -38,7 +38,16 @@ class MockSpec { final bool returnNullOnMissingStub; - const MockSpec({Symbol as, this.returnNullOnMissingStub = false}) + final Set unsupportedMembers; + + final Map fallbackGenerators; + + const MockSpec({ + Symbol as, + this.returnNullOnMissingStub = false, + this.unsupportedMembers = const {}, + this.fallbackGenerators = const {}, + }) : mockName = as; } ''' @@ -470,8 +479,8 @@ void main() { '''), }, message: contains( - "Mockito cannot generate a valid stub for method 'Foo.m'; parameter " - "'a' causes a problem: default value has a private type: " + "Mockito cannot generate a valid override for method 'Foo.m'; " + "parameter 'a' causes a problem: default value has a private type: " 'asset:foo/lib/foo.dart#_Bar'), ); }); @@ -493,8 +502,8 @@ void main() { '''), }, message: contains( - "Mockito cannot generate a valid stub for method 'Foo.m'; parameter " - "'a' causes a problem: default value has a private type: " + "Mockito cannot generate a valid override for method 'Foo.m'; " + "parameter 'a' causes a problem: default value has a private type: " 'asset:foo/lib/foo.dart#Bar::_named'), ); }); @@ -510,9 +519,9 @@ void main() { } '''), }, - message: contains( - "Mockito cannot generate a valid stub for method 'Foo.m'; parameter " - "'a' causes a problem: default value is a Type: int"), + message: contains('Mockito cannot generate a valid override for method ' + "'Foo.m'; parameter 'a' causes a problem: default value is a Type: " + 'int'), ); }); @@ -2817,6 +2826,25 @@ void main() { ); }); + test( + 'throws when GenerateMocks is given a class with a getter with a ' + 'non-nullable class-declared type variable type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + T get f; + } + '''), + }, + message: contains( + "The property accessor 'Foo.f' features a non-nullable unknown " + 'return type, and cannot be stubbed'), + ); + }); + test( 'throws when GenerateMocks is given a class with a method with a ' 'non-nullable class-declared type variable return type', () async { diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 932a86854..2d3e7c16e 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -37,11 +37,14 @@ class MockSpec { final bool returnNullOnMissingStub; + final Set unsupportedMembers; + final Map fallbackGenerators; const MockSpec({ Symbol? as, this.returnNullOnMissingStub = false, + this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, }) : mockName = as; } @@ -325,6 +328,69 @@ void main() { expect(mocksContent, isNot(contains('throwOnMissingStub'))); }); + test( + 'generates mock methods with non-nullable unknown types, given ' + 'unsupportedMembers', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + T m(T a); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks( + [], + customMocks: [ + MockSpec(unsupportedMembers: {#m}), + ], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' T m(T? a) => throw UnsupportedError(\n' + r" '\'m\' cannot be used without a mockito fallback generator.');")); + }); + + test('generates mock classes including a fallback generator for a getter', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + T get f; + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T fShim() { + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [ + MockSpec(fallbackGenerators: {#f: fShim}), + ], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains('T get f =>\n' + ' (super.noSuchMethod(Invocation.getter(#f), returnValue: _i3.fShim())\n' + ' as T);'), + ); + }); + test( 'generates mock classes including a fallback generator for a generic ' 'method with positional parameters', () async { diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 1ba22ff0f..b10a4479e 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -24,8 +24,9 @@ class Foo { class Bar {} -abstract class Baz { +abstract class Baz { T returnsTypeVariable(); T returnsBoundedTypeVariable(); T returnsTypeVariableFromTwo(); + S get typeVariableField; } diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 6aa4f44ef..29202e05a 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -6,11 +6,15 @@ import 'foo.dart'; import 'foo_sub.dart'; import 'generated_mocks_test.mocks.dart'; -T dummyMethod() => [1, 1.5].whereType().first!; +T returnsTypeVariableShim() => [1, 1.5].whereType().first!; -T dummyBoundedMethod() => [1, 1.5].whereType().first!; +T returnsBoundedTypeVariableShim() => + [1, 1.5].whereType().first!; -T dummyMethodTwo() => [1, 1.5].whereType().first!; +T returnsTypeVariableFromTwoShim() => [1, 1.5].whereType().first!; + +T typeVariableFieldShim() => + throw UnsupportedError('typeVariableField cannot be used'); @GenerateMocks([ Foo, @@ -19,10 +23,17 @@ T dummyMethodTwo() => [1, 1.5].whereType().first!; ], customMocks: [ MockSpec(as: #MockFooRelaxed, returnNullOnMissingStub: true), MockSpec(as: #MockBarRelaxed, returnNullOnMissingStub: true), - MockSpec(as: #MockBaz, fallbackGenerators: { - #returnsTypeVariable: dummyMethod, - #returnsBoundedTypeVariable: dummyBoundedMethod, - #returnsTypeVariableFromTwo: dummyMethodTwo, + MockSpec(as: #MockBazWithUnsupportedMembers, unsupportedMembers: { + #returnsTypeVariable, + #returnsBoundedTypeVariable, + #returnsTypeVariableFromTwo, + #typeVariableField, + }), + MockSpec(as: #MockBazWithFallbackGenerators, fallbackGenerators: { + #returnsTypeVariable: returnsTypeVariableShim, + #returnsBoundedTypeVariable: returnsBoundedTypeVariableShim, + #returnsTypeVariableFromTwo: returnsTypeVariableFromTwoShim, + #typeVariableField: typeVariableFieldShim, }), ]) void main() { @@ -157,11 +168,27 @@ void main() { }); }); + group('for a generated mock using unsupportedMembers', () { + late Baz baz; + + setUp(() { + baz = MockBazWithUnsupportedMembers(); + }); + + test('a real method call throws', () { + expect(() => baz.returnsTypeVariable(), throwsUnsupportedError); + }); + + test('a real getter call (or field access) throws', () { + expect(() => baz.typeVariableField, throwsUnsupportedError); + }); + }); + group('for a generated mock using fallbackGenerators,', () { late Baz baz; setUp(() { - baz = MockBaz(); + baz = MockBazWithFallbackGenerators(); }); test('a method with a type variable return type can be called', () { From 0938766b976627fed76dc2a80f2584386a1eab53 Mon Sep 17 00:00:00 2001 From: nbosch Date: Tue, 8 Feb 2022 19:39:55 -0500 Subject: [PATCH 419/607] Switch to a feature version bump There is new API surface area in this release. PiperOrigin-RevId: 427326116 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 881d7c13c..553711971 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.18-dev +## 5.1.0-dev * In creating mocks for a pre-null-safe library, opt out of null safety in the generated code. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index dd2b00b9a..f63bc2114 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.18-dev'; +const packageVersion = '5.1.0-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 31fcc8a79..29ebe0cf1 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.0.18-dev +version: 5.1.0-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, From 4966a11dcfa7e72546bde1cc0fc3b8baa2905af3 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 9 Feb 2022 12:26:39 -0500 Subject: [PATCH 420/607] Mark a few functions as `@useResult` to prevent accidental misuse. PiperOrigin-RevId: 427490997 --- pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/mock.dart | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 553711971..2e0514a85 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -14,6 +14,8 @@ * Support generating a mock class for a class with members with non-nullable unknown return types via a new parameter on `MockSpec` called `unsupportedMembers`. See [NULL_SAFETY_README][] for details. +* Mark `when`, `verify`, `verifyInOrder`, `verifyNever`, and `untilCalled` with + `@useResult` to encourage proper API use. ## 5.0.17 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index c6ccf2f05..da8236743 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -892,6 +892,7 @@ typedef _InOrderVerification = List Function( /// /// Mockito will pass the current test case, as `cat.eatFood` has not been /// called with `"chicken"`. +@useResult Verification get verifyNever => _makeVerify(true); /// Verify that a method on a mock object was called with the given arguments. @@ -921,6 +922,7 @@ Verification get verifyNever => _makeVerify(true); /// /// See also: [verifyNever], [verifyInOrder], [verifyZeroInteractions], and /// [verifyNoMoreInteractions]. +@useResult Verification get verify => _makeVerify(false); Verification _makeVerify(bool never) { @@ -988,6 +990,7 @@ Verification _makeVerify(bool never) { /// given, but not that those were the only calls. In the example above, if /// other calls were made to `eatFood` or `sound` between the three given /// calls, or before or after them, the verification will still succeed. +@useResult _InOrderVerification get verifyInOrder { if (_verifyCalls.isNotEmpty) { throw StateError(_verifyCalls.join()); @@ -1080,6 +1083,7 @@ typedef Expectation = PostExpectation Function(T x); /// The response generators include `thenReturn`, `thenAnswer`, and `thenThrow`. /// /// See the README for more information. +@useResult Expectation get when { if (_whenCall != null) { throw StateError('Cannot call `when` within a stub response'); @@ -1106,6 +1110,7 @@ typedef InvocationLoader = Future Function(T _); /// In the above example, the untilCalled(cat.chew()) will complete only when /// that method is called. If the given invocation has already been called, the /// future will return immediately. +@useResult InvocationLoader get untilCalled { _untilCalledInProgress = true; return (T _) { From e695e4c2e675fffe42b8b971da9437ad7dc2f019 Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 9 Feb 2022 17:11:44 -0500 Subject: [PATCH 421/607] Automated g4 rollback of changelist 427490997. *** Reason for rollback *** Breaking existing code, due to https://github.com/dart-lang/sdk/issues/47473. *** Original change description *** Mark a few functions as `@useResult` to prevent accidental misuse. *** PiperOrigin-RevId: 427568105 --- pkgs/mockito/CHANGELOG.md | 2 -- pkgs/mockito/lib/src/mock.dart | 5 ----- 2 files changed, 7 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 2e0514a85..553711971 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -14,8 +14,6 @@ * Support generating a mock class for a class with members with non-nullable unknown return types via a new parameter on `MockSpec` called `unsupportedMembers`. See [NULL_SAFETY_README][] for details. -* Mark `when`, `verify`, `verifyInOrder`, `verifyNever`, and `untilCalled` with - `@useResult` to encourage proper API use. ## 5.0.17 diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index da8236743..c6ccf2f05 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -892,7 +892,6 @@ typedef _InOrderVerification = List Function( /// /// Mockito will pass the current test case, as `cat.eatFood` has not been /// called with `"chicken"`. -@useResult Verification get verifyNever => _makeVerify(true); /// Verify that a method on a mock object was called with the given arguments. @@ -922,7 +921,6 @@ Verification get verifyNever => _makeVerify(true); /// /// See also: [verifyNever], [verifyInOrder], [verifyZeroInteractions], and /// [verifyNoMoreInteractions]. -@useResult Verification get verify => _makeVerify(false); Verification _makeVerify(bool never) { @@ -990,7 +988,6 @@ Verification _makeVerify(bool never) { /// given, but not that those were the only calls. In the example above, if /// other calls were made to `eatFood` or `sound` between the three given /// calls, or before or after them, the verification will still succeed. -@useResult _InOrderVerification get verifyInOrder { if (_verifyCalls.isNotEmpty) { throw StateError(_verifyCalls.join()); @@ -1083,7 +1080,6 @@ typedef Expectation = PostExpectation Function(T x); /// The response generators include `thenReturn`, `thenAnswer`, and `thenThrow`. /// /// See the README for more information. -@useResult Expectation get when { if (_whenCall != null) { throw StateError('Cannot call `when` within a stub response'); @@ -1110,7 +1106,6 @@ typedef InvocationLoader = Future Function(T _); /// In the above example, the untilCalled(cat.chew()) will complete only when /// that method is called. If the given invocation has already been called, the /// future will return immediately. -@useResult InvocationLoader get untilCalled { _untilCalledInProgress = true; return (T _) { From ec6f6c2419593974a2624687b3b4848a6317d7c9 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 10 Feb 2022 18:32:14 -0500 Subject: [PATCH 422/607] Bump to version 5.1.0 PiperOrigin-RevId: 427859393 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 553711971..a70a77091 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.1.0-dev +## 5.1.0 * In creating mocks for a pre-null-safe library, opt out of null safety in the generated code. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index f63bc2114..6347bc32c 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.1.0-dev'; +const packageVersion = '5.1.0'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 29ebe0cf1..c64518341 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.1.0-dev +version: 5.1.0 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, From 3e7e3b6d7db7d336e17af73260eddaaa856374db Mon Sep 17 00:00:00 2001 From: srawlins Date: Sat, 5 Mar 2022 18:58:33 -0500 Subject: [PATCH 423/607] Fix generation of methods with return type of `FutureOr`. The type is potentially non-nullable, as `T` (bounded to dynamic) is potentially non-nullable. Use the original code's nullability suffix instead. PiperOrigin-RevId: 432694680 --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/lib/src/builder.dart | 4 +++- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- pkgs/mockito/test/builder/auto_mocks_test.dart | 16 ++++++++++++++++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a70a77091..a062e62ae 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.1.1-dev + +* Fix generation of methods with return type of `FutureOr` for generic, + potentially nullable `T`. + ## 5.1.0 * In creating mocks for a pre-null-safe library, opt out of null safety in the diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 9d6726267..6239ed1fa 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -16,6 +16,7 @@ import 'dart:collection'; import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart' as analyzer; import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/dart/element/type_system.dart'; @@ -1609,7 +1610,8 @@ class _MockClassInfo { return TypeReference((b) { b ..symbol = type.element.name - ..isNullable = forceNullable || typeSystem.isPotentiallyNullable(type) + ..isNullable = forceNullable || + type.nullabilitySuffix == NullabilitySuffix.question ..url = _typeImport(type.element) ..types.addAll(type.typeArguments.map(_typeReference)); }); diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 6347bc32c..572664595 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.1.0'; +const packageVersion = '5.1.1-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c64518341..c222996ec 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.1.0 +version: 5.1.1-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index ecbf7c009..166c9baf9 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1445,6 +1445,22 @@ void main() { ); }); + test( + 'matches nullability of return type of FutureOr for potentially nullable T', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + import 'dart:async'; + abstract class Foo { + FutureOr m(); + } + '''), + _containsAllOf( + '_i3.FutureOr m() => (super.noSuchMethod(Invocation.method(#m, []),', + ' returnValue: Future.value(null)) as _i3.FutureOr);'), + ); + }); + test( 'matches nullability of parameter types within a generic function-typed ' 'parameter', () async { From ddef210ca3771f705180acefed2d2944844ccbbd Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 14 Mar 2022 16:00:21 -0400 Subject: [PATCH 424/607] Support `@GenerateMocks` annotations on `import` and `export` directives. This benefits 'mock' libraries which declare mocks but not any tests or any other top-level elements. PiperOrigin-RevId: 434544581 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/builder.dart | 9 +++-- .../mockito/test/builder/auto_mocks_test.dart | 34 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a062e62ae..fa777d468 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,7 @@ * Fix generation of methods with return type of `FutureOr` for generic, potentially nullable `T`. +* Support `@GenerateMocks` annotations on `import` and `export` directives. ## 5.1.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 6239ed1fa..14e157d37 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -396,13 +396,18 @@ class _MockTargetGatherer { ) { final mockTargets = <_MockTarget>{}; - for (final element in entryLib.topLevelElements) { + final possiblyAnnotatedElements = [ + ...entryLib.exports, + ...entryLib.imports, + ...entryLib.topLevelElements, + ]; + + for (final element in possiblyAnnotatedElements) { // TODO(srawlins): Re-think the idea of multiple @GenerateMocks // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; final annotationClass = annotation.element!.enclosingElement!.name; - // TODO(srawlins): check library as well. if (annotationClass == 'GenerateMocks') { mockTargets .addAll(_mockTargetsFromGenerateMocks(annotation, entryLib)); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 166c9baf9..6e69bd395 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -808,6 +808,40 @@ void main() { ); }); + test('generates mock classes from an annotation on an import directive', + () async { + var mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r'class Foo {} class Bar {}'), + 'foo|test/foo_test.dart': ''' + @GenerateMocks([Foo]) + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + ''' + }); + expect(mocksOutput, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + }); + + test('generates mock classes from an annotation on an export directive', + () async { + var mocksOutput = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + @GenerateMocks([Foo]) + export 'dart:core'; + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + ''' + }); + expect(mocksOutput, + contains('class MockFoo extends _i1.Mock implements _i2.Foo')); + }); + test('generates multiple mock classes', () async { var mocksOutput = await buildWithNonNullable({ ...annotationsAsset, From 447be29f3d47289100fddd655275084b6fd191db Mon Sep 17 00:00:00 2001 From: Samuel Rawlins Date: Wed, 6 Apr 2022 15:04:58 -0400 Subject: [PATCH 425/607] clarify docs for unsupportedMembers. some problems with the previous wording: - "each such member" is ambiguous since the previous paragraph talks about 2 or perhaps 3 kinds of members: - 1 where the return type is non-nullable and unknown - 1 where it's that plus it's specified in unsupportedMembers - presumably 1 more where it's that plus not in unsupportedMembers - typo: "with throws" - the hypothesis "if unsupportedMembers contains the name of each such member" makes one wonder what unsupportedMembers does in general. is this asserting that client code must put such members into unsupportedMembers? is unsupportedMembers allowed to contain more than that? - "generated with an override" should probably apply to the member, not the class PiperOrigin-RevId: 439901387 --- pkgs/mockito/lib/annotations.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 280db404d..b2c1504f7 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -88,11 +88,12 @@ class MockSpec { /// override member, unless the member is specified in [unsupportedMembers], /// or a fallback implementation is given in [fallbackGenerators]. /// - /// If [unsupportedMembers] contains the name of each such member, the mock - /// class is generated with an override, with throws an exception, for each - /// member with a non-nullable unknown return type. Such an override cannot be - /// used with the mockito stubbing and verification APIs, but makes the mock - /// class a valid implementation of the class-to-mock. + /// For each member M in [unsupportedMembers], the mock class will have an + /// override that throws, which may be useful if the return type T of M is + /// non-nullable and it's inconvenient to define a fallback generator for M, + /// e.g. if T is an unknown type variable. Such an override cannot be used + /// with the mockito stubbing and verification APIs, but makes the mock class + /// a valid implementation of the class-to-mock. /// /// Each entry in [fallbackGenerators] specifies a mapping from a method name /// to a function, with the same signature as the method. This function is From 101a1e69bd6660303a91278deb2574be4dd5eadd Mon Sep 17 00:00:00 2001 From: fzyzcjy <5236035+fzyzcjy@users.noreply.github.com> Date: Fri, 13 May 2022 17:56:38 +0800 Subject: [PATCH 426/607] Fix compile errors in Flutter 3.0 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c222996ec..d98112175 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=2.1.0 <4.0.0' + analyzer: '>=2.1.0 <5.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 From b2803c9337f3c0bedc746e76888fc42cd96fb44f Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 19 Apr 2022 11:06:25 -0400 Subject: [PATCH 427/607] Populate the pubspec 'repository' field. PiperOrigin-RevId: 442813943 --- pkgs/mockito/pubspec.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index d98112175..bde500e13 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,16 +1,15 @@ name: mockito version: 5.1.1-dev - description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. -homepage: https://github.com/dart-lang/mockito +repository: https://github.com/dart-lang/mockito environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=2.1.0 <5.0.0' + analyzer: '>=2.1.0 <4.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 From 7ef3d7b8d176b1e7261e47204071456b9498a94d Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 16 May 2022 13:03:21 -0400 Subject: [PATCH 428/607] Import https://github.com/dart-lang/mockito/pull/533 Fix compile errors in Flutter 3.0 PiperOrigin-RevId: 448988369 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index bde500e13..499fe54a2 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=2.1.0 <4.0.0' + analyzer: '>=2.1.0 <5.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.0.0 collection: ^1.15.0 From 5260d7dc0308436175213b4a5f265f04524050c3 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 16 May 2022 17:15:07 -0400 Subject: [PATCH 429/607] Release mockito 5.2.0 Minor version release, as annotating imports and exports is a new feature. PiperOrigin-RevId: 449051035 --- pkgs/mockito/CHANGELOG.md | 7 ++++--- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index fa777d468..8c4c089d0 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,9 @@ -## 5.1.1-dev +## 5.2.0 * Fix generation of methods with return type of `FutureOr` for generic, potentially nullable `T`. * Support `@GenerateMocks` annotations on `import` and `export` directives. +* Support analyzer 4.x. ## 5.1.0 @@ -29,7 +30,7 @@ * Do not needlessly implement `toString` unless the class-to-mock implements `toString` with additional parameters. [#461](https://github.com/dart-lang/mockito/issues/461) -* Support analyzer 3.x +* Support analyzer 3.x. ## 5.0.16 @@ -71,7 +72,7 @@ * Override `toString` in a Fake implementation when the class-to-be-faked has a superclass which overrides `toString` with additional parameters. [#371](https://github.com/dart-lang/mockito/issues/371) -* Support analyzer 2.0.0 +* Support analyzer 2.x. ## 5.0.11 diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 572664595..9c8430933 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.1.1-dev'; +const packageVersion = '5.2.0'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 499fe54a2..91ce9917b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.1.1-dev +version: 5.2.0 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From bbecf91c491577cfd5d0db4ebb1e96d476d875ea Mon Sep 17 00:00:00 2001 From: "D. Kasi Pavan Kumar" <44864604+kasipavankumar@users.noreply.github.com> Date: Sat, 4 Jun 2022 16:36:08 +0530 Subject: [PATCH 430/607] Fix ISS example readme links. --- pkgs/mockito/example/iss/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/example/iss/README.md b/pkgs/mockito/example/iss/README.md index 9e0acde4e..68d2f1863 100644 --- a/pkgs/mockito/example/iss/README.md +++ b/pkgs/mockito/example/iss/README.md @@ -91,10 +91,10 @@ This test runs asynchronously. ## Files -* [iss.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss.dart) +* [iss.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/example/iss/iss.dart) : International space station API library -* [iss_test.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss_test.dart) +* [iss_test.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/example/iss/iss_test.dart) : Unit tests for iss.dart library From 341580bf3f783bd145d2d3e6cb04af0b5cd0f995 Mon Sep 17 00:00:00 2001 From: nbosch Date: Thu, 26 May 2022 21:03:11 -0400 Subject: [PATCH 431/607] Annotate the mock library import in examples Show only the new single convention of always annotating the directive that is guaranteed to exist in every library. The annotation discovery still allows using it on any top level definition or directive, but the docs now only reference the import to push for consistency. PiperOrigin-RevId: 451283797 --- pkgs/mockito/NULL_SAFETY_README.md | 21 ++++++++++++--------- pkgs/mockito/README.md | 11 ++++++----- pkgs/mockito/example/iss/README.md | 4 ++-- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index a91fa3ec0..0fed98cbc 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -73,9 +73,8 @@ In order to generate a mock for the HttpServer class, we edit `http_server_test.dart`: 1. import mockito's annotations library, -2. annotate a top-level library member (like an import, or the main function) - with `@GenerateMocks`, -3. import the generated mocks library, +2. import the generated mocks library, +3. annotate the import with `@GenerateMocks`, 4. change `httpServer` from an HttpServer to the generated class, MockHttpServer. @@ -84,9 +83,10 @@ In order to generate a mock for the HttpServer class, we edit import 'package:mockito/annotations.dart'; import 'package:test/test.dart'; import 'http_server.dart'; -import 'http_server_test.mocks.dart'; @GenerateMocks([HttpServer]) +import 'http_server_test.mocks.dart'; + void main() { test('test', () { var httpServer = MockHttpServer(); @@ -161,11 +161,13 @@ If a class has a member with a type variable as a return type (for example, valid value. For example, given this class and test: ```dart +@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) +import 'foo_test.mocks.dart'; + abstract class Foo { T m(T a, int b); } -@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) void testFoo(Foo foo) { when(foo.m(7)).thenReturn(42); } @@ -199,6 +201,11 @@ return type as the member, and it must have the same positional and named parameters as the member, except that each parameter must be made nullable: ```dart +@GenerateMocks([], customMocks: [ + MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim}) +]) +import 'foo_test.mocks.dart'; + abstract class Foo { T m(T a, int b); } @@ -207,10 +214,6 @@ T mShim(T a, int? b) { if (a is int) return 1; throw 'unknown'; } - -@GenerateMocks([], customMocks: [ - MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim}) -]) ``` The fallback values will never be returned from a real member call; these are diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 013a8a81b..ccbdadbf7 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -21,6 +21,9 @@ Let's start with a Dart library, `cat.dart`: ```dart import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; + +// Annotation which generates the cat.mocks.dart library and the MockCat class. +@GenerateMocks([Cat]) import 'cat.mocks.dart'; // Real class @@ -34,17 +37,15 @@ class Cat { int lives = 9; } -// Annotation which generates the cat.mocks.dart library and the MockCat class. -@GenerateMocks([Cat]) void main() { // Create mock object. var cat = MockCat(); } ``` -By annotating a library element (such as a test file's `main` function, or a -class) with `@GenerateMocks`, you are directing Mockito's code generation to -write a mock class for each "real" class listed, in a new library. +By annotating the import of a `.mocks.dart` library with `@GenerateMocks`, you +are directing Mockito's code generation to write a mock class for each "real" +class listed, in a new library. The next step is to run `build_runner` in order to generate this new library: diff --git a/pkgs/mockito/example/iss/README.md b/pkgs/mockito/example/iss/README.md index 68d2f1863..9e0acde4e 100644 --- a/pkgs/mockito/example/iss/README.md +++ b/pkgs/mockito/example/iss/README.md @@ -91,10 +91,10 @@ This test runs asynchronously. ## Files -* [iss.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/example/iss/iss.dart) +* [iss.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss.dart) : International space station API library -* [iss_test.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/example/iss/iss_test.dart) +* [iss_test.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss_test.dart) : Unit tests for iss.dart library From 9b4dd126121b9ffbfcc8712bca5f84c90653bf10 Mon Sep 17 00:00:00 2001 From: davidmorgan Date: Fri, 3 Jun 2022 04:50:59 -0400 Subject: [PATCH 432/607] Mockito fix: allow unsupported methods and fallback methods that return functions. PiperOrigin-RevId: 452717516 --- pkgs/mockito/lib/src/builder.dart | 4 +++- pkgs/mockito/test/end2end/foo.dart | 1 + pkgs/mockito/test/end2end/generated_mocks_test.dart | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 14e157d37..0f5f5fb19 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -695,7 +695,9 @@ class _MockTargetGatherer { returnType.typeArguments, enclosingElement, isParameter: isParameter)); } else if (returnType is analyzer.FunctionType) { - errorMessages.addAll(_checkFunction(returnType, enclosingElement)); + errorMessages.addAll(_checkFunction(returnType, enclosingElement, + allowUnsupportedMember: allowUnsupportedMember, + hasDummyGenerator: hasDummyGenerator)); } else if (returnType is analyzer.TypeParameterType) { if (!isParameter && !allowUnsupportedMember && diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index b10a4479e..26893c711 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -28,5 +28,6 @@ abstract class Baz { T returnsTypeVariable(); T returnsBoundedTypeVariable(); T returnsTypeVariableFromTwo(); + S Function(S) returnsGenericFunction(); S get typeVariableField; } diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 29202e05a..c18e1add0 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -16,6 +16,8 @@ T returnsTypeVariableFromTwoShim() => [1, 1.5].whereType().first!; T typeVariableFieldShim() => throw UnsupportedError('typeVariableField cannot be used'); +T Function(T) returnsGenericFunctionShim() => (T _) => null as T; + @GenerateMocks([ Foo, FooSub, @@ -27,12 +29,14 @@ T typeVariableFieldShim() => #returnsTypeVariable, #returnsBoundedTypeVariable, #returnsTypeVariableFromTwo, + #returnsGenericFunction, #typeVariableField, }), MockSpec(as: #MockBazWithFallbackGenerators, fallbackGenerators: { #returnsTypeVariable: returnsTypeVariableShim, #returnsBoundedTypeVariable: returnsBoundedTypeVariableShim, #returnsTypeVariableFromTwo: returnsTypeVariableFromTwoShim, + #returnsGenericFunction: returnsGenericFunctionShim, #typeVariableField: typeVariableFieldShim, }), ]) From d153490708024da0243483225c1cdab2d2110151 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 6 Jun 2022 13:53:34 -0400 Subject: [PATCH 433/607] Import https://github.com/dart-lang/mockito/pull/537 Fix ISS example readme links. "Files" section in ISS example's README file points to an invalid (404) location. This PR fixes it by pointing to the correct URL of files. PiperOrigin-RevId: 453235525 --- pkgs/mockito/example/iss/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/example/iss/README.md b/pkgs/mockito/example/iss/README.md index 9e0acde4e..68d2f1863 100644 --- a/pkgs/mockito/example/iss/README.md +++ b/pkgs/mockito/example/iss/README.md @@ -91,10 +91,10 @@ This test runs asynchronously. ## Files -* [iss.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss.dart) +* [iss.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/example/iss/iss.dart) : International space station API library -* [iss_test.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/test/example/iss/iss_test.dart) +* [iss_test.dart](https://raw.githubusercontent.com/dart-lang/mockito/master/example/iss/iss_test.dart) : Unit tests for iss.dart library From eb7f2d2222f41bf26f67c62e6bd1e43d853a46c3 Mon Sep 17 00:00:00 2001 From: Naman Shergill <33877135+NamanShergill@users.noreply.github.com> Date: Sun, 10 Jul 2022 18:10:04 +0530 Subject: [PATCH 434/607] Minor grammar fix in the readme --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index ccbdadbf7..1124034fb 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -189,7 +189,7 @@ verify(cat.lives=9); If an argument other than an ArgMatcher (like [`any`], [`anyNamed`], [`argThat`], [`captureThat`], etc.) is passed to a mock method, then the [`equals`] matcher is used for argument matching. If you need more strict -matching consider use `argThat(identical(arg))`. +matching, consider using `argThat(identical(arg))`. However, note that `null` cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example: From 9dac73d0249c8f776980bb7277ca38e09b0dd9bf Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 15 Jun 2022 18:41:57 -0400 Subject: [PATCH 435/607] Update the example build_runner commands. The new command aligns with that mentioned in the Flutter docs: https://docs.flutter.dev/cookbook/testing/unit/mockingdart-lang/mockito#3-create-a-test-file-with-a-mock-httpclient Fixes https://github.com/dart-lang/mockito/issues/535 PiperOrigin-RevId: 455236959 --- pkgs/mockito/NULL_SAFETY_README.md | 4 +++- pkgs/mockito/README.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 0fed98cbc..99d4d031c 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -101,7 +101,9 @@ under `dev_dependencies`: something like `build_runner: ^1.10.0`. The final step is to run build_runner in order to generate the new library: ```shell -pub run build_runner build +flutter pub run build_runner build +# OR +dart run build_runner build ``` build_runner will generate the `http_server_test.mocks.dart` file which we diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 1124034fb..d15d159aa 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -50,6 +50,8 @@ class listed, in a new library. The next step is to run `build_runner` in order to generate this new library: ```shell +flutter pub run build_runner build +# OR dart run build_runner build ``` @@ -189,7 +191,7 @@ verify(cat.lives=9); If an argument other than an ArgMatcher (like [`any`], [`anyNamed`], [`argThat`], [`captureThat`], etc.) is passed to a mock method, then the [`equals`] matcher is used for argument matching. If you need more strict -matching, consider using `argThat(identical(arg))`. +matching consider use `argThat(identical(arg))`. However, note that `null` cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example: From 24f62d6e00aa8f0b7bb4188a03141b7842e153be Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 7 Jul 2022 13:17:45 -0400 Subject: [PATCH 436/607] Don't expect that `part` directive alway includes the referenced file. The file must also confirm that it is a part of this library. https://dart-review.googlesource.com/c/sdk/+/250680 starts this. There will be another CL to deprecate `LibraryElement.parts`, but in order to land this one, I need to fix unit tests for 3 packages. The rest of google3 it green. PiperOrigin-RevId: 459542853 --- pkgs/mockito/test/builder/auto_mocks_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 6e69bd395..3799e16ea 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -788,6 +788,7 @@ void main() { part 'part.dart'; ''', 'foo|test/part.dart': ''' + part of 'foo_test.dart'; @GenerateMocks([Foo]) void fooTests() {} ''' From 36cef0f435480b63bf9594b6b021ec4ac8447a35 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 7 Jul 2022 13:43:54 -0400 Subject: [PATCH 437/607] Avoid name conflicts with dart:core The primary mechanism here is a custom code_builder Allocator. Prefixing _all_ 'dart:core' elements would make the generated code quite unreadable. Code would look like: ```dart @_i3.override _i3.Future<_i3.bool> List(_i3.Map<_i3.String, _i3.int> p) { ... } ``` This PR instead gathers all members which would conflict with an element exported from 'dart:core', and only prefixes those elements. In this way, most code is unchanged. The only, very rare, cases of prefixing 'dart:core', are for members named things like 'List'. PiperOrigin-RevId: 459549619 --- pkgs/mockito/lib/src/builder.dart | 96 +++++++++++++++++-- .../mockito/test/builder/auto_mocks_test.dart | 56 +++++++++-- 2 files changed, 132 insertions(+), 20 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 0f5f5fb19..671362aa7 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -108,8 +108,11 @@ class MockBuilder implements Builder { b.body.addAll(mockLibraryInfo.mockClasses); }); - final emitter = DartEmitter.scoped( - orderDirectives: true, useNullSafetySyntax: sourceLibIsNonNullable); + final emitter = DartEmitter( + allocator: _AvoidConflictsAllocator( + coreConflicts: mockLibraryInfo.coreConflicts), + orderDirectives: true, + useNullSafetySyntax: sourceLibIsNonNullable); final rawOutput = mockLibrary.accept(emitter).toString(); // The source lib may be pre-null-safety because of an explicit opt-out // (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To @@ -816,13 +819,22 @@ class _MockLibraryInfo { /// Asset-resolving while building the mock library. final Map assetUris; + final LibraryElement? dartCoreLibrary; + + /// Names of overridden members which conflict with elements exported from + /// 'dart:core'. + /// + /// Each of these must be imported with a prefix to avoid the conflict. + final coreConflicts = {}; + /// Build mock classes for [mockTargets]. _MockLibraryInfo( Iterable<_MockTarget> mockTargets, { required this.assetUris, required LibraryElement entryLib, required InheritanceManager3 inheritanceManager, - }) { + }) : dartCoreLibrary = entryLib.importedLibraries + .firstWhereOrNull((library) => library.isDartCore) { for (final mockTarget in mockTargets) { final fallbackGenerators = mockTarget.fallbackGenerators; mockClasses.add(_MockClassInfo( @@ -1013,11 +1025,18 @@ class _MockClassInfo { if (typeSystem._returnTypeIsNonNullable(method) || typeSystem._hasNonNullableParameter(method) || _needsOverrideForVoidStub(method)) { + _checkForConflictWithCore(method.name); yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } } } + void _checkForConflictWithCore(String name) { + if (mockLibraryInfo.dartCoreLibrary?.exportNamespace.get(name) != null) { + mockLibraryInfo.coreConflicts.add(name); + } + } + /// The default behavior of mocks is to return null for unstubbed methods. To /// use the new behavior of throwing an error, we must explicitly call /// `throwOnMissingStub`. @@ -1036,7 +1055,7 @@ class _MockClassInfo { if (method.isOperator) name = 'operator$name'; builder ..name = name - ..annotations.addAll([refer('override')]) + ..annotations.add(referImported('override', 'dart:core')) ..returns = _typeReference(method.returnType) ..types.addAll(method.typeParameters.map(_typeParameterReference)); @@ -1105,7 +1124,8 @@ class _MockClassInfo { return; } - final invocation = refer('Invocation').property('method').call([ + final invocation = + referImported('Invocation', 'dart:core').property('method').call([ refer('#${method.displayName}'), literalList(invocationPositionalArgs), if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs), @@ -1202,6 +1222,7 @@ class _MockClassInfo { return TypeReference((b) { b ..symbol = 'Stream' + ..url = 'dart:async' ..types.add(elementType); }).property('empty').call([]); } else if (type.isDartCoreString) { @@ -1224,7 +1245,9 @@ class _MockClassInfo { /// Returns a reference to [Future], optionally with a type argument for the /// value of the Future. TypeReference _futureReference([Reference? valueType]) => TypeReference((b) { - b.symbol = 'Future'; + b + ..symbol = 'Future' + ..url = 'dart:async'; if (valueType != null) { b.types.add(valueType); } @@ -1513,7 +1536,7 @@ class _MockClassInfo { MethodBuilder builder, PropertyAccessorElement getter) { builder ..name = getter.displayName - ..annotations.addAll([refer('override')]) + ..annotations.add(referImported('override', 'dart:core')) ..type = MethodType.getter ..returns = _typeReference(getter.returnType); @@ -1540,7 +1563,8 @@ class _MockClassInfo { return; } - final invocation = refer('Invocation').property('getter').call([ + final invocation = + referImported('Invocation', 'dart:core').property('getter').call([ refer('#${getter.displayName}'), ]); final namedArgs = { @@ -1566,7 +1590,7 @@ class _MockClassInfo { MethodBuilder builder, PropertyAccessorElement setter) { builder ..name = setter.displayName - ..annotations.addAll([refer('override')]) + ..annotations.add(referImported('override', 'dart:core')) ..type = MethodType.setter; assert(setter.parameters.length == 1); @@ -1576,7 +1600,8 @@ class _MockClassInfo { ..type = _typeReference(parameter.type, forceNullable: true))); final invocationPositionalArg = refer(parameter.displayName); - final invocation = refer('Invocation').property('setter').call([ + final invocation = + referImported('Invocation', 'dart:core').property('setter').call([ refer('#${setter.displayName}'), invocationPositionalArg, ]); @@ -1714,6 +1739,57 @@ class InvalidMockitoAnnotationException implements Exception { String toString() => 'Invalid @GenerateMocks annotation: $message'; } +/// An [Allocator] that avoids conflicts with elements exported from +/// 'dart:core'. +/// +/// This does not prefix _all_ 'dart:core' elements; instead it takes a set of +/// names which conflict, and if that is non-empty, generates two import +/// directives for 'dart:core': +/// +/// * an unprefixed import with conflicting names enumerated in the 'hide' +/// combinator, +/// * a prefixed import which will be the way to reference the conflicting +/// names. +class _AvoidConflictsAllocator implements Allocator { + final _imports = {}; + var _keys = 1; + + /// The collection of names of elements which conflict with elements exported + /// from 'dart:core'. + final Set _coreConflicts; + + _AvoidConflictsAllocator({required Set coreConflicts}) + : _coreConflicts = coreConflicts; + + @override + String allocate(Reference reference) { + final symbol = reference.symbol!; + final url = reference.url; + if (url == null) { + return symbol; + } + if (url == 'dart:core' && !_coreConflicts.contains(symbol)) { + return symbol; + } + return '_i${_imports.putIfAbsent(url, _nextKey)}.$symbol'; + } + + int _nextKey() => _keys++; + + @override + Iterable get imports => [ + if (_imports.containsKey('dart:core')) + // 'dart:core' is explicitly imported to avoid a conflict between an + // overriding member and an element exported by 'dart:core'. We must + // add another, unprefixed, import for 'dart:core' which hides the + // conflicting names. + Directive.import('dart:core', hide: _coreConflicts.toList()), + ..._imports.keys.map( + (u) => Directive.import(u, as: '_i${_imports[u]}'), + ), + ]; +} + /// A [MockBuilder] instance for use by `build.yaml`. Builder buildMocks(BuilderOptions options) => MockBuilder(); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 3799e16ea..866783455 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -534,8 +534,8 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value()) as _i3.Future); ''')), ); }); @@ -549,7 +549,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Stream m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Stream.empty()) as _i3.Stream); + returnValue: _i3.Stream.empty()) as _i3.Stream); ''')), ); }); @@ -715,6 +715,25 @@ void main() { ); }); + test('overrides methods, adjusting imports for names that conflict with core', + () async { + await expectSingleNonNullableOutput( + dedent(''' + import 'dart:core' as core; + class Foo { + void List(core.int a) {} + core.List m() => []; + } + '''), + _containsAllOf( + ' void List(int? a) => super.noSuchMethod(Invocation.method(#List, [a]),\n', + dedent2(''' + _i3.List m() => + (super.noSuchMethod(Invocation.method(#m, []), returnValue: []) + as _i3.List);'''), + )); + }); + test( 'overrides `toString` with a correct signature if the class overrides ' 'it', () async { @@ -1257,6 +1276,23 @@ void main() { expect(mocksContent, contains('m(_i3.Bar? a)')); }); + test('imports dart:core with a prefix when members conflict with dart:core', + () async { + await expectSingleNonNullableOutput( + dedent(''' + import 'dart:core' as core; + class Foo { + void List(int a) {} + core.List m() => []; + } + '''), + _containsAllOf( + "import 'dart:core' hide List;", + "import 'dart:core' as _i3;", + ), + ); + }); + test('prefixes parameter type on generic function-typed parameter', () async { await expectSingleNonNullableOutput( dedent(r''' @@ -1492,7 +1528,7 @@ void main() { '''), _containsAllOf( '_i3.FutureOr m() => (super.noSuchMethod(Invocation.method(#m, []),', - ' returnValue: Future.value(null)) as _i3.FutureOr);'), + ' returnValue: _i3.Future.value(null)) as _i3.FutureOr);'), ); }); @@ -2148,7 +2184,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future.value(false)) as _i3.Future); + returnValue: _i3.Future.value(false)) as _i3.Future); ''')), ); }); @@ -2164,7 +2200,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future.value(() {})) as _i3.Future); + returnValue: _i3.Future.value(() {})) as _i3.Future); ''')), ); }); @@ -2180,7 +2216,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Future<_i2.Bar?> m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future<_i2.Bar?>.value()) as _i3.Future<_i2.Bar?>); + returnValue: _i3.Future<_i2.Bar?>.value()) as _i3.Future<_i2.Bar?>); ''')), ); }); @@ -2198,7 +2234,7 @@ void main() { _containsAllOf(dedent2(''' _i3.Future<_i4.Uint8List> m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future<_i4.Uint8List>.value(_i4.Uint8List(0))) + returnValue: _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0))) as _i3.Future<_i4.Uint8List>); ''')), ); @@ -2216,7 +2252,7 @@ void main() { _containsAllOf(dedent2(''' _i3.Future> m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Future>.value([])) + returnValue: _i3.Future>.value([])) as _i3.Future>); ''')), ); @@ -2231,7 +2267,7 @@ void main() { '''), _containsAllOf(dedent2(''' _i3.Stream m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: Stream.empty()) as _i3.Stream); + returnValue: _i3.Stream.empty()) as _i3.Stream); ''')), ); }); From e54927385024ac33b117c69577a1cfb88ebf77d6 Mon Sep 17 00:00:00 2001 From: srawlins Date: Fri, 8 Jul 2022 16:36:11 -0400 Subject: [PATCH 438/607] When reporting an error with an unrevivable parameter, refer to the class-to-mock. Fixes https://github.com/dart-lang/mockito/issues/540 PiperOrigin-RevId: 459824583 --- pkgs/mockito/lib/src/builder.dart | 6 ++--- .../mockito/test/builder/auto_mocks_test.dart | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 671362aa7..5f061b950 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1367,11 +1367,11 @@ class _MockClassInfo { .code; } on _ReviveException catch (e) { final method = parameter.enclosingElement!; - final clazz = method.enclosingElement!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' - "'${clazz.displayName}.${method.displayName}'; parameter " - "'${parameter.displayName}' causes a problem: ${e.message}"); + "'${mockTarget.classElement.displayName}.${method.displayName}'; " + "parameter '${parameter.displayName}' causes a problem: " + '${e.message}'); } } }); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 866783455..c0d6e941b 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -485,6 +485,31 @@ void main() { ); }); + test( + 'throws when given a parameter default value using a private type, and ' + 'refers to the class-to-mock', () { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': dedent(r''' + class FooBase { + void m([Bar a = const _Bar()]) {} + } + class Foo extends FooBase {} + class Bar {} + class _Bar implements Bar { + const _Bar(); + } + '''), + }, + message: contains( + "Mockito cannot generate a valid override for method 'Foo.m'; " + "parameter 'a' causes a problem: default value has a private type: " + 'asset:foo/lib/foo.dart#_Bar'), + ); + }); + test( 'throws when given a parameter default value using a private constructor', () { From fb7edf4a272e64d332feeca019a31571d15eec72 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 11 Jul 2022 14:57:13 -0400 Subject: [PATCH 439/607] Minor grammar fix in the readme Import https://github.com/dart-lang/mockito/pull/544 PiperOrigin-RevId: 460267361 --- pkgs/mockito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index d15d159aa..99640c6b8 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -191,7 +191,7 @@ verify(cat.lives=9); If an argument other than an ArgMatcher (like [`any`], [`anyNamed`], [`argThat`], [`captureThat`], etc.) is passed to a mock method, then the [`equals`] matcher is used for argument matching. If you need more strict -matching consider use `argThat(identical(arg))`. +matching, consider using `argThat(identical(arg))`. However, note that `null` cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example: From 2f7617bc7b2c965a0900aeefe17c63a503a8436f Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Sun, 17 Jul 2022 23:26:42 -0400 Subject: [PATCH 440/607] Stop using deprecated DartType.displayName We would like to remove it: https://dart-review.googlesource.com/c/sdk/+/251783 PiperOrigin-RevId: 461522931 --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 5f061b950..d958d2eb3 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1231,7 +1231,7 @@ class _MockClassInfo { // These "List" types from dart:typed_data are "non-subtypeable", but they // have predicatble constructors; each has an unnamed constructor which // takes a single int argument. - return referImported(type.displayName, 'dart:typed_data') + return referImported(type.element.name, 'dart:typed_data') .call([literalNum(0)]); // TODO(srawlins): Do other types from typed_data have a "non-subtypeable" // restriction as well? From 3145e114e2b78888227ed159bd2603d02d5d0d1a Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 19 Jul 2022 14:31:58 -0400 Subject: [PATCH 441/607] Allow mixing types into generated mocks. This is primarily here to support private field promotion: https://github.com/dart-lang/language/issues/2020 Also discussion at https://github.com/dart-lang/language/issues/2275 The broad stroke is that users may need to start declaring little mixins next to their base classes with implementations for this or that private API which is (intentionally or not) accessed against a mock instance during a test. Fixes https://github.com/dart-lang/mockito/issues/342 PiperOrigin-RevId: 461933542 --- pkgs/mockito/lib/annotations.dart | 6 +- pkgs/mockito/lib/src/builder.dart | 41 +++++- .../test/builder/custom_mocks_test.dart | 135 +++++++++++++++++- pkgs/mockito/test/end2end/foo.dart | 15 ++ .../test/end2end/generated_mocks_test.dart | 42 ++++-- 5 files changed, 220 insertions(+), 19 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index b2c1504f7..32281c220 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -69,6 +69,8 @@ class GenerateMocks { class MockSpec { final Symbol? mockName; + final List mixins; + final bool returnNullOnMissingStub; final Set unsupportedMembers; @@ -103,8 +105,10 @@ class MockSpec { /// as a legal return value. const MockSpec({ Symbol? as, + List mixingIn = const [], this.returnNullOnMissingStub = false, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, - }) : mockName = as; + }) : mockName = as, + mixins = mixingIn; } diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index d958d2eb3..95c7d0675 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -153,13 +153,16 @@ $rawOutput .whereType() .forEach(addTypesFrom); // For a type like `Foo extends Bar`, add the `Baz`. - for (var supertype in type.allSupertypes) { + for (final supertype in type.allSupertypes) { addTypesFrom(supertype); } } - for (var mockTarget in mockTargets) { + for (final mockTarget in mockTargets) { addTypesFrom(mockTarget.classType); + for (final mixinTarget in mockTarget.mixins) { + addTypesFrom(mixinTarget); + } } final typeUris = {}; @@ -359,6 +362,8 @@ class _MockTarget { /// The desired name of the mock class. final String mockName; + final List mixins; + final bool returnNullOnMissingStub; final Set unsupportedMembers; @@ -368,6 +373,7 @@ class _MockTarget { _MockTarget( this.classType, this.mockName, { + required this.mixins, required this.returnNullOnMissingStub, required this.unsupportedMembers, required this.fallbackGenerators, @@ -453,6 +459,7 @@ class _MockTargetGatherer { mockTargets.add(_MockTarget( declarationType, mockName, + mixins: [], returnNullOnMissingStub: false, unsupportedMembers: {}, fallbackGenerators: {}, @@ -480,6 +487,27 @@ class _MockTargetGatherer { } final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? 'Mock${type.element.name}'; + final mixins = []; + for (final m in mockSpec.getField('mixins')!.toListValue()!) { + final typeToMixin = m.toTypeValue(); + if (typeToMixin == null) { + throw InvalidMockitoAnnotationException( + 'The "mixingIn" argument includes a non-type: $m'); + } + if (typeToMixin.isDynamic) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mix `dynamic` into a mock class'); + } + final mixinInterfaceType = + _determineDartType(typeToMixin, entryLib.typeProvider); + if (!mixinInterfaceType.interfaces.contains(type)) { + throw InvalidMockitoAnnotationException('The "mixingIn" type, ' + '${typeToMixin.getDisplayString(withNullability: false)}, must ' + 'implement the class to mock, ${typeToMock.getDisplayString(withNullability: false)}'); + } + mixins.add(mixinInterfaceType); + } + final returnNullOnMissingStub = mockSpec.getField('returnNullOnMissingStub')!.toBoolValue()!; final unsupportedMembers = { @@ -492,6 +520,7 @@ class _MockTargetGatherer { mockTargets.add(_MockTarget( type, mockName, + mixins: mixins, returnNullOnMissingStub: returnNullOnMissingStub, unsupportedMembers: unsupportedMembers, fallbackGenerators: @@ -930,6 +959,14 @@ class _MockClassInfo { typeArguments.add(refer(typeParameter.name)); } } + for (final mixin in mockTarget.mixins) { + cBuilder.mixins.add(TypeReference((b) { + b + ..symbol = mixin.name + ..url = _typeImport(mixin.element) + ..types.addAll(mixin.typeArguments.map(_typeReference)); + })); + } cBuilder.implements.add(TypeReference((b) { b ..symbol = classToMock.name diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 2d3e7c16e..23210f4b9 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -35,6 +35,8 @@ class GenerateMocks { class MockSpec { final Symbol mockName; + final List mixins; + final bool returnNullOnMissingStub; final Set unsupportedMembers; @@ -43,10 +45,12 @@ class MockSpec { const MockSpec({ Symbol? as, + List mixingIn = const [], this.returnNullOnMissingStub = false, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, - }) : mockName = as; + }) : mockName = as, + mixins = mixingIn; } ''' }; @@ -95,7 +99,7 @@ void main() { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 12)) + languageVersion: LanguageVersion(2, 15)) ]); await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); @@ -310,6 +314,74 @@ void main() { contains('class MockBFoo extends _i1.Mock implements _i3.Foo')); }); + test('generates a mock class with a declared mixin', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + class Foo {} + + class FooMixin implements Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec(mixingIn: [FooMixin])]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock with _i2.FooMixin implements _i2.Foo {'), + ); + }); + + test('generates a mock class with multiple declared mixins', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + class Foo {} + + class Mixin1 implements Foo {} + class Mixin2 implements Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec(mixingIn: [Mixin1, Mixin2])]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock with _i2.Mixin1, _i2.Mixin2 implements _i2.Foo {'), + ); + }); + + test('generates a mock class with a declared mixin with a type arg', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + class Foo {} + + class FooMixin implements Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [MockSpec>(mixingIn: [FooMixin])]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock with _i2.FooMixin implements _i2.Foo {'), + ); + }); + test( 'generates a mock class which uses the old behavior of returning null on ' 'missing stubs', () async { @@ -804,6 +876,65 @@ void main() { ); }); + test('throws when MockSpec mixes in dynamic', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + import 'package:foo/foo.dart'; + @GenerateMocks([], customMocks: [MockSpec(mixingIn: [dynamic])]) + void main() {} + '''), + }, + message: contains('Mockito cannot mix `dynamic` into a mock class'), + ); + }); + + test('throws when MockSpec mixes in a private type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + import 'package:foo/foo.dart'; + @GenerateMocks([], customMocks: [MockSpec(mixingIn: [_FooMixin])]) + void main() {} + + mixin _FooMixin implements Foo {} + '''), + }, + message: contains('Mockito cannot mock a private type: _FooMixin'), + ); + }); + + test('throws when MockSpec mixes in a non-mixinable type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + import 'package:foo/foo.dart'; + @GenerateMocks([], customMocks: [MockSpec(mixingIn: [FooMixin])]) + void main() {} + + mixin FooMixin {} + '''), + }, + message: contains( + 'The "mixingIn" type, FooMixin, must implement the class to mock, Foo'), + ); + }); + test('given a pre-non-nullable library, does not override any members', () async { await testPreNonNullable( diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 26893c711..f3f9f13a6 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -31,3 +31,18 @@ abstract class Baz { S Function(S) returnsGenericFunction(); S get typeVariableField; } + +class HasPrivate { + Object? _p; + + Object? get p => _p; +} + +void setPrivate(HasPrivate hasPrivate) { + hasPrivate._p = 7; +} + +mixin HasPrivateMixin implements HasPrivate { + @override + Object? _p; +} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index c18e1add0..502c2ff48 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -25,20 +25,27 @@ T Function(T) returnsGenericFunctionShim() => (T _) => null as T; ], customMocks: [ MockSpec(as: #MockFooRelaxed, returnNullOnMissingStub: true), MockSpec(as: #MockBarRelaxed, returnNullOnMissingStub: true), - MockSpec(as: #MockBazWithUnsupportedMembers, unsupportedMembers: { - #returnsTypeVariable, - #returnsBoundedTypeVariable, - #returnsTypeVariableFromTwo, - #returnsGenericFunction, - #typeVariableField, - }), - MockSpec(as: #MockBazWithFallbackGenerators, fallbackGenerators: { - #returnsTypeVariable: returnsTypeVariableShim, - #returnsBoundedTypeVariable: returnsBoundedTypeVariableShim, - #returnsTypeVariableFromTwo: returnsTypeVariableFromTwoShim, - #returnsGenericFunction: returnsGenericFunctionShim, - #typeVariableField: typeVariableFieldShim, - }), + MockSpec( + as: #MockBazWithUnsupportedMembers, + unsupportedMembers: { + #returnsTypeVariable, + #returnsBoundedTypeVariable, + #returnsTypeVariableFromTwo, + #returnsGenericFunction, + #typeVariableField, + }, + ), + MockSpec( + as: #MockBazWithFallbackGenerators, + fallbackGenerators: { + #returnsTypeVariable: returnsTypeVariableShim, + #returnsBoundedTypeVariable: returnsBoundedTypeVariableShim, + #returnsTypeVariableFromTwo: returnsTypeVariableFromTwoShim, + #returnsGenericFunction: returnsGenericFunctionShim, + #typeVariableField: typeVariableFieldShim, + }, + ), + MockSpec(mixingIn: [HasPrivateMixin]), ]) void main() { group('for a generated mock,', () { @@ -257,4 +264,11 @@ void main() { when(foo.methodWithBarArg(bar)).thenReturn('mocked result'); expect(foo.methodWithBarArg(bar), equals('mocked result')); }); + + test('a generated mock with a mixed in type can use mixed in members', () { + var hasPrivate = MockHasPrivate(); + // This should not throw, when `setPrivate` accesses a private member on + // `hasPrivate`. + setPrivate(hasPrivate); + }); } From 3d1a6cc337775f824e4630a2b42dee59e927a054 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 21 Jul 2022 09:35:54 -0400 Subject: [PATCH 442/607] Introduce new `@GenerateNiceMocks` annotation and `onMissingStub` option `onMissingStub` part cloned from cl/455672146 (edited to reflect parameter rename): Original change by srawlins@srawlins:mockito-go:43847:citc on 2022/06/17 12:27:38. Introduce a new "on missing stub behavior" to return legal default values. Introduce a new `MockSpec` parameter, `onMissingStub`, which allows specifying three actions to take when a real call is made to a mock method with no matching stub. The two existing behaviors are the default behavior of throwing an exception, and the legacy behavior of returning `null`. A new behavior is also introduced: returning a legal default value. With this behavior, legal default values are returned for any given type. Some additions on top of that: 1. New @GenerateNiceMocks annotation. It generates mocks with the new missing stub behavior by default. It also only accepts a list of `MockSpec`s, so there is no distinction between auto/custom. 2. In the `_dummyValue()` function, add a shortcut to produce `null` everywhere where it fits. 3. Add `ignore_for_file: subtype_of_sealed_class` to the generated code, to make it possible to mock classes with `@sealed` annotation. PiperOrigin-RevId: 462370781 --- pkgs/mockito/CHANGELOG.md | 13 ++ pkgs/mockito/lib/annotations.dart | 71 +++++- pkgs/mockito/lib/src/builder.dart | 206 ++++++++++++------ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 114 ++++++++++ .../test/end2end/generated_mocks_test.dart | 39 ++++ 7 files changed, 375 insertions(+), 72 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 8c4c089d0..12de16959 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,16 @@ +## 5.3.0-dev + +* Introduce a new `MockSpec` parameter, `onMissingStub`, which allows + specifying three actions to take when a real call is made to a mock method + with no matching stub. The two existing behaviors are the default + behavior of throwing an exception, and the legacy behavior of returning + `null`. A new behavior is also introduced: returning a legal default value. + With this behavior, legal default values are returned for any given type. +* Deprecate the `MockSpec` `returnNullOnMissingStub` parameter in favor of the + new `onMissingStub` parameter. +* Introduce a new `@GenerateNiceMocks` annotation, that uses the new + "return a legal value" behavior for missing stubs. + ## 5.2.0 * Fix generation of methods with return type of `FutureOr` for generic, diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 32281c220..9ed32fcc8 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -39,6 +39,34 @@ class GenerateMocks { const GenerateMocks(this.classes, {this.customMocks = const []}); } +/// An annotation to direct Mockito to generate mock classes. +/// +/// During [code generation][NULL_SAFETY_README], Mockito will generate a +/// `Mock{Type} extends Mock` class for each class to be mocked, in +/// `{name}.mocks.dart`, where `{name}` is the basename of the file in which +/// `@GenerateNiceMocks` is used. +/// +/// For example, if `@GenerateNiceMocks([MockSpec()])` is found at +/// the top-level of a Dart library, `foo_test.dart`, then Mockito will +/// generate `class MockFoo extends Mock implements Foo` in a new library, +/// `foo_test.mocks.dart`. +/// +/// If the class-to-mock is generic, then the mock will be identically generic. +/// For example, given the class `class Foo`, Mockito will generate +/// `class MockFoo extends Mock implements Foo`. +/// +/// `@GenerateNiceMocks` is different from `@GenerateMocks` in two ways: +/// - only `MockSpec`s are allowed in the argument list +/// - generated mocks won't throw on unstubbed method calls by default, +/// instead some value appropriate for the target type will be +/// returned. +/// +/// [NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md +class GenerateNiceMocks { + final List mocks; + const GenerateNiceMocks(this.mocks); +} + /// A specification of how to mock a specific class. /// /// The type argument `T` is the class-to-mock. If this class is generic, and no @@ -71,8 +99,12 @@ class MockSpec { final List mixins; + @Deprecated('Specify "missing stub" behavior with the [onMissingStub] ' + 'parameter.') final bool returnNullOnMissingStub; + final OnMissingStub? onMissingStub; + final Set unsupportedMembers; final Map fallbackGenerators; @@ -81,9 +113,15 @@ class MockSpec { /// /// Specify a custom name with the [as] parameter. /// - /// If [returnNullOnMissingStub] is true, a real call to a mock method will - /// return `null` when no stub is found. This may result in a runtime error, - /// if the return type of the method is non-nullable. + /// If [onMissingStub] is specified as [OnMissingStub.returnNull], + /// (or if the deprecated parameter [returnNullOnMissingStub] is `true`), then + /// a real call to a mock method (or getter) will return `null` when no stub + /// is found. This may result in a runtime error, if the return type of the + /// method (or the getter) is non-nullable. + /// + /// If [onMissingStub] is specified as + /// [OnMissingStub.returnDefault], a real call to a mock method (or + /// getter) will return a legal value when no stub is found. /// /// If the class-to-mock has a member with a non-nullable unknown return type /// (such as a type variable, `T`), then mockito cannot generate a valid @@ -106,9 +144,34 @@ class MockSpec { const MockSpec({ Symbol? as, List mixingIn = const [], - this.returnNullOnMissingStub = false, + @Deprecated('Specify "missing stub" behavior with the ' + '[onMissingStub] parameter.') + this.returnNullOnMissingStub = false, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, + this.onMissingStub, }) : mockName = as, mixins = mixingIn; } + +/// Values indicating the action to perform when a real call is made to a mock +/// method (or getter) when no stub is found. +enum OnMissingStub { + /// An exception should be thrown. + throwException, + + /// A `null` value should be returned. + /// + /// This is considered legacy behavior, as it may result in a runtime error, + /// if the return type of the method (or the getter) is non-nullable. + @Deprecated( + 'This is legacy behavior, it may result in runtime errors. Consider using returnDefault instead') + returnNull, + + /// A legal default value should be returned. + /// + /// For basic known types, like `int` and `Future`, a simple value is + /// returned (like `0` and `Future.value('')`). For unknown user types, an + /// instance of a fake implementation is returned. + returnDefault; +} diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 95c7d0675..3ed30ae1f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -38,6 +38,7 @@ import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart' hide refer; import 'package:collection/collection.dart'; import 'package:dart_style/dart_style.dart'; +import 'package:mockito/annotations.dart'; import 'package:mockito/src/version.dart'; import 'package:path/path.dart' as p; import 'package:source_gen/source_gen.dart'; @@ -103,7 +104,9 @@ class MockBuilder implements Builder { // The code_builder `asA` API unconditionally adds defensive parentheses. b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n')); // The generator appends a suffix to fake classes - b.body.add(Code('// ignore_for_file: camel_case_types\n\n')); + b.body.add(Code('// ignore_for_file: camel_case_types\n')); + // The generator has to occasionally implement sealed classes + b.body.add(Code('// ignore_for_file: subtype_of_sealed_class\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); @@ -364,7 +367,7 @@ class _MockTarget { final List mixins; - final bool returnNullOnMissingStub; + final OnMissingStub onMissingStub; final Set unsupportedMembers; @@ -374,7 +377,7 @@ class _MockTarget { this.classType, this.mockName, { required this.mixins, - required this.returnNullOnMissingStub, + required this.onMissingStub, required this.unsupportedMembers, required this.fallbackGenerators, }); @@ -417,9 +420,15 @@ class _MockTargetGatherer { for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; final annotationClass = annotation.element!.enclosingElement!.name; - if (annotationClass == 'GenerateMocks') { - mockTargets - .addAll(_mockTargetsFromGenerateMocks(annotation, entryLib)); + switch (annotationClass) { + case 'GenerateMocks': + mockTargets + .addAll(_mockTargetsFromGenerateMocks(annotation, entryLib)); + break; + case 'GenerateNiceMocks': + mockTargets.addAll( + _mockTargetsFromGenerateNiceMocks(annotation, entryLib)); + break; } } } @@ -460,75 +469,129 @@ class _MockTargetGatherer { declarationType, mockName, mixins: [], - returnNullOnMissingStub: false, + onMissingStub: OnMissingStub.throwException, unsupportedMembers: {}, fallbackGenerators: {}, )); } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { - for (var mockSpec in customMocksField.toListValue()!) { - final mockSpecType = mockSpec.type as analyzer.InterfaceType; - assert(mockSpecType.typeArguments.length == 1); - final typeToMock = mockSpecType.typeArguments.single; - if (typeToMock.isDynamic) { - throw InvalidMockitoAnnotationException( - 'Mockito cannot mock `dynamic`; be sure to declare type ' - 'arguments on MockSpec(), in @GenerateMocks.'); - } - var type = _determineDartType(typeToMock, entryLib.typeProvider); - - if (!type.hasExplicitTypeArguments) { - // We assume the type was given without explicit type arguments. In - // this case the type argument(s) on `type` have been instantiated to - // bounds. Switch to the declaration, which will be an uninstantiated - // type. - type = (type.element.declaration as ClassElement).thisType; + mockTargets.addAll(customMocksField + .toListValue()! + .map((mockSpec) => _mockTargetFromMockSpec(mockSpec, entryLib))); + } + return mockTargets; + } + + static _MockTarget _mockTargetFromMockSpec( + DartObject mockSpec, LibraryElement entryLib, + {bool nice = false}) { + final mockSpecType = mockSpec.type as analyzer.InterfaceType; + assert(mockSpecType.typeArguments.length == 1); + final typeToMock = mockSpecType.typeArguments.single; + if (typeToMock.isDynamic) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock `dynamic`; be sure to declare type ' + 'arguments on MockSpec(), in @GenerateMocks.'); + } + var type = _determineDartType(typeToMock, entryLib.typeProvider); + + if (!type.hasExplicitTypeArguments) { + // We assume the type was given without explicit type arguments. In + // this case the type argument(s) on `type` have been instantiated to + // bounds. Switch to the declaration, which will be an uninstantiated + // type. + type = (type.element.declaration as ClassElement).thisType; + } + final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? + 'Mock${type.element.name}'; + final mixins = []; + for (final m in mockSpec.getField('mixins')!.toListValue()!) { + final typeToMixin = m.toTypeValue(); + if (typeToMixin == null) { + throw InvalidMockitoAnnotationException( + 'The "mixingIn" argument includes a non-type: $m'); + } + if (typeToMixin.isDynamic) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mix `dynamic` into a mock class'); + } + final mixinInterfaceType = + _determineDartType(typeToMixin, entryLib.typeProvider); + if (!mixinInterfaceType.interfaces.contains(type)) { + throw InvalidMockitoAnnotationException('The "mixingIn" type, ' + '${typeToMixin.getDisplayString(withNullability: false)}, must ' + 'implement the class to mock, ${typeToMock.getDisplayString(withNullability: false)}'); + } + mixins.add(mixinInterfaceType); + } + + final returnNullOnMissingStub = + mockSpec.getField('returnNullOnMissingStub')!.toBoolValue()!; + final onMissingStubValue = mockSpec.getField('onMissingStub')!; + final OnMissingStub onMissingStub; + if (nice) { + // The new @GenerateNiceMocks API. Don't allow `returnNullOnMissingStub`. + if (returnNullOnMissingStub) { + throw ArgumentError("'returnNullOnMissingStub' is not supported with " + '@GenerateNiceMocks'); + } + if (onMissingStubValue.isNull) { + onMissingStub = OnMissingStub.returnDefault; + } else { + final onMissingStubIndex = + onMissingStubValue.getField('index')!.toIntValue()!; + onMissingStub = OnMissingStub.values[onMissingStubIndex]; + if (onMissingStub == OnMissingStub.returnNull) { + throw ArgumentError( + "'OnMissingStub.returnNull' is not supported with " + '@GenerateNiceMocks'); } - final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? - 'Mock${type.element.name}'; - final mixins = []; - for (final m in mockSpec.getField('mixins')!.toListValue()!) { - final typeToMixin = m.toTypeValue(); - if (typeToMixin == null) { - throw InvalidMockitoAnnotationException( - 'The "mixingIn" argument includes a non-type: $m'); - } - if (typeToMixin.isDynamic) { - throw InvalidMockitoAnnotationException( - 'Mockito cannot mix `dynamic` into a mock class'); - } - final mixinInterfaceType = - _determineDartType(typeToMixin, entryLib.typeProvider); - if (!mixinInterfaceType.interfaces.contains(type)) { - throw InvalidMockitoAnnotationException('The "mixingIn" type, ' - '${typeToMixin.getDisplayString(withNullability: false)}, must ' - 'implement the class to mock, ${typeToMock.getDisplayString(withNullability: false)}'); - } - mixins.add(mixinInterfaceType); + } + } else { + if (onMissingStubValue.isNull) { + // No value was given for `onMissingStub`. But the behavior may + // be specified by `returnNullOnMissingStub`. + onMissingStub = returnNullOnMissingStub + ? OnMissingStub.returnNull + : OnMissingStub.throwException; + } else { + // A value was given for `onMissingStub`. + if (returnNullOnMissingStub) { + throw ArgumentError("Cannot specify 'returnNullOnMissingStub' and a " + "'onMissingStub' value at the same time."); } - - final returnNullOnMissingStub = - mockSpec.getField('returnNullOnMissingStub')!.toBoolValue()!; - final unsupportedMembers = { - for (final m - in mockSpec.getField('unsupportedMembers')!.toSetValue()!) - m.toSymbolValue()!, - }; - final fallbackGeneratorObjects = - mockSpec.getField('fallbackGenerators')!.toMapValue()!; - mockTargets.add(_MockTarget( - type, - mockName, - mixins: mixins, - returnNullOnMissingStub: returnNullOnMissingStub, - unsupportedMembers: unsupportedMembers, - fallbackGenerators: - _extractFallbackGenerators(fallbackGeneratorObjects), - )); + final onMissingStubIndex = + onMissingStubValue.getField('index')!.toIntValue()!; + onMissingStub = OnMissingStub.values[onMissingStubIndex]; } } - return mockTargets; + final unsupportedMembers = { + for (final m in mockSpec.getField('unsupportedMembers')!.toSetValue()!) + m.toSymbolValue()!, + }; + final fallbackGeneratorObjects = + mockSpec.getField('fallbackGenerators')!.toMapValue()!; + return _MockTarget( + type, + mockName, + mixins: mixins, + onMissingStub: onMissingStub, + unsupportedMembers: unsupportedMembers, + fallbackGenerators: _extractFallbackGenerators(fallbackGeneratorObjects), + ); + } + + static Iterable<_MockTarget> _mockTargetsFromGenerateNiceMocks( + ElementAnnotation annotation, LibraryElement entryLib) { + final generateNiceMocksValue = annotation.computeConstantValue()!; + final mockSpecsField = generateNiceMocksValue.getField('mocks')!; + if (mockSpecsField.isNull) { + throw InvalidMockitoAnnotationException( + 'The GenerateNiceMocks "mockSpecs" argument is missing'); + } + return mockSpecsField.toListValue()!.map( + (mockSpec) => _mockTargetFromMockSpec(mockSpec, entryLib, nice: true)); } static Map _extractFallbackGenerators( @@ -973,7 +1036,7 @@ class _MockClassInfo { ..url = _typeImport(mockTarget.classElement) ..types.addAll(typeArguments); })); - if (!mockTarget.returnNullOnMissingStub) { + if (mockTarget.onMissingStub == OnMissingStub.throwException) { cBuilder.constructors.add(_constructorWithThrowOnMissingStub); } @@ -1174,6 +1237,10 @@ class _MockClassInfo { } else if (returnType.isFutureOfVoid) { returnValueForMissingStub = _futureReference(refer('void')).property('value').call([]); + } else if (mockTarget.onMissingStub == OnMissingStub.returnDefault) { + // Return a legal default value if no stub is found which matches a real + // call. + returnValueForMissingStub = _dummyValue(returnType); } final namedArgs = { if (fallbackGenerator != null) @@ -1211,6 +1278,11 @@ class _MockClassInfo { } Expression _dummyValue(analyzer.DartType type) { + // The type is nullable, just take a shortcut and return `null`. + if (typeSystem.isNullable(type)) { + return literalNull; + } + if (type is analyzer.FunctionType) { return _dummyFunctionValue(type); } @@ -1609,6 +1681,8 @@ class _MockClassInfo { 'returnValue': _fallbackGeneratorCode(getter, fallbackGenerator) else if (typeSystem._returnTypeIsNonNullable(getter)) 'returnValue': _dummyValue(returnType), + if (mockTarget.onMissingStub == OnMissingStub.returnDefault) + 'returnValueForMissingStub': _dummyValue(returnType), }; var superNoSuchMethod = refer('super').property('noSuchMethod').call([invocation], namedArgs); diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 9c8430933..d8844f287 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.2.0'; +const packageVersion = '5.3.0-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 91ce9917b..faea5d790 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.2.0 +version: 5.3.0-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 23210f4b9..5978f0a90 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -32,6 +32,12 @@ class GenerateMocks { const GenerateMocks(this.classes, {this.customMocks = []}); } +class GenerateNiceMocks { + final List mocks; + + const GenerateNiceMocks(this.mocks); +} + class MockSpec { final Symbol mockName; @@ -39,6 +45,8 @@ class MockSpec { final bool returnNullOnMissingStub; + final OnMissingStub? onMissingStub; + final Set unsupportedMembers; final Map fallbackGenerators; @@ -47,11 +55,14 @@ class MockSpec { Symbol? as, List mixingIn = const [], this.returnNullOnMissingStub = false, + this.onMissingStub, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, }) : mockName = as, mixins = mixingIn; } + +enum OnMissingStub { throwException, returnNull, returnDefault } ''' }; @@ -429,6 +440,67 @@ void main() { r" '\'m\' cannot be used without a mockito fallback generator.');")); }); + test( + 'generates mock methods with non-nullable return types, specifying ' + 'legal default values for basic known types', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + int m(); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks( + [], + customMocks: [ + MockSpec(onMissingStub: OnMissingStub.returnDefault), + ], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' int m() => (super.noSuchMethod(Invocation.method(#m, []),\n' + ' returnValue: 0, returnValueForMissingStub: 0) as int);')); + }); + + test( + 'generates mock methods with non-nullable return types, specifying ' + 'legal default values for unknown types', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + Bar m(); + } + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks( + [], + customMocks: [ + MockSpec(onMissingStub: OnMissingStub.returnDefault), + ], + ) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + ' _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []),\n' + ' returnValue: _FakeBar_0(),\n' + ' returnValueForMissingStub: _FakeBar_0()) as _i2.Bar);\n')); + }); + test('generates mock classes including a fallback generator for a getter', () async { var mocksContent = await buildWithNonNullable({ @@ -986,6 +1058,48 @@ void main() { }, ); }); + + test( + 'generates a mock class which uses the new behavior of returning ' + 'a valid value for missing stubs, if GenerateNiceMocks were used', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + int m(); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([MockSpec()]) + void main() {} + ''' + }); + expect(mocksContent, isNot(contains('throwOnMissingStub'))); + expect(mocksContent, contains('returnValueForMissingStub:')); + }); + + test('mixed GenerateMocks and GenerateNiceMocks annotations could be used', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([MockSpec()]) + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + ''' + }); + expect(mocksContent, contains('class MockFoo')); + expect(mocksContent, contains('class MockBar')); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 502c2ff48..a2b0b776b 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -24,6 +24,10 @@ T Function(T) returnsGenericFunctionShim() => (T _) => null as T; Bar ], customMocks: [ MockSpec(as: #MockFooRelaxed, returnNullOnMissingStub: true), + MockSpec( + as: #MockFooWithDefaults, + onMissingStub: OnMissingStub.returnDefault, + ), MockSpec(as: #MockBarRelaxed, returnNullOnMissingStub: true), MockSpec( as: #MockBazWithUnsupportedMembers, @@ -47,6 +51,7 @@ T Function(T) returnsGenericFunctionShim() => (T _) => null as T; ), MockSpec(mixingIn: [HasPrivateMixin]), ]) +@GenerateNiceMocks([MockSpec(as: #MockFooNice)]) void main() { group('for a generated mock,', () { late MockFoo foo; @@ -249,6 +254,40 @@ void main() { }); }); + group('for a generated mock using OnMissingStub.returnDefault,', () { + late Foo foo; + + setUp(() { + foo = MockFooWithDefaults(); + }); + + test('an unstubbed method returns a value', () { + when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); + expect(foo.namedParameter(x: 43), equals('')); + }); + + test('an unstubbed getter returns a value', () { + expect(foo.getter, equals('')); + }); + }); + + group('for a generated nice mock', () { + late Foo foo; + + setUp(() { + foo = MockFooNice(); + }); + + test('an unstubbed method returns a value', () { + when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); + expect(foo.namedParameter(x: 43), equals('')); + }); + + test('an unstubbed getter returns a value', () { + expect(foo.getter, equals('')); + }); + }); + test('a generated mock can be used as a stub argument', () { var foo = MockFoo(); var bar = MockBar(); From 5bf014f0bd8e4a14e5166f0a9bb932d8c198ba3c Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 21 Jul 2022 12:23:02 -0400 Subject: [PATCH 443/607] Improve an error message to indicate which MockSpec has an error. PiperOrigin-RevId: 462401324 --- pkgs/mockito/lib/src/builder.dart | 32 +++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 3ed30ae1f..53375f134 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -476,23 +476,23 @@ class _MockTargetGatherer { } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { - mockTargets.addAll(customMocksField - .toListValue()! - .map((mockSpec) => _mockTargetFromMockSpec(mockSpec, entryLib))); + mockTargets.addAll(customMocksField.toListValue()!.mapIndexed( + (index, mockSpec) => + _mockTargetFromMockSpec(mockSpec, entryLib, index))); } return mockTargets; } static _MockTarget _mockTargetFromMockSpec( - DartObject mockSpec, LibraryElement entryLib, + DartObject mockSpec, LibraryElement entryLib, int index, {bool nice = false}) { final mockSpecType = mockSpec.type as analyzer.InterfaceType; assert(mockSpecType.typeArguments.length == 1); final typeToMock = mockSpecType.typeArguments.single; if (typeToMock.isDynamic) { throw InvalidMockitoAnnotationException( - 'Mockito cannot mock `dynamic`; be sure to declare type ' - 'arguments on MockSpec(), in @GenerateMocks.'); + 'Mockito cannot mock `dynamic`; be sure to declare a type argument ' + 'on the ${(index + 1).ordinal} MockSpec() in @GenerateMocks.'); } var type = _determineDartType(typeToMock, entryLib.typeProvider); @@ -590,8 +590,8 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'The GenerateNiceMocks "mockSpecs" argument is missing'); } - return mockSpecsField.toListValue()!.map( - (mockSpec) => _mockTargetFromMockSpec(mockSpec, entryLib, nice: true)); + return mockSpecsField.toListValue()!.mapIndexed((index, mockSpec) => + _mockTargetFromMockSpec(mockSpec, entryLib, index, nice: true)); } static Map _extractFallbackGenerators( @@ -1998,5 +1998,21 @@ extension on TypeSystem { method.parameters.any((p) => isPotentiallyNonNullable(p.type)); } +extension on int { + String get ordinal { + final remainder = this % 10; + switch (remainder) { + case (1): + return '${this}st'; + case (2): + return '${this}nd'; + case (3): + return '${this}rd'; + default: + return '${this}th'; + } + } +} + bool _needsOverrideForVoidStub(ExecutableElement method) => method.returnType.isVoid || method.returnType.isFutureOfVoid; From 2d69ac09b8d0844802204c5c191707d378f113a4 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 22 Jul 2022 12:15:05 -0400 Subject: [PATCH 444/607] Add `SmartFake` and fix the `MissingStubError` message `MissingStubError` shouldn't suggest to use deprecated `returnNullOnMissingStub` option, changed it to suggest `@GenerateNiceMocks` instead. Also changed the generator to return a bit smarter fakes for implementable classes: previously if these fakes were accessed they threw just an `UnimplementedError` that had no hint that the root cause is a missing stub. Now the error mentions the missing stub, shows the stack trace where the fake was created and suggests adding an expectation. PiperOrigin-RevId: 462636910 --- pkgs/mockito/CHANGELOG.md | 3 + pkgs/mockito/lib/mockito.dart | 4 +- pkgs/mockito/lib/src/builder.dart | 39 +++++++---- pkgs/mockito/lib/src/mock.dart | 45 ++++++++++++- .../mockito/test/builder/auto_mocks_test.dart | 66 ++++++++++++------- .../test/builder/custom_mocks_test.dart | 5 +- pkgs/mockito/test/end2end/foo.dart | 6 +- .../test/end2end/generated_mocks_test.dart | 23 +++++++ 8 files changed, 149 insertions(+), 42 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 12de16959..fc24537d1 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -10,6 +10,9 @@ new `onMissingStub` parameter. * Introduce a new `@GenerateNiceMocks` annotation, that uses the new "return a legal value" behavior for missing stubs. +* Add `SmartFake` class to be used as a return values for unstubbed + methods. It remembers where it was created and throws a descriptive error + in case the fake is later used. ## 5.2.0 diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 59ec2a2a0..77ca3d4f7 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -18,6 +18,7 @@ export 'package:test_api/fake.dart' show Fake; export 'src/mock.dart' show Mock, + SmartFake, named, // ignore: deprecated_member_use_from_same_package // -- setting behaviour @@ -49,4 +50,5 @@ export 'src/mock.dart' resetMockitoState, logInvocations, untilCalled, - MissingStubError; + MissingStubError, + FakeUsedError; diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 53375f134..aba23aaac 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1240,13 +1240,13 @@ class _MockClassInfo { } else if (mockTarget.onMissingStub == OnMissingStub.returnDefault) { // Return a legal default value if no stub is found which matches a real // call. - returnValueForMissingStub = _dummyValue(returnType); + returnValueForMissingStub = _dummyValue(returnType, invocation); } final namedArgs = { if (fallbackGenerator != null) 'returnValue': _fallbackGeneratorCode(method, fallbackGenerator) else if (typeSystem._returnTypeIsNonNullable(method)) - 'returnValue': _dummyValue(returnType), + 'returnValue': _dummyValue(returnType, invocation), if (returnValueForMissingStub != null) 'returnValueForMissingStub': returnValueForMissingStub, }; @@ -1277,14 +1277,14 @@ class _MockClassInfo { [for (var t in method.typeParameters) refer(t.name)]); } - Expression _dummyValue(analyzer.DartType type) { + Expression _dummyValue(analyzer.DartType type, Expression invocation) { // The type is nullable, just take a shortcut and return `null`. if (typeSystem.isNullable(type)) { return literalNull; } if (type is analyzer.FunctionType) { - return _dummyFunctionValue(type); + return _dummyFunctionValue(type, invocation); } if (type is! analyzer.InterfaceType) { @@ -1303,7 +1303,7 @@ class _MockClassInfo { final typeArgument = typeArguments.first; final futureValueArguments = typeSystem.isPotentiallyNonNullable(typeArgument) - ? [_dummyValue(typeArgument)] + ? [_dummyValue(typeArgument, invocation)] : []; return _futureReference(_typeReference(typeArgument)) .property('value') @@ -1348,7 +1348,7 @@ class _MockClassInfo { // This class is unknown; we must likely generate a fake class, and return // an instance here. - return _dummyValueImplementing(type); + return _dummyValueImplementing(type, invocation); } /// Returns a reference to [Future], optionally with a type argument for the @@ -1362,7 +1362,8 @@ class _MockClassInfo { } }); - Expression _dummyFunctionValue(analyzer.FunctionType type) { + Expression _dummyFunctionValue( + analyzer.FunctionType type, Expression invocation) { return Method((b) { // The positional parameters in a FunctionType have no names. This // counter lets us create unique dummy names. @@ -1388,12 +1389,13 @@ class _MockClassInfo { if (type.returnType.isVoid) { b.body = Code(''); } else { - b.body = _dummyValue(type.returnType).code; + b.body = _dummyValue(type.returnType, invocation).code; } }).genericClosure; } - Expression _dummyValueImplementing(analyzer.InterfaceType dartType) { + Expression _dummyValueImplementing( + analyzer.InterfaceType dartType, Expression invocation) { final elementToFake = dartType.element; if (elementToFake.isEnum) { return _typeReference(dartType).property( @@ -1409,7 +1411,7 @@ class _MockClassInfo { b ..symbol = fakeName ..types.addAll(typeArguments.map(_typeReference)); - }).newInstance([]); + }).newInstance([refer('this'), invocation]); } } @@ -1422,7 +1424,7 @@ class _MockClassInfo { final typeParameters = []; cBuilder ..name = fakeName - ..extend = referImported('Fake', 'package:mockito/mockito.dart'); + ..extend = referImported('SmartFake', 'package:mockito/mockito.dart'); for (var typeParameter in elementToFake.typeParameters) { cBuilder.types.add(_typeParameterReference(typeParameter)); typeParameters.add(refer(typeParameter.name)); @@ -1433,6 +1435,17 @@ class _MockClassInfo { ..url = _typeImport(elementToFake) ..types.addAll(typeParameters); })); + cBuilder.constructors.add(Constructor((constrBuilder) => constrBuilder + ..requiredParameters.addAll([ + Parameter((pBuilder) => pBuilder + ..name = 'parent' + ..type = referImported('Object', 'dart:core')), + Parameter((pBuilder) => pBuilder + ..name = 'parentInvocation' + ..type = referImported('Invocation', 'dart:core')) + ]) + ..initializers.add(refer('super') + .call([refer('parent'), refer('parentInvocation')]).code))); final toStringMethod = elementToFake.lookUpMethod('toString', elementToFake.library); @@ -1680,9 +1693,9 @@ class _MockClassInfo { if (fallbackGenerator != null) 'returnValue': _fallbackGeneratorCode(getter, fallbackGenerator) else if (typeSystem._returnTypeIsNonNullable(getter)) - 'returnValue': _dummyValue(returnType), + 'returnValue': _dummyValue(returnType, invocation), if (mockTarget.onMissingStub == OnMissingStub.returnDefault) - 'returnValueForMissingStub': _dummyValue(returnType), + 'returnValueForMissingStub': _dummyValue(returnType, invocation), }; var superNoSuchMethod = refer('super').property('noSuchMethod').call([invocation], namedArgs); diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index c6ccf2f05..320170907 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -218,6 +218,47 @@ class Mock { _realCallsToString(_realCalls.where((call) => !call.verified)); } +/// A slightly smarter fake to be used for return value on missing stubs. +/// Shows a more descriptive error message to the user that mentions not +/// only a place where a fake was used but also why it was created +/// (i.e. which stub needs to be added). +/// +/// Inspired by Java's Mockito `SmartNull`. +class SmartFake { + final Object _parent; + final Invocation _parentInvocation; + final StackTrace _createdStackTrace; + @override + dynamic noSuchMethod(Invocation invocation) => throw FakeUsedError( + _parentInvocation, invocation, _parent, _createdStackTrace); + SmartFake(this._parent, this._parentInvocation) + : _createdStackTrace = StackTrace.current; +} + +class FakeUsedError extends Error { + final Invocation parentInvocation, invocation; + final Object receiver; + final StackTrace createdStackTrace; + final String _memberName; + + FakeUsedError(this.parentInvocation, this.invocation, this.receiver, + this.createdStackTrace) + : _memberName = _symbolToString(parentInvocation.memberName); + + @override + String toString() => "FakeUsedError: '$_memberName'\n" + 'No stub was found which matches the argument of this method call:\n' + '${parentInvocation.toPrettyString()}\n\n' + 'A fake object was created for this call, in the hope that it ' + "won't be ever accessed.\n" + "Here is the stack trace where '$_memberName' was called:\n\n" + '${createdStackTrace.toString()}\n\n' + "However, member '${_symbolToString(invocation.memberName)}' of the " + 'created fake object was accessed.\n' + 'Add a stub for ' + "${receiver.runtimeType}.$_memberName using Mockito's 'when' API.\n"; +} + /// An error which is thrown when no stub is found which matches the arguments /// of a real method call on a mock object. class MissingStubError extends Error { @@ -232,8 +273,8 @@ class MissingStubError extends Error { 'No stub was found which matches the arguments of this method call:\n' '${invocation.toPrettyString()}\n\n' "Add a stub for this method using Mockito's 'when' API, or generate the " - '${receiver.runtimeType} mock with a MockSpec with ' - "'returnNullOnMissingStub: true' (see " + '${receiver.runtimeType} mock with the @GenerateNiceMocks annotation ' + '(see ' 'https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).'; } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index c0d6e941b..b50a9fccd 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2309,9 +2309,8 @@ void main() { } '''), _containsAllOf(dedent2(''' - _i2.Bar m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: _FakeBar_0()) - as _i2.Bar); + _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: _FakeBar_0(this, Invocation.method(#m, []))) as _i2.Bar); ''')), ); }); @@ -2326,7 +2325,8 @@ void main() { '''), _containsAllOf(dedent2(''' _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _FakeBar_0()) as _i2.Bar); + returnValue: _FakeBar_0(this, Invocation.method(#m, []))) + as _i2.Bar); ''')), ); }); @@ -2395,9 +2395,11 @@ void main() { Foo Function() m() => () => Foo(); } '''), - _containsAllOf( - '_i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []),\n' - ' returnValue: () => _FakeFoo_0()) as _i2.Foo Function());'), + _containsAllOf(dedent2(''' + _i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: () => _FakeFoo_0(this, Invocation.method(#m, []))) + as _i2.Foo Function()); + ''')), ); }); @@ -2411,9 +2413,11 @@ void main() { _Callback m() => () => Foo(); } '''), - _containsAllOf( - '_i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []),\n' - ' returnValue: () => _FakeFoo_0()) as _i2.Foo Function());'), + _containsAllOf(dedent2(''' + _i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []), + returnValue: () => _FakeFoo_0(this, Invocation.method(#m, []))) + as _i2.Foo Function()); + ''')), ); }); @@ -2479,7 +2483,8 @@ void main() { '''), _containsAllOf(dedent2(''' _i2.File Function() m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: () => _FakeFile_0()) as _i2.File Function()); + returnValue: () => _FakeFile_0(this, Invocation.method(#m, []))) + as _i2.File Function()); ''')), ); }); @@ -2492,7 +2497,11 @@ void main() { } class Bar {} '''), - _containsAllOf('class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}'), + _containsAllOf(dedent(''' + class _FakeBar_0 extends _i1.SmartFake implements _i2.Bar { + _FakeBar_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); + }''')), ); }); @@ -2515,10 +2524,14 @@ void main() { class Bar {} ''', }); - expect(mocksOutput, - contains('class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}')); - expect(mocksOutput, - contains('class _FakeBar_1 extends _i1.Fake implements _i3.Bar {}')); + expect( + mocksOutput, + contains( + 'class _FakeBar_0 extends _i1.SmartFake implements _i2.Bar {')); + expect( + mocksOutput, + contains( + 'class _FakeBar_1 extends _i1.SmartFake implements _i3.Bar {')); }); test('generates a fake generic class used in return values', () async { @@ -2530,7 +2543,7 @@ void main() { class Bar {} '''), _containsAllOf( - 'class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}'), + 'class _FakeBar_0 extends _i1.SmartFake implements _i2.Bar {'), ); }); @@ -2544,8 +2557,10 @@ void main() { Bar m1() => Bar(); } '''), - _containsAllOf( - 'class _FakeBar_0 extends _i2.Fake implements _i1.Bar {}'), + _containsAllOf(dedent(''' + class _FakeBar_0 extends _i2.SmartFake + implements _i1.Bar { + ''')), ); }); @@ -2559,8 +2574,10 @@ void main() { BarOfBaz m1() => Bar(); } '''), - _containsAllOf( - 'class _FakeBar_0 extends _i2.Fake implements _i1.Bar {}'), + _containsAllOf(dedent(''' + class _FakeBar_0 extends _i2.SmartFake + implements _i1.Bar { + ''')), ); }); @@ -2576,7 +2593,7 @@ void main() { } '''), _containsAllOf( - 'class _FakeBar_0 extends _i1.Fake implements _i2.Bar {}'), + 'class _FakeBar_0 extends _i1.SmartFake implements _i2.Bar {'), ); }); @@ -2592,7 +2609,10 @@ void main() { } '''), _containsAllOf(dedent(''' - class _FakeBar_0 extends _i1.Fake implements _i2.Bar { + class _FakeBar_0 extends _i1.SmartFake implements _i2.Bar { + _FakeBar_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); + @override String toString({bool? a = true}) => super.toString(); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 5978f0a90..8702d9b89 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -497,8 +497,9 @@ void main() { mocksContent, contains( ' _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []),\n' - ' returnValue: _FakeBar_0(),\n' - ' returnValueForMissingStub: _FakeBar_0()) as _i2.Bar);\n')); + ' returnValue: _FakeBar_0(this, Invocation.method(#m, [])),\n' + ' returnValueForMissingStub:\n' + ' _FakeBar_0(this, Invocation.method(#m, []))) as _i2.Bar);\n')); }); test('generates mock classes including a fallback generator for a getter', diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index f3f9f13a6..fb9108a31 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -20,9 +20,13 @@ class Foo { void returnsVoid() {} Future returnsFutureVoid() => Future.value(); Future? returnsNullableFutureVoid() => Future.value(); + Bar returnsBar(int arg) => Bar(); } -class Bar {} +class Bar { + int get x => 0; + int f() => 0; +} abstract class Baz { T returnsTypeVariable(); diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index a2b0b776b..20c685b53 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -286,6 +286,29 @@ void main() { test('an unstubbed getter returns a value', () { expect(foo.getter, equals('')); }); + + test('an unstubbed method returning non-core type returns a fake', () { + when(foo.returnsBar(42)).thenReturn(Bar()); + expect(foo.returnsBar(43), isA()); + }); + + test('a fake throws a FakeUsedError if a getter is called', () { + when(foo.returnsBar(42)).thenReturn(Bar()); + final bar = foo.returnsBar(43); + expect( + () => bar.x, + throwsA(isA().having( + (e) => e.toString(), 'toString()', contains('returnsBar(43)')))); + }); + + test('a fake throws a FakeUsedError if a method is called', () { + when(foo.returnsBar(42)).thenReturn(Bar()); + final bar = foo.returnsBar(43); + expect( + () => bar.f(), + throwsA(isA().having( + (e) => e.toString(), 'toString()', contains('returnsBar(43)')))); + }); }); test('a generated mock can be used as a stub argument', () { From d78bdff496d4d2f7d1e76255c97d4ea4f2aaba7e Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 27 Jul 2022 11:57:08 -0400 Subject: [PATCH 445/607] Migrate off of deprecated analyzer APIs Also fix some bad doc comment references. PiperOrigin-RevId: 463599669 --- pkgs/mockito/lib/src/builder.dart | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index aba23aaac..9af4488c2 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -409,8 +409,8 @@ class _MockTargetGatherer { final mockTargets = <_MockTarget>{}; final possiblyAnnotatedElements = [ - ...entryLib.exports, - ...entryLib.imports, + ...entryLib.libraryExports, + ...entryLib.libraryImports, ...entryLib.topLevelElements, ]; @@ -419,7 +419,7 @@ class _MockTargetGatherer { // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; - final annotationClass = annotation.element!.enclosingElement!.name; + final annotationClass = annotation.element!.enclosingElement2!.name; switch (annotationClass) { case 'GenerateMocks': mockTargets @@ -766,9 +766,9 @@ class _MockTargetGatherer { /// /// If any type in the above positions is private, [function] is un-stubbable. /// If the return type is potentially non-nullable, [function] is - /// un-stubbable, unless [isParamter] is true (indicating that [function] is + /// un-stubbable, unless [isParameter] is true (indicating that [function] is /// found in a parameter of a method-to-be-stubbed) or - /// [allowUnsupportedMember] is true, or [hasDummyCenerator] is true + /// [allowUnsupportedMember] is true, or [hasDummyGenerator] is true /// (indicating that a dummy generator, which can return dummy values, has /// been provided). List _checkFunction( @@ -1025,7 +1025,7 @@ class _MockClassInfo { for (final mixin in mockTarget.mixins) { cBuilder.mixins.add(TypeReference((b) { b - ..symbol = mixin.name + ..symbol = mixin.element.name ..url = _typeImport(mixin.element) ..types.addAll(mixin.typeArguments.map(_typeReference)); })); @@ -1415,7 +1415,7 @@ class _MockClassInfo { } } - /// Adds a [Fake] implementation of [elementToFake], named [fakeName]. + /// Adds a `Fake` implementation of [elementToFake], named [fakeName]. void _addFakeClass(String fakeName, ClassElement elementToFake) { mockLibraryInfo.fakeClasses.add(Class((cBuilder) { // For each type parameter on [elementToFake], the Fake class needs a type @@ -1488,7 +1488,7 @@ class _MockClassInfo { parameter.computeConstantValue()!, parameter) .code; } on _ReviveException catch (e) { - final method = parameter.enclosingElement!; + final method = parameter.enclosingElement2!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' "'${mockTarget.classElement.displayName}.${method.displayName}'; " @@ -1519,8 +1519,8 @@ class _MockClassInfo { if (!parameter.isCovariant) { return type; } - final method = parameter.enclosingElement as MethodElement; - final class_ = method.enclosingElement as ClassElement; + final method = parameter.enclosingElement2 as MethodElement; + final class_ = method.enclosingElement2 as ClassElement; final name = Name(method.librarySource.uri, method.name); final overriddenMethods = inheritanceManager.getOverridden2(class_, name); if (overriddenMethods == null) { @@ -1530,7 +1530,7 @@ class _MockClassInfo { while (allOverriddenMethods.isNotEmpty) { final overriddenMethod = allOverriddenMethods.removeFirst(); final secondaryOverrides = inheritanceManager.getOverridden2( - overriddenMethod.enclosingElement as ClassElement, name); + overriddenMethod.enclosingElement2 as ClassElement, name); if (secondaryOverrides != null) { allOverriddenMethods.addAll(secondaryOverrides); } @@ -1833,7 +1833,7 @@ class _MockClassInfo { /// Returns a [Reference] to [symbol] with [url]. /// - /// This function overrides [code_builder.refer] so as to ensure that [url] is + /// This function overrides `code_builder.refer` so as to ensure that [url] is /// given. static Reference referImported(String symbol, String? url) => Reference(symbol, url); @@ -1923,10 +1923,10 @@ extension on Element { if (this is ClassElement) { return "The class '$name'"; } else if (this is MethodElement) { - var className = enclosingElement!.name; + var className = enclosingElement2!.name; return "The method '$className.$name'"; } else if (this is PropertyAccessorElement) { - var className = enclosingElement!.name; + var className = enclosingElement2!.name; return "The property accessor '$className.$name'"; } else { return 'unknown element'; From 088c02667f9b816d5668dc09b77d40b4382e91e4 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 28 Jul 2022 04:27:01 -0400 Subject: [PATCH 446/607] Ignore deprecated use of `returnNullOnMissingStub` and `OnMissingStub.returnNull`. PiperOrigin-RevId: 463778706 --- pkgs/mockito/lib/annotations.dart | 4 ++++ pkgs/mockito/lib/src/builder.dart | 2 ++ 2 files changed, 6 insertions(+) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 9ed32fcc8..32c95c0cc 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -109,6 +109,10 @@ class MockSpec { final Map fallbackGenerators; + // This is here for the doc comment references to `returnNullOnMissingStub`. + // Remove when those are gone. + // ignore_for_file: deprecated_member_use_from_same_package + /// Constructs a custom mock specification. /// /// Specify a custom name with the [as] parameter. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 9af4488c2..167ff3635 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -542,6 +542,7 @@ class _MockTargetGatherer { final onMissingStubIndex = onMissingStubValue.getField('index')!.toIntValue()!; onMissingStub = OnMissingStub.values[onMissingStubIndex]; + // ignore: deprecated_member_use_from_same_package if (onMissingStub == OnMissingStub.returnNull) { throw ArgumentError( "'OnMissingStub.returnNull' is not supported with " @@ -553,6 +554,7 @@ class _MockTargetGatherer { // No value was given for `onMissingStub`. But the behavior may // be specified by `returnNullOnMissingStub`. onMissingStub = returnNullOnMissingStub + // ignore: deprecated_member_use_from_same_package ? OnMissingStub.returnNull : OnMissingStub.throwException; } else { From cfd004074a487cd3f8dcd4b953f745e1d1c3c69e Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 28 Jul 2022 04:41:46 -0400 Subject: [PATCH 447/607] Include `required` keyword in function types to match overridden function types. Fixes https://github.com/dart-lang/mockito/issues/436 PiperOrigin-RevId: 463780594 --- pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/builder.dart | 10 ++++++++-- pkgs/mockito/test/builder/auto_mocks_test.dart | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index fc24537d1..1b3ff22b7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -13,6 +13,8 @@ * Add `SmartFake` class to be used as a return values for unstubbed methods. It remembers where it was created and throws a descriptive error in case the fake is later used. +* Include `required` keyword in function types to match overridden function + types. ## 5.2.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 167ff3635..6c5c8611e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1787,8 +1787,14 @@ class _MockClassInfo { .addAll(type.normalParameterTypes.map(_typeReference)) ..optionalParameters .addAll(type.optionalParameterTypes.map(_typeReference)); - for (var parameter in type.namedParameterTypes.entries) { - b.namedParameters[parameter.key] = _typeReference(parameter.value); + for (var parameter + in type.parameters.where((p) => p.isOptionalNamed)) { + b.namedParameters[parameter.name] = _typeReference(parameter.type); + } + for (var parameter + in type.parameters.where((p) => p.isRequiredNamed)) { + b.namedRequiredParameters[parameter.name] = + _typeReference(parameter.type); } b.types.addAll(type.typeFormals.map(_typeParameterReference)); }); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index b50a9fccd..376b7cb6e 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -755,7 +755,8 @@ void main() { dedent2(''' _i3.List m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: []) - as _i3.List);'''), + as _i3.List); + '''), )); }); @@ -1597,6 +1598,20 @@ void main() { ); }); + test( + 'matches requiredness of parameter types within a function-typed ' + 'parameter', () async { + await expectSingleNonNullableOutput(dedent(''' + class Foo { + void m(void Function({required int p}) cb) {} + } + '''), _containsAllOf(dedent2(''' + void m(void Function({required int p})? cb) => + super.noSuchMethod(Invocation.method(#m, [cb]), + returnValueForMissingStub: null); + '''))); + }); + test('matches nullability of a generic parameter', () async { await expectSingleNonNullableOutput( dedent(r''' From 9d54b2942a937e9d61dbe6b920805b9eebc19c8c Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 28 Jul 2022 10:51:04 -0400 Subject: [PATCH 448/607] Bump to 5.3.0 PiperOrigin-RevId: 463836330 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 1b3ff22b7..055bb07ba 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.3.0-dev +## 5.3.0 * Introduce a new `MockSpec` parameter, `onMissingStub`, which allows specifying three actions to take when a real call is made to a mock method diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index d8844f287..71feea41c 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.0-dev'; +const packageVersion = '5.3.0'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index faea5d790..4141418de 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.0-dev +version: 5.3.0 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From 7ad9a23df5df41a3031936a1324db31975e22029 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 28 Jul 2022 08:22:23 -0700 Subject: [PATCH 449/607] Bump SDK version in order to use enhanced enums --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 4141418de..eb187fb3f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,7 +6,7 @@ description: >- repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.12.0-0 <3.0.0' + sdk: '>=2.17.0-0 <3.0.0' dependencies: analyzer: '>=2.1.0 <5.0.0' From aa2aa8e9e52c69008050befe92fd14d6b7bf8768 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 28 Jul 2022 09:55:53 -0700 Subject: [PATCH 450/607] Bump to code_builder ^4.2.0 for namedRequiredParameters --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index eb187fb3f..17aeda963 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: analyzer: '>=2.1.0 <5.0.0' build: '>=1.3.0 <3.0.0' - code_builder: ^4.0.0 + code_builder: ^4.2.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 From afee25f9c0a0288e8ba2f0d5a3b94a2ba6afc3fb Mon Sep 17 00:00:00 2001 From: nbosch Date: Wed, 3 Aug 2022 20:15:28 -0400 Subject: [PATCH 451/607] Bump min dependency on code_builder Towards dart-lang/mockito#561 This package depends on a new API that was introduced in version 4.2.0 of `code_builder`, but we did not document the API in the changelog or bump min dependencies. Changing this now won't retroactively correct the fact that users with an older code_builder can pick up an older mockito, but it should reduce the risk of users continuing to see it. PiperOrigin-RevId: 465185748 --- pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 055bb07ba..7172f6819 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,5 @@ +## 5.3.1-dev + ## 5.3.0 * Introduce a new `MockSpec` parameter, `onMissingStub`, which allows diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 71feea41c..005c67d59 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.0'; +const packageVersion = '5.3.1-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 17aeda963..60f21ea00 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,12 +1,12 @@ name: mockito -version: 5.3.0 +version: 5.3.1-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.17.0-0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dependencies: analyzer: '>=2.1.0 <5.0.0' From c6e4dd568d65886558f7f210ac7ab96ad506c50a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 12 Aug 2022 19:42:20 -0400 Subject: [PATCH 452/607] Stop using deprecated declarations, prepare for analyzer breaking changes. PiperOrigin-RevId: 467322433 --- pkgs/mockito/lib/src/builder.dart | 94 +++++++++++++++---------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 6c5c8611e..11aaab619 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -149,8 +149,8 @@ $rawOutput return; } seenTypes.add(type); - librariesWithTypes.add(type.element.library); - type.element.accept(typeVisitor); + librariesWithTypes.add(type.element2.library); + type.element2.accept(typeVisitor); // For a type like `Foo`, add the `Bar`. type.typeArguments .whereType() @@ -278,14 +278,14 @@ class _TypeVisitor extends RecursiveElementVisitor { if (type == null) return; if (type is analyzer.InterfaceType) { - final alreadyVisitedElement = _elements.contains(type.element); - _elements.add(type.element); + final alreadyVisitedElement = _elements.contains(type.element2); + _elements.add(type.element2); type.typeArguments.forEach(_addType); if (!alreadyVisitedElement) { - type.element.typeParameters.forEach(visitTypeParameterElement); + type.element2.typeParameters.forEach(visitTypeParameterElement); final toStringMethod = - type.element.lookUpMethod('toString', type.element.library); + type.element2.lookUpMethod('toString', type.element2.library); if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { // In a Fake class which implements a class which overrides `toString` // with additional (optional) parameters, we must also override @@ -294,7 +294,7 @@ class _TypeVisitor extends RecursiveElementVisitor { for (final parameter in toStringMethod.parameters) { final parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - parameterType.element.accept(this); + parameterType.element2.accept(this); } } } @@ -382,7 +382,7 @@ class _MockTarget { required this.fallbackGenerators, }); - ClassElement get classElement => classType.element; + InterfaceElement get classElement => classType.element2; } /// This class gathers and verifies mock targets referenced in `GenerateMocks` @@ -419,7 +419,7 @@ class _MockTargetGatherer { // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; - final annotationClass = annotation.element!.enclosingElement2!.name; + final annotationClass = annotation.element!.enclosingElement3!.name; switch (annotationClass) { case 'GenerateMocks': mockTargets @@ -463,8 +463,8 @@ class _MockTargetGatherer { // `type` have been instantiated to bounds here. Switch to the // declaration, which will be an uninstantiated type. final declarationType = - (type.element.declaration as ClassElement).thisType; - final mockName = 'Mock${declarationType.element.name}'; + (type.element2.declaration as ClassElement).thisType; + final mockName = 'Mock${declarationType.element2.name}'; mockTargets.add(_MockTarget( declarationType, mockName, @@ -501,10 +501,10 @@ class _MockTargetGatherer { // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. - type = (type.element.declaration as ClassElement).thisType; + type = (type.element2.declaration as ClassElement).thisType; } final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? - 'Mock${type.element.name}'; + 'Mock${type.element2.name}'; final mixins = []; for (final m in mockSpec.getField('mixins')!.toListValue()!) { final typeToMixin = m.toTypeValue(); @@ -624,8 +624,8 @@ class _MockTargetGatherer { static analyzer.InterfaceType _determineDartType( analyzer.DartType typeToMock, TypeProvider typeProvider) { if (typeToMock is analyzer.InterfaceType) { - final elementToMock = typeToMock.element; - if (elementToMock.isEnum) { + final elementToMock = typeToMock.element2; + if (elementToMock is EnumElement) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock an enum: ${elementToMock.displayName}'); } @@ -697,7 +697,7 @@ class _MockTargetGatherer { var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => c.interfaces - .map((type) => type.element) + .map((type) => type.element2) .contains(mockTarget.classElement) && _isMockClass(c.supertype!)); if (preexistingMock != null) { @@ -783,7 +783,7 @@ class _MockTargetGatherer { final errorMessages = []; final returnType = function.returnType; if (returnType is analyzer.InterfaceType) { - if (returnType.element.isPrivate) { + if (returnType.element2.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private return type, and ' 'cannot be stubbed.'); @@ -811,7 +811,7 @@ class _MockTargetGatherer { for (var parameter in function.parameters) { var parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - var parameterTypeElement = parameterType.element; + var parameterTypeElement = parameterType.element2; if (parameterTypeElement.isPrivate) { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not @@ -850,7 +850,7 @@ class _MockTargetGatherer { var typeParameter = element.bound; if (typeParameter == null) continue; if (typeParameter is analyzer.InterfaceType) { - if (typeParameter.element.isPrivate) { + if (typeParameter.element2.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type parameter ' 'bound, and cannot be stubbed.'); @@ -873,7 +873,7 @@ class _MockTargetGatherer { var errorMessages = []; for (var typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { - if (typeArgument.element.isPrivate) { + if (typeArgument.element2.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type argument, ' 'and cannot be stubbed.'); @@ -888,8 +888,8 @@ class _MockTargetGatherer { /// Return whether [type] is the Mock class declared by mockito. bool _isMockClass(analyzer.InterfaceType type) => - type.element.name == 'Mock' && - type.element.source.fullName.endsWith('lib/src/mock.dart'); + type.element2.name == 'Mock' && + type.element2.source.fullName.endsWith('lib/src/mock.dart'); } class _MockLibraryInfo { @@ -904,7 +904,7 @@ class _MockLibraryInfo { /// [ClassElement]s which are used in non-nullable return types, for which /// fake classes are added to the generated library. - final fakedClassElements = []; + final fakedClassElements = []; /// A mapping of each necessary [Element] to a URI from which it can be /// imported. @@ -1027,8 +1027,8 @@ class _MockClassInfo { for (final mixin in mockTarget.mixins) { cBuilder.mixins.add(TypeReference((b) { b - ..symbol = mixin.element.name - ..url = _typeImport(mixin.element) + ..symbol = mixin.element2.name + ..url = _typeImport(mixin.element2) ..types.addAll(mixin.typeArguments.map(_typeReference)); })); } @@ -1327,7 +1327,7 @@ class _MockClassInfo { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return literalSet({}, elementType); - } else if (type.element.declaration == typeProvider.streamElement) { + } else if (type.element2.declaration == typeProvider.streamElement) { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return TypeReference((b) { @@ -1342,7 +1342,7 @@ class _MockClassInfo { // These "List" types from dart:typed_data are "non-subtypeable", but they // have predicatble constructors; each has an unnamed constructor which // takes a single int argument. - return referImported(type.element.name, 'dart:typed_data') + return referImported(type.element2.name, 'dart:typed_data') .call([literalNum(0)]); // TODO(srawlins): Do other types from typed_data have a "non-subtypeable" // restriction as well? @@ -1398,8 +1398,8 @@ class _MockClassInfo { Expression _dummyValueImplementing( analyzer.InterfaceType dartType, Expression invocation) { - final elementToFake = dartType.element; - if (elementToFake.isEnum) { + final elementToFake = dartType.element2; + if (elementToFake is EnumElement) { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); } else { @@ -1418,7 +1418,7 @@ class _MockClassInfo { } /// Adds a `Fake` implementation of [elementToFake], named [fakeName]. - void _addFakeClass(String fakeName, ClassElement elementToFake) { + void _addFakeClass(String fakeName, InterfaceElement elementToFake) { mockLibraryInfo.fakeClasses.add(Class((cBuilder) { // For each type parameter on [elementToFake], the Fake class needs a type // parameter with same type variables, and a mirrored type argument for @@ -1490,7 +1490,7 @@ class _MockClassInfo { parameter.computeConstantValue()!, parameter) .code; } on _ReviveException catch (e) { - final method = parameter.enclosingElement2!; + final method = parameter.enclosingElement3!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' "'${mockTarget.classElement.displayName}.${method.displayName}'; " @@ -1521,8 +1521,8 @@ class _MockClassInfo { if (!parameter.isCovariant) { return type; } - final method = parameter.enclosingElement2 as MethodElement; - final class_ = method.enclosingElement2 as ClassElement; + final method = parameter.enclosingElement3 as MethodElement; + final class_ = method.enclosingElement3 as ClassElement; final name = Name(method.librarySource.uri, method.name); final overriddenMethods = inheritanceManager.getOverridden2(class_, name); if (overriddenMethods == null) { @@ -1532,7 +1532,7 @@ class _MockClassInfo { while (allOverriddenMethods.isNotEmpty) { final overriddenMethod = allOverriddenMethods.removeFirst(); final secondaryOverrides = inheritanceManager.getOverridden2( - overriddenMethod.enclosingElement2 as ClassElement, name); + overriddenMethod.enclosingElement3 as ClassElement, name); if (secondaryOverrides != null) { allOverriddenMethods.addAll(secondaryOverrides); } @@ -1623,7 +1623,7 @@ class _MockClassInfo { // We can create this invocation by referring to a const field or // top-level variable. return referImported( - revivable.accessor, _typeImport(object.type!.element)); + revivable.accessor, _typeImport(object.type!.element2)); } final name = revivable.source.fragment; @@ -1635,9 +1635,9 @@ class _MockClassInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final element = parameter != null && name != object.type!.element!.name - ? parameter.type.element - : object.type!.element; + final element = parameter != null && name != object.type!.element2!.name + ? parameter.type.element2 + : object.type!.element2; final type = referImported(name, _typeImport(element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( @@ -1767,10 +1767,10 @@ class _MockClassInfo { if (type is analyzer.InterfaceType) { return TypeReference((b) { b - ..symbol = type.element.name + ..symbol = type.element2.name ..isNullable = forceNullable || type.nullabilitySuffix == NullabilitySuffix.question - ..url = _typeImport(type.element) + ..url = _typeImport(type.element2) ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { @@ -1811,13 +1811,13 @@ class _MockClassInfo { } else if (type is analyzer.TypeParameterType) { return TypeReference((b) { b - ..symbol = type.element.name + ..symbol = type.element2.name ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { return referImported( type.getDisplayString(withNullability: false), - _typeImport(type.element), + _typeImport(type.element2), ); } } @@ -1931,10 +1931,10 @@ extension on Element { if (this is ClassElement) { return "The class '$name'"; } else if (this is MethodElement) { - var className = enclosingElement2!.name; + var className = enclosingElement3!.name; return "The method '$className.$name'"; } else if (this is PropertyAccessorElement) { - var className = enclosingElement2!.name; + var className = enclosingElement3!.name; return "The property accessor '$className.$name'"; } else { return 'unknown element'; @@ -1951,10 +1951,10 @@ extension on analyzer.DartType { /// Returns whether this type is a "List" type from the dart:typed_data /// library. bool get isDartTypedDataList { - if (element!.library!.name != 'dart.typed_data') { + if (element2!.library!.name != 'dart.typed_data') { return false; } - final name = element!.name; + final name = element2!.name; return name == 'Float32List' || name == 'Float64List' || name == 'Int8List' || @@ -1993,7 +1993,7 @@ extension on analyzer.InterfaceType { // is the bound of the corresponding type paramter (`dynamic` or // otherwise). We determine that an explicit type argument was given if // [typeArgument] is not equal to [bound]. - final bound = element.typeParameters[i].bound; + final bound = element2.typeParameters[i].bound; if (!typeArgument.isDynamic && typeArgument != bound) return true; } return false; From 8ac980bd3664ba8324a086672ecc2360493ce67c Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 16 Aug 2022 04:51:02 -0400 Subject: [PATCH 453/607] Check the annotation AST for presence of explicit type args Assuming `Foo` is declared as `class Foo { /* ... */ }`, there is indeed no way to take apart `Foo` from `Foo` once it gets into the analyzer internals and becomes an `InterfaceType`. The AST still preserve this information though but we are not looking at the annotation AST, instead we utilize the `computeContantValue` that gives us `DartObject` that is easier to work with but now `Foo` and `Foo` are smashed together. It appears to me that there is currently even no public API to get the AST for the annotation, so I had to cheat with ```dart (annotation as ElementAnnotationImpl).annotationAst ``` to get the actual AST. When we can use it to just check the presence of type arguments and pass the results down. This is still rather unclean, not using the best practice and doesn't work in all cases where constant evaluation does. For example, with constant evaluation one can declared `MockSpec` as a constant outside of the annotation and then use it, but it breaks with direct AST access. I haven't found any other examples so far. Not being able to use `MockSpec`s defined as constants doesn't really look like a big deal though: there is no reasonable way to use a `MockSpec` instance more than once anyway, I think. On the positive side, this change fixes the bug without changing the API. Fixes https://github.com/dart-lang/mockito/issues/559 and https://github.com/dart-lang/mockito/issues/563. Also opens a way for fixing https://github.com/dart-lang/mockito/issues/562. PiperOrigin-RevId: 467867637 --- pkgs/mockito/lib/src/builder.dart | 94 +++++++++++-------- .../test/builder/custom_mocks_test.dart | 22 +++++ 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 11aaab619..345c68924 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -14,6 +14,7 @@ import 'dart:collection'; +import 'package:analyzer/dart/ast/ast.dart' as ast; import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; @@ -22,6 +23,9 @@ import 'package:analyzer/dart/element/type_provider.dart'; import 'package:analyzer/dart/element/type_system.dart'; import 'package:analyzer/dart/element/visitor.dart'; // ignore: implementation_imports +import 'package:analyzer/src/dart/element/element.dart' + show ElementAnnotationImpl; +// ignore: implementation_imports import 'package:analyzer/src/dart/element/inheritance_manager3.dart' show InheritanceManager3, Name; // ignore: implementation_imports @@ -373,6 +377,10 @@ class _MockTarget { final Map fallbackGenerators; + /// Instantiated mock was requested, i.e. `MockSpec>`, + /// instead of `MockSpec`. + final bool hasExplicitTypeArguments; + _MockTarget( this.classType, this.mockName, { @@ -380,6 +388,7 @@ class _MockTarget { required this.onMissingStub, required this.unsupportedMembers, required this.fallbackGenerators, + this.hasExplicitTypeArguments = false, }); InterfaceElement get classElement => classType.element2; @@ -437,6 +446,27 @@ class _MockTargetGatherer { entryLib, mockTargets.toList(), inheritanceManager); } + static bool _hasExplicitTypeArgs(ast.CollectionElement mockSpec) { + if (mockSpec is! ast.InstanceCreationExpression) { + throw InvalidMockitoAnnotationException( + 'Mockspecs must be constructor calls inside the annotation, ' + 'please inline them if you are using a variable'); + } + return (mockSpec.constructorName.type2.typeArguments?.arguments.firstOrNull + as ast.NamedType?) + ?.typeArguments != + null; + } + + static ast.ListLiteral? _customMocksAst(ast.Annotation annotation) => + (annotation.arguments!.arguments + .firstWhereOrNull((arg) => arg is ast.NamedExpression) + as ast.NamedExpression?) + ?.expression as ast.ListLiteral?; + + static ast.ListLiteral _niceMocksAst(ast.Annotation annotation) => + annotation.arguments!.arguments.first as ast.ListLiteral; + static Iterable<_MockTarget> _mockTargetsFromGenerateMocks( ElementAnnotation annotation, LibraryElement entryLib) { final generateMocksValue = annotation.computeConstantValue()!; @@ -476,15 +506,21 @@ class _MockTargetGatherer { } final customMocksField = generateMocksValue.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { + final customMocksAsts = + _customMocksAst(annotation.annotationAst)?.elements ?? + []; mockTargets.addAll(customMocksField.toListValue()!.mapIndexed( - (index, mockSpec) => - _mockTargetFromMockSpec(mockSpec, entryLib, index))); + (index, mockSpec) => _mockTargetFromMockSpec( + mockSpec, entryLib, index, customMocksAsts.toList()))); } return mockTargets; } static _MockTarget _mockTargetFromMockSpec( - DartObject mockSpec, LibraryElement entryLib, int index, + DartObject mockSpec, + LibraryElement entryLib, + int index, + List mockSpecAsts, {bool nice = false}) { final mockSpecType = mockSpec.type as analyzer.InterfaceType; assert(mockSpecType.typeArguments.length == 1); @@ -496,8 +532,8 @@ class _MockTargetGatherer { } var type = _determineDartType(typeToMock, entryLib.typeProvider); - if (!type.hasExplicitTypeArguments) { - // We assume the type was given without explicit type arguments. In + if (!_hasExplicitTypeArgs(mockSpecAsts[index])) { + // The type was given without explicit type arguments. In // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. @@ -581,6 +617,7 @@ class _MockTargetGatherer { onMissingStub: onMissingStub, unsupportedMembers: unsupportedMembers, fallbackGenerators: _extractFallbackGenerators(fallbackGeneratorObjects), + hasExplicitTypeArguments: _hasExplicitTypeArgs(mockSpecAsts[index]), ); } @@ -592,8 +629,11 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'The GenerateNiceMocks "mockSpecs" argument is missing'); } + final mockSpecAsts = _niceMocksAst(annotation.annotationAst).elements; return mockSpecsField.toListValue()!.mapIndexed((index, mockSpec) => - _mockTargetFromMockSpec(mockSpec, entryLib, index, nice: true)); + _mockTargetFromMockSpec( + mockSpec, entryLib, index, mockSpecAsts.toList(), + nice: true)); } static Map _extractFallbackGenerators( @@ -1006,7 +1046,7 @@ class _MockClassInfo { // parameter with same type variables, and a mirrored type argument for // the "implements" clause. var typeArguments = []; - if (typeToMock.hasExplicitTypeArguments) { + if (mockTarget.hasExplicitTypeArguments) { // [typeToMock] is a reference to a type with type arguments (for // example: `Foo`). Generate a non-generic mock class which // implements the mock target with said type arguments. For example: @@ -1968,38 +2008,6 @@ extension on analyzer.DartType { } } -extension on analyzer.InterfaceType { - bool get hasExplicitTypeArguments { - // If it appears that one type argument was given, then they all were. This - // returns the wrong result when the type arguments given are all `dynamic`, - // or are each equal to the bound of the corresponding type parameter. There - // may not be a way to get around this. - for (var i = 0; i < typeArguments.length; i++) { - final typeArgument = typeArguments[i]; - // If [typeArgument] is a type parameter, this indicates that no type - // arguments were passed. This likely came from the 'classes' argument of - // GenerateMocks, and [type] is the declaration type (`Foo` vs - // `Foo`). - if (typeArgument is analyzer.TypeParameterType) return false; - - // If [type] was given to @GenerateMocks as a Type, and no explicit type - // argument is given, [typeArgument] is `dynamic` (_not_ the bound, as one - // might think). We determine that an explicit type argument was given if - // it is not `dynamic`. - if (typeArgument.isDynamic) continue; - - // If, on the other hand, [type] was given to @GenerateMock as a type - // argument to `MockSpec()`, and no type argument is given, [typeArgument] - // is the bound of the corresponding type paramter (`dynamic` or - // otherwise). We determine that an explicit type argument was given if - // [typeArgument] is not equal to [bound]. - final bound = element2.typeParameters[i].bound; - if (!typeArgument.isDynamic && typeArgument != bound) return true; - } - return false; - } -} - extension on TypeSystem { bool _returnTypeIsNonNullable(ExecutableElement method) => isPotentiallyNonNullable(method.returnType); @@ -2037,3 +2045,11 @@ extension on int { bool _needsOverrideForVoidStub(ExecutableElement method) => method.returnType.isVoid || method.returnType.isFutureOfVoid; + +/// This casts `ElementAnnotation` to the internal `ElementAnnotationImpl` +/// class, since analyzer doesn't provide public interface to access +/// the annotation AST currently. +extension on ElementAnnotation { + ast.Annotation get annotationAst => + (this as ElementAnnotationImpl).annotationAst; +} diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 8702d9b89..8636cfe06 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -199,6 +199,28 @@ void main() { 'class MockFooOfIntBar extends _i1.Mock implements _i2.Foo')); }); + test('generates a generic mock class with lower bound type arguments', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks( + [], customMocks: [MockSpec>(as: #MockFoo)]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock implements _i2.Foo')); + }); + test('generates a generic mock class with nullable type arguments', () async { var mocksContent = await buildWithNonNullable({ ...annotationsAsset, From 9b6ccdad305e51203da67742fdc50c23cf255e88 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 16 Aug 2022 05:35:37 -0400 Subject: [PATCH 454/607] Check for type arguments that turned into `dynamic` Most likely because they were referring to to-be-defined mocks. Kinda fixes https://github.com/dart-lang/mockito/issues/562 PiperOrigin-RevId: 467875322 --- pkgs/mockito/lib/src/builder.dart | 33 +++++++++++++++---- .../test/builder/custom_mocks_test.dart | 19 +++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 345c68924..1f16a1784 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -446,16 +446,16 @@ class _MockTargetGatherer { entryLib, mockTargets.toList(), inheritanceManager); } - static bool _hasExplicitTypeArgs(ast.CollectionElement mockSpec) { + static ast.TypeArgumentList? _mockTypeArguments( + ast.CollectionElement mockSpec) { if (mockSpec is! ast.InstanceCreationExpression) { throw InvalidMockitoAnnotationException( 'Mockspecs must be constructor calls inside the annotation, ' 'please inline them if you are using a variable'); } return (mockSpec.constructorName.type2.typeArguments?.arguments.firstOrNull - as ast.NamedType?) - ?.typeArguments != - null; + as ast.NamedType?) + ?.typeArguments; } static ast.ListLiteral? _customMocksAst(ast.Annotation annotation) => @@ -532,12 +532,31 @@ class _MockTargetGatherer { } var type = _determineDartType(typeToMock, entryLib.typeProvider); - if (!_hasExplicitTypeArgs(mockSpecAsts[index])) { + final mockTypeArguments = _mockTypeArguments(mockSpecAsts[index]); + if (mockTypeArguments == null) { // The type was given without explicit type arguments. In // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. - type = (type.element2.declaration as ClassElement).thisType; + type = (type.element.declaration as ClassElement).thisType; + } else { + // Check explicit type arguments for unknown types that were + // turned into `dynamic` by the analyzer. + type.typeArguments.forEachIndexed((typeArgIdx, typeArgument) { + if (!typeArgument.isDynamic) return; + if (typeArgIdx >= mockTypeArguments.arguments.length) return; + final typeArgAst = mockTypeArguments.arguments[typeArgIdx]; + if (typeArgAst is! ast.NamedType) { + // Is this even possible? + throw InvalidMockitoAnnotationException( + 'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type'); + } + if (typeArgAst.name.name == 'dynamic') return; + throw InvalidMockitoAnnotationException( + 'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type. ' + 'Are you trying to pass to-be-generated mock class as a type argument? Mockito does not support that (yet).', + ); + }); } final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? 'Mock${type.element2.name}'; @@ -617,7 +636,7 @@ class _MockTargetGatherer { onMissingStub: onMissingStub, unsupportedMembers: unsupportedMembers, fallbackGenerators: _extractFallbackGenerators(fallbackGeneratorObjects), - hasExplicitTypeArguments: _hasExplicitTypeArgs(mockSpecAsts[index]), + hasExplicitTypeArguments: mockTypeArguments != null, ); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 8636cfe06..d8ce8f6ed 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1030,6 +1030,25 @@ void main() { ); }); + test('throws when type argument is unknown type', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + class Bar {} + class Foo {} + '''), + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + import 'package:foo/foo.dart'; + @GenerateMocks([Bar], customMocks: [MockSpec>()]) + void main() {} + '''), + }, + message: contains('Undefined type MockBar'), + ); + }); + test('given a pre-non-nullable library, does not override any members', () async { await testPreNonNullable( From 063b4ee220a9aec7746377e2b4fa4c519bc610f3 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 18 Aug 2022 04:16:47 -0400 Subject: [PATCH 455/607] Improve the error message in the case of an unknown type-to-mock. The error message is improved for misspelled or not-yet-imported types, and for missing type arguments. PiperOrigin-RevId: 468394995 --- pkgs/mockito/lib/src/builder.dart | 27 ++++++++++++------- .../test/builder/custom_mocks_test.dart | 18 ++++++++++++- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 1f16a1784..190296578 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -446,16 +446,14 @@ class _MockTargetGatherer { entryLib, mockTargets.toList(), inheritanceManager); } - static ast.TypeArgumentList? _mockTypeArguments( - ast.CollectionElement mockSpec) { + static ast.NamedType? _mockType(ast.CollectionElement mockSpec) { if (mockSpec is! ast.InstanceCreationExpression) { throw InvalidMockitoAnnotationException( - 'Mockspecs must be constructor calls inside the annotation, ' + 'MockSpecs must be constructor calls inside the annotation, ' 'please inline them if you are using a variable'); } - return (mockSpec.constructorName.type2.typeArguments?.arguments.firstOrNull - as ast.NamedType?) - ?.typeArguments; + return mockSpec.constructorName.type2.typeArguments?.arguments.firstOrNull + as ast.NamedType?; } static ast.ListLiteral? _customMocksAst(ast.Annotation annotation) => @@ -524,15 +522,24 @@ class _MockTargetGatherer { {bool nice = false}) { final mockSpecType = mockSpec.type as analyzer.InterfaceType; assert(mockSpecType.typeArguments.length == 1); + final mockType = _mockType(mockSpecAsts[index]); final typeToMock = mockSpecType.typeArguments.single; if (typeToMock.isDynamic) { - throw InvalidMockitoAnnotationException( - 'Mockito cannot mock `dynamic`; be sure to declare a type argument ' - 'on the ${(index + 1).ordinal} MockSpec() in @GenerateMocks.'); + final mockTypeName = mockType?.name.name; + if (mockTypeName == null) { + throw InvalidMockitoAnnotationException( + 'MockSpec requires a type argument to determine the class to mock. ' + 'Be sure to declare a type argument on the ${(index + 1).ordinal} ' + 'MockSpec() in @GenerateMocks.'); + } else { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock unknown type `$mockTypeName`. Did you ' + 'misspell it or forget to add a dependency on it?'); + } } var type = _determineDartType(typeToMock, entryLib.typeProvider); - final mockTypeArguments = _mockTypeArguments(mockSpecAsts[index]); + final mockTypeArguments = mockType?.typeArguments; if (mockTypeArguments == null) { // The type was given without explicit type arguments. In // this case the type argument(s) on `type` have been instantiated to diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index d8ce8f6ed..5f8cb792e 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -842,7 +842,23 @@ void main() { void main() {} '''), }, - message: contains('Mockito cannot mock `dynamic`'), + message: contains( + 'MockSpec requires a type argument to determine the class to mock'), + ); + }); + + test('throws when MockSpec() is given an unknown type argument', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + import 'package:mockito/annotations.dart'; + // Missing required type argument to MockSpec. + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains('Mockito cannot mock unknown type `Unknown`'), ); }); From e61e2ee4a09cde5b27c7738c82dbe12b6690ef6a Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 22 Aug 2022 12:18:37 -0400 Subject: [PATCH 456/607] Reference `@GenerateNiceMocks` in documentation. Also quick changes to required newer analyzer and code_builder deps. PiperOrigin-RevId: 469202917 --- pkgs/mockito/CHANGELOG.md | 5 ++- pkgs/mockito/NULL_SAFETY_README.md | 54 +++++++++++++++++++----------- pkgs/mockito/README.md | 36 +++++++++++++++++--- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 +-- 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 7172f6819..82dabd66a 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,7 @@ -## 5.3.1-dev +## 5.3.1 + +* Fix analyzer and code_builder dependencies. +* Reference `@GenerateNiceMocks` in documentation. ## 5.3.0 diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 99d4d031c..616fbd2fa 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -44,8 +44,8 @@ manually implement it, overriding specific methods to handle non-nullability. Mockito provides a "one size fits all" code-generating solution for packages that use null safety which can generate a mock for any class. To direct Mockito -to generate mock classes, use the new `@GenerateMocks` annotation, and import -the generated mocks library. Let's continue with the HTTP server example: +to generate mock classes, use the new `@GenerateNiceMocks` annotation, and +import the generated mocks library. Let's continue with the HTTP server example: ```dart // http_server.dart: @@ -74,7 +74,7 @@ In order to generate a mock for the HttpServer class, we edit 1. import mockito's annotations library, 2. import the generated mocks library, -3. annotate the import with `@GenerateMocks`, +3. annotate the import with `@GenerateNiceMocks`, 4. change `httpServer` from an HttpServer to the generated class, MockHttpServer. @@ -84,7 +84,7 @@ import 'package:mockito/annotations.dart'; import 'package:test/test.dart'; import 'http_server.dart'; -@GenerateMocks([HttpServer]) +@GenerateNiceMocks([MockSpec()]) import 'http_server_test.mocks.dart'; void main() { @@ -108,17 +108,17 @@ dart run build_runner build build_runner will generate the `http_server_test.mocks.dart` file which we import in `http_server_test.dart`. The path is taken directly from the file in -which `@GenerateMocks` was found, changing the `.dart` suffix to `.mocks.dart`. -If we previously had a shared mocks file which declared mocks to be used by -multiple tests, for example named `shared_mocks.dart`, we can edit that file to -generate mocks, and then import `shared_mocks.mocks.dart` in the tests which -previously imported `shared_mocks.dart`. +which `@GenerateNiceMocks` was found, changing the `.dart` suffix to +`.mocks.dart`. If we previously had a shared mocks file which declared mocks to +be used by multiple tests, for example named `shared_mocks.dart`, we can edit +that file to generate mocks, and then import `shared_mocks.mocks.dart` in the +tests which previously imported `shared_mocks.dart`. ### Custom generated mocks Mockito might need some additional input in order to generate the right mock for certain use cases. We can generate custom mock classes by passing MockSpec -objects to the `customMocks` list argument in `@GenerateMocks`. +objects to the `customMocks` list argument in `@GenerateNiceMocks`. #### Mock with a custom name @@ -126,7 +126,7 @@ Use MockSpec's constructor's `as` named parameter to use a non-standard name for the mock class. For example: ```dart -@GenerateMocks([], customMocks: [MockSpec(as: #BaseMockFoo)]) +@GenerateNiceMocks([MockSpec(as: #BaseMockFoo)]) ``` Mockito will generate a mock class called `BaseMockFoo`, instead of the default, @@ -139,16 +139,29 @@ To generate a mock class which extends a class with type arguments, specify them on MockSpec's type argument: ```dart -@GenerateMocks([], customMocks: [MockSpec>(as: #MockFooOfInt)]) +@GenerateNiceMocks([MockSpec>(as: #MockFooOfInt)]) ``` Mockito will generate `class MockFooOfInt extends Mock implements Foo`. -#### Old "missing stub" behavior +#### Classic "throw an exception" missing stub behavior -When a method of a generated mock class is called, which does not match any -method stub created with the `when` API, the call will throw an exception. To -use the old default behavior of returning null (which doesn't make a lot of +When a mock class generated with `@GenerateNiceMocks` receives a method call +which does not match any method stub created with the `when` API, a simple, +legal default value is returned. This is the default and recommended +"missing stub" behavior. + +There is a "classic" "missing stub" behavior, which is to throw an exception +when such a method call is received. To generate a mock class with this +behavior, use `@GenerateMocks`: + +```dart +@GenerateMocks([Foo]) +``` + +#### Old "return null" missing stub behavior + + To use the old default behavior of returning null (which doesn't make a lot of sense in the Null safety type system), for legacy code, use `returnNullOnMissingStub`: @@ -156,6 +169,9 @@ sense in the Null safety type system), for legacy code, use @GenerateMocks([], customMocks: [MockSpec(returnNullOnMissingStub: true)]) ``` +This option is soft-deprecated (no deprecation warning yet); it will be marked +`@deprecated` in a future release, and removed in a later release. + #### Mocking members with non-nullable type variable return types If a class has a member with a type variable as a return type (for example, @@ -163,7 +179,7 @@ If a class has a member with a type variable as a return type (for example, valid value. For example, given this class and test: ```dart -@GenerateMocks([], customMocks: [MockSpec(as: #MockFoo)]) +@GenerateNiceMocks([MockSpec(as: #MockFoo)]) import 'foo_test.mocks.dart'; abstract class Foo { @@ -183,7 +199,7 @@ One way to generate a mock class with such members is to use `unsupportedMembers`: ```dart -@GenerateMocks([], customMocks: [ +@GenerateNiceMocks([ MockSpec(unsupportedMembers: {#method}), ]) ``` @@ -203,7 +219,7 @@ return type as the member, and it must have the same positional and named parameters as the member, except that each parameter must be made nullable: ```dart -@GenerateMocks([], customMocks: [ +@GenerateNiceMocks([ MockSpec(as: #MockFoo, fallbackGenerators: {#m: mShim}) ]) import 'foo_test.mocks.dart'; diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 99640c6b8..47d394fcf 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -23,7 +23,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; // Annotation which generates the cat.mocks.dart library and the MockCat class. -@GenerateMocks([Cat]) +@GenerateNiceMocks([MockSpec()]) import 'cat.mocks.dart'; // Real class @@ -43,9 +43,9 @@ void main() { } ``` -By annotating the import of a `.mocks.dart` library with `@GenerateMocks`, you -are directing Mockito's code generation to write a mock class for each "real" -class listed, in a new library. +By annotating the import of a `.mocks.dart` library with `@GenerateNiceMocks`, +you are directing Mockito's code generation to write a mock class for each +"real" class listed, in a new library. The next step is to run `build_runner` in order to generate this new library: @@ -56,7 +56,7 @@ dart run build_runner build ``` `build_runner` will generate a file with a name based on the file containing the -`@GenerateMocks` annotation. In the above `cat.dart` example, we import the +`@GenerateNiceMocks` annotation. In the above `cat.dart` example, we import the generated library as `cat.mocks.dart`. The generated mock class, `MockCat`, extends Mockito's Mock class and implements @@ -315,6 +315,32 @@ cat.eatFood("Fish"); await untilCalled(cat.eatFood(any)); // Completes immediately. ``` +## Nice mocks vs classic mocks + +Mockito provides two APIs for generating mocks, the `@GenerateNiceMocks` +annotation and the `@GenerateMocks` annotation. **The recommended API is +`@GenerateNiceMocks`.** The difference between these two APIs is in the behavior +of a generated mock class when a method is called and no stub could be found. +For example: + +```dart +void main() { + var cat = MockCat(); + cat.sound(); +} +``` + +The `Cat.sound` method retuns a non-nullable String, but no stub has been made +with `when(cat.sound())`, so what should the code do? What is the "missing stub" +behavior? + +* The "missing stub" behavior of a mock class generated with `@GenerateMocks` is + to throw an exception. +* The "missing stub" behavior of a mock class generated with + `@GenerateNiceMocks` is to return a "simple" legal value (for example, a + non-`null` value for a non-nullable return type). The value should not be used + in any way; it is returned solely to avoid a runtime type exception. + ## Writing a fake You can also write a simple fake class that implements a real class, by diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 005c67d59..984dcec8c 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.1-dev'; +const packageVersion = '5.3.1'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 60f21ea00..3443f15e9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.1-dev +version: 5.3.1 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -9,7 +9,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=2.1.0 <5.0.0' + analyzer: '>=4.6.0 <5.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.2.0 collection: ^1.15.0 From 8ad7804e3baeac8d089df45743b32140524a0776 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 22 Aug 2022 18:03:02 -0400 Subject: [PATCH 457/607] Stop using deprecated declarations, prepare for analyzer breaking changes. PiperOrigin-RevId: 469292776 --- pkgs/mockito/lib/src/builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 190296578..9ea6da55f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -452,7 +452,7 @@ class _MockTargetGatherer { 'MockSpecs must be constructor calls inside the annotation, ' 'please inline them if you are using a variable'); } - return mockSpec.constructorName.type2.typeArguments?.arguments.firstOrNull + return mockSpec.constructorName.type.typeArguments?.arguments.firstOrNull as ast.NamedType?; } @@ -545,7 +545,7 @@ class _MockTargetGatherer { // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. - type = (type.element.declaration as ClassElement).thisType; + type = (type.element2.declaration as ClassElement).thisType; } else { // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. From 759dd6c54a555d95b102430bb11cd7de81759edd Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 23 Aug 2022 20:31:04 -0400 Subject: [PATCH 458/607] code_builder import from GitHub. The big change affecting all of the goldens is that we now add trailing commas. The motivation is that code with trailing commas can avoid pathological corners of the formatter, which can save entire seconds in generating some Dart source code. See details at: https://github.com/dart-lang/code_builder/pull/376 - f635ab6e2776955d057cf75757905f73f76adbd9 Add support for trailing commas in Emitter (dart-lang/mockito#376) by Sam Rawlins - e082adb3e01d619370e7ec14b5f8ae6ac8ecf903 Fix spelling in README.md (dart-lang/mockito#360) by Saint Gabriel <53136855+chineduG@users.noreply.github.com> - bed3ca90fb5a42b2b4c7fc93a8d73652a06d4314 Fix spelling in CHANGELOG.md (dart-lang/mockito#361) by Saint Gabriel <53136855+chineduG@users.noreply.github.com> PiperOrigin-RevId: 469597645 --- .../mockito/test/builder/auto_mocks_test.dart | 680 ++++++++++-------- .../test/builder/custom_mocks_test.dart | 83 +-- 2 files changed, 409 insertions(+), 354 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 376b7cb6e..cb2c0849e 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -212,8 +212,7 @@ void main() { void m(int a) {} } '''), - _containsAllOf( - 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(int? a) => super.noSuchMethod('), ); }); @@ -224,8 +223,13 @@ void main() { void m(int a, [int b, int c = 0]) {} } '''), - _containsAllOf('void m(int? a, [int? b, int? c = 0]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b, c])'), + _containsAllOf(dedent2(''' + void m( + int? a, [ + int? b, + int? c = 0, + ]) => + ''')), ); }); @@ -236,8 +240,13 @@ void main() { void m(int a, {int b, int c = 0}) {} } '''), - _containsAllOf('void m(int? a, {int? b, int? c = 0}) =>', - 'super.noSuchMethod(Invocation.method(#m, [a], {#b: b, #c: c})'), + _containsAllOf(dedent2(''' + void m( + int? a, { + int? b, + int? c = 0, + }) => + ''')), ); }); @@ -248,8 +257,11 @@ void main() { void m([int a, int b = 0]) {} } '''), - _containsAllOf('void m([int? a, int? b = 0]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b])'), + _containsAllOf(dedent2(''' + void m([ + int? a, + int? b = 0, + ]) =>''')), ); }); @@ -260,8 +272,12 @@ void main() { void m([bool a = true, bool b = false]) {} } '''), - _containsAllOf('void m([bool? a = true, bool? b = false]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b])'), + _containsAllOf(dedent2(''' + void m([ + bool? a = true, + bool? b = false, + ]) => + ''')), ); }); @@ -272,8 +288,12 @@ void main() { void m([int a = 0, double b = 0.5]) {} } '''), - _containsAllOf('void m([int? a = 0, double? b = 0.5]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b])'), + _containsAllOf(dedent2(''' + void m([ + int? a = 0, + double? b = 0.5, + ]) => + ''')), ); }); @@ -284,9 +304,12 @@ void main() { void m([String a = 'Hello', String b = 'Hello ' r"World"]) {} } '''), - _containsAllOf( - "void m([String? a = r'Hello', String? b = r'Hello World']) =>", - 'super.noSuchMethod(Invocation.method(#m, [a, b])'), + _containsAllOf(dedent2(''' + void m([ + String? a = r'Hello', + String? b = r'Hello World', + ]) => + ''')), ); }); @@ -297,9 +320,12 @@ void main() { void m([List a = const [], Map b = const {}]) {} } '''), - _containsAllOf( - 'void m([List? a = const [], Map? b = const {}]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a, b])'), + _containsAllOf(dedent2(''' + void m([ + List? a = const [], + Map? b = const {}, + ]) => + ''')), ); }); @@ -310,8 +336,14 @@ void main() { void m([List a = const [1, 2, 3]]) {} } '''), - _containsAllOf('void m([List? a = const [1, 2, 3]]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf(dedent2(''' + void m( + [List? a = const [ + 1, + 2, + 3, + ]]) => + ''')), ); }); @@ -322,9 +354,13 @@ void main() { void m([Map a = const {1: 'a', 2: 'b'}]) {} } '''), - _containsAllOf( - "void m([Map? a = const {1: r'a', 2: r'b'}]) =>", - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf(dedent2(''' + void m( + [Map? a = const { + 1: r'a', + 2: r'b', + }]) => + ''')), ); }); @@ -335,9 +371,13 @@ void main() { void m([Map a = const {1: 'a', 2: 'b'}]) {} } '''), - _containsAllOf( - "void m([Map? a = const {1: r'a', 2: r'b'}]) =>", - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf(dedent2(''' + void m( + [Map? a = const { + 1: r'a', + 2: r'b', + }]) => + ''')), ); }); @@ -352,8 +392,8 @@ void main() { const Bar(); } '''), - _containsAllOf('void m([_i2.Bar? a = const _i2.Bar()]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf( + 'void m([_i2.Bar? a = const _i2.Bar()]) => super.noSuchMethod('), ); }); @@ -365,8 +405,8 @@ void main() { void m([Duration a = const Duration(days: 1)]) {} } '''), - _containsAllOf('void m([Duration? a = const Duration(days: 1)]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf( + 'void m([Duration? a = const Duration(days: 1)]) => super.noSuchMethod('), ); }); @@ -381,8 +421,8 @@ void main() { const Bar.named(); } '''), - _containsAllOf('void m([_i2.Bar? a = const _i2.Bar.named()]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf( + 'void m([_i2.Bar? a = const _i2.Bar.named()]) => super.noSuchMethod('), ); }); @@ -398,8 +438,8 @@ void main() { const Bar(this.i); } '''), - _containsAllOf('void m([_i2.Bar? a = const _i2.Bar(7)]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf( + 'void m([_i2.Bar? a = const _i2.Bar(7)]) => super.noSuchMethod('), ); }); @@ -415,8 +455,8 @@ void main() { const Bar({this.i}); } '''), - _containsAllOf('void m([_i2.Bar? a = const _i2.Bar(i: 7)]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf( + 'void m([_i2.Bar? a = const _i2.Bar(i: 7)]) => super.noSuchMethod('), ); }); @@ -429,8 +469,7 @@ void main() { } const x = 1; '''), - _containsAllOf( - 'void m([int? a = 1]) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m([int? a = 1]) => super.noSuchMethod('), ); }); @@ -444,8 +483,8 @@ void main() { void m([Callback a = defaultCallback]) {} } '''), - _containsAllOf('void m([_i2.Callback? a = _i2.defaultCallback]) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf( + 'void m([_i2.Callback? a = _i2.defaultCallback]) => super.noSuchMethod('), ); }); @@ -458,8 +497,7 @@ void main() { void m([int a = x]) {} } '''), - _containsAllOf( - 'void m([int? a = 1]) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m([int? a = 1]) => super.noSuchMethod('), ); }); @@ -557,11 +595,7 @@ void main() { Future m() async => print(s); } '''), - _containsAllOf(dedent2(''' - _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Future.value(), - returnValueForMissingStub: _i3.Future.value()) as _i3.Future); - ''')), + _containsAllOf('_i3.Future m() => (super.noSuchMethod('), ); }); @@ -572,10 +606,7 @@ void main() { Stream m() async* { yield 7; } } '''), - _containsAllOf(dedent2(''' - _i3.Stream m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Stream.empty()) as _i3.Stream); - ''')), + _containsAllOf('_i3.Stream m() => (super.noSuchMethod('), ); }); @@ -586,11 +617,7 @@ void main() { Iterable m() sync* { yield 7; } } '''), - _containsAllOf(dedent2(''' - Iterable m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: []) - as Iterable); - ''')), + _containsAllOf('Iterable m() => (super.noSuchMethod('), ); }); @@ -602,8 +629,7 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf( - 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(int? a) => super.noSuchMethod('), ); }); @@ -616,8 +642,7 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf( - 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(int? a) => super.noSuchMethod('), ); }); @@ -629,8 +654,7 @@ void main() { } class Foo with Mixin {} '''), - _containsAllOf( - 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(int? a) => super.noSuchMethod('), ); }); @@ -643,8 +667,7 @@ void main() { class FooBase with Mixin {} class Foo extends FooBase {} '''), - _containsAllOf( - 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(int? a) => super.noSuchMethod('), ); }); @@ -662,8 +685,7 @@ void main() { } class Foo with MixinConstraint, Mixin {} '''), - _containsAllOf( - 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(num? a) => super.noSuchMethod('), ); }); @@ -675,8 +697,7 @@ void main() { } class Foo implements Interface {} '''), - _containsAllOf( - 'void m(int? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(int? a) => super.noSuchMethod('), ); }); @@ -688,13 +709,10 @@ void main() { } class Foo implements Interface {} '''), - _containsAllOf(dedent2(''' - int get m => - (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); - '''), dedent2(''' - set m(int? _m) => super - .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); - ''')), + _containsAllOf( + 'int get m => (super.noSuchMethod(', + 'set m(int? _m) => super.noSuchMethod(', + ), ); }); @@ -709,8 +727,7 @@ void main() { class FooBase1 extends FooBase2 {} class Foo extends FooBase2 {} '''), - _containsAllOf( - 'void m(int? a) =>', 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(int? a) => super.noSuchMethod('), ); }); @@ -722,8 +739,7 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf('void m() => super', - '.noSuchMethod(Invocation.method(#m, []), returnValueForMissingStub: null);'), + _containsAllOf('void m() => super.noSuchMethod('), ); }); @@ -735,8 +751,7 @@ void main() { } class Foo extends FooBase {} '''), - _containsAllOf( - 'void m(T? a) =>', 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(T? a) => super.noSuchMethod('), ); }); @@ -751,12 +766,8 @@ void main() { } '''), _containsAllOf( - ' void List(int? a) => super.noSuchMethod(Invocation.method(#List, [a]),\n', - dedent2(''' - _i3.List m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: []) - as _i3.List); - '''), + ' void List(int? a) => super.noSuchMethod(\n', + ' _i3.List m() => (super.noSuchMethod(\n', )); }); @@ -991,9 +1002,12 @@ void main() { void m(dynamic a, int b) {} } '''), - _containsAllOf( - 'void m(dynamic a, int? b) => super.noSuchMethod(Invocation.method(#m, [a, b])', - ), + _containsAllOf(dedent2(''' + void m( + dynamic a, + int? b, + ) => + ''')), ); }); @@ -1004,8 +1018,7 @@ void main() { void m(T Function() a) {} } '''), - _containsAllOf( - 'void m(T Function()? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(T Function()? a) => super.noSuchMethod('), ); }); @@ -1016,9 +1029,7 @@ void main() { void m(T a) {} } '''), - _containsAllOf( - 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a])', - ), + _containsAllOf('void m(T? a) => super.noSuchMethod('), ); }); @@ -1327,8 +1338,8 @@ void main() { dynamic m(void Function(Foo f) a) {} } '''), - _containsAllOf('dynamic m(void Function(_i2.Foo)? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'dynamic m(void Function(_i2.Foo)? a) => super.noSuchMethod(Invocation.method('), ); }); @@ -1340,8 +1351,7 @@ void main() { void m(Foo Function() a) {} } '''), - _containsAllOf('void m(_i2.Foo Function()? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(_i2.Foo Function()? a) => super.noSuchMethod('), ); }); @@ -1353,8 +1363,8 @@ void main() { void m(void a(Foo f)) {} } '''), - _containsAllOf('void m(void Function(_i2.Foo)? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf( + 'void m(void Function(_i2.Foo)? a) => super.noSuchMethod('), ); }); @@ -1366,8 +1376,7 @@ void main() { void m(Foo a()) {} } '''), - _containsAllOf('void m(_i2.Foo Function()? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(_i2.Foo Function()? a) => super.noSuchMethod('), ); }); @@ -1378,8 +1387,12 @@ void main() { void m(int? a, int b); } '''), - _containsAllOf( - 'void m(int? a, int? b) => super.noSuchMethod(Invocation.method(#m, [a, b])'), + _containsAllOf(dedent2(''' + void m( + int? a, + int? b, + ) => + ''')), ); }); @@ -1392,8 +1405,12 @@ void main() { void m(int? a, T b); } '''), - _containsAllOf( - 'void m(int? a, T? b) => super.noSuchMethod(Invocation.method(#m, [a, b])'), + _containsAllOf(dedent2(''' + void m( + int? a, + T? b, + ) => + ''')), ); }); @@ -1407,8 +1424,7 @@ void main() { void m(covariant int a); } '''), - _containsAllOf( - 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(num? a) => super.noSuchMethod('), ); }); @@ -1424,8 +1440,7 @@ void main() { void m([covariant int a = 0]); } '''), - _containsAllOf( - 'void m([num? a = 0]) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m([num? a = 0]) => super.noSuchMethod('), ); }); @@ -1441,8 +1456,7 @@ void main() { void m(int a); } '''), - _containsAllOf( - 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(num? a) => super.noSuchMethod('), ); }); @@ -1460,8 +1474,7 @@ void main() { void m(covariant int a); } '''), - _containsAllOf( - 'void m(Object? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(Object? a) => super.noSuchMethod('), ); }); @@ -1477,8 +1490,7 @@ void main() { void m(covariant int a); } '''), - _containsAllOf( - 'void m(num? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m(num? a) => super.noSuchMethod('), ); }); @@ -1497,8 +1509,7 @@ void main() { void m([covariant int a]); } '''), - _containsAllOf( - 'void m([num? a]) => super.noSuchMethod(Invocation.method(#m, [a])'), + _containsAllOf('void m([num? a]) => super.noSuchMethod('), ); }); @@ -1513,8 +1524,7 @@ void main() { void m({required covariant int a}); } '''), - _containsAllOf( - 'void m({num? a}) => super.noSuchMethod(Invocation.method(#m, [], {#a: a})'), + _containsAllOf('void m({num? a}) => super.noSuchMethod('), ); }); @@ -1525,7 +1535,12 @@ void main() { void m(List a, List b); } '''), - _containsAllOf('void m(List? a, List? b) =>'), + _containsAllOf(dedent2(''' + void m( + List? a, + List? b, + ) => + ''')), ); }); @@ -1538,7 +1553,12 @@ void main() { void m(int? Function() a, int Function() b); } '''), - _containsAllOf('void m(int? Function()? a, int Function()? b) =>'), + _containsAllOf(dedent2(''' + void m( + int? Function()? a, + int Function()? b, + ) => + ''')), ); }); @@ -1552,9 +1572,7 @@ void main() { FutureOr m(); } '''), - _containsAllOf( - '_i3.FutureOr m() => (super.noSuchMethod(Invocation.method(#m, []),', - ' returnValue: _i3.Future.value(null)) as _i3.FutureOr);'), + _containsAllOf('_i3.FutureOr m() => (super.noSuchMethod('), ); }); @@ -1567,8 +1585,12 @@ void main() { void m(void Function(int?) a, void Function(int) b); } '''), - _containsAllOf( - 'void m(void Function(int?)? a, void Function(int)? b) =>'), + _containsAllOf(dedent2(''' + void m( + void Function(int?)? a, + void Function(int)? b, + ) => + ''')), ); }); @@ -1580,7 +1602,12 @@ void main() { void m(int? a(), int b()); } '''), - _containsAllOf('void m(int? Function()? a, int Function()? b) =>'), + _containsAllOf(dedent2(''' + void m( + int? Function()? a, + int Function()? b, + ) => + ''')), ); }); @@ -1593,23 +1620,26 @@ void main() { void m(void a(int? x), void b(int x)); } '''), - _containsAllOf( - 'void m(void Function(int?)? a, void Function(int)? b) =>'), + _containsAllOf(dedent2(''' + void m( + void Function(int?)? a, + void Function(int)? b, + ) => + ''')), ); }); test( 'matches requiredness of parameter types within a function-typed ' 'parameter', () async { - await expectSingleNonNullableOutput(dedent(''' + await expectSingleNonNullableOutput( + dedent(''' class Foo { void m(void Function({required int p}) cb) {} } - '''), _containsAllOf(dedent2(''' - void m(void Function({required int p})? cb) => - super.noSuchMethod(Invocation.method(#m, [cb]), - returnValueForMissingStub: null); - '''))); + '''), + _containsAllOf( + 'void m(void Function({required int p})? cb) => super.noSuchMethod(')); }); test('matches nullability of a generic parameter', () async { @@ -1619,7 +1649,12 @@ void main() { void m(T? a, T b); } '''), - _containsAllOf('void m(T? a, T? b) =>'), + _containsAllOf(dedent2(''' + void m( + T? a, + T? b, + ) => + ''')), ); }); @@ -1630,7 +1665,12 @@ void main() { void m(dynamic a, int b); } '''), - _containsAllOf('void m(dynamic a, int? b) =>'), + _containsAllOf(dedent2(''' + void m( + dynamic a, + int? b, + ) => + ''')), ); }); @@ -1685,8 +1725,8 @@ void main() { m(int a); } '''), - _containsAllOf('dynamic m(int? a) =>', - 'super.noSuchMethod(Invocation.method(#m, [a]));'), + _containsAllOf( + 'dynamic m(int? a) => super.noSuchMethod(Invocation.method('), ); }); @@ -1698,7 +1738,7 @@ void main() { } '''), _containsAllOf( - 'dynamic f(int? a) => super.noSuchMethod(Invocation.method(#f, [a]));'), + 'dynamic f(int? a) => super.noSuchMethod(Invocation.method('), ); }); @@ -1809,9 +1849,14 @@ void main() { } '''), _containsAllOf(dedent2(''' - int m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: 0) as int); - ''')), + int m() => (super.noSuchMethod( + Invocation.method( + #m, + [], + ), + returnValue: 0, + ) as int); + ''')), ); }); @@ -1840,8 +1885,13 @@ void main() { '''), }, outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'void m(T? a) => super.noSuchMethod(Invocation.method(#m, [a])'), + 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent2(''' + void m(T? a) => super.noSuchMethod( + Invocation.method( + #m, + [a], + ), + ''')), }, ); }); @@ -1865,9 +1915,11 @@ void main() { } '''), _containsAllOf(dedent2(''' - int get m => - (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); - ''')), + int get m => (super.noSuchMethod( + Invocation.getter(#m), + returnValue: 0, + ) as int); + ''')), ); }); @@ -1889,9 +1941,11 @@ void main() { class Foo extends FooBase {} '''), _containsAllOf(dedent2(''' - int get m => - (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); - ''')), + int get m => (super.noSuchMethod( + Invocation.getter(#m), + returnValue: 0, + ) as int); + ''')), ); }); @@ -1916,9 +1970,14 @@ void main() { } '''), _containsAllOf(dedent2(''' - set m(int? a) => super - .noSuchMethod(Invocation.setter(#m, a), returnValueForMissingStub: null); - ''')), + set m(int? a) => super.noSuchMethod( + Invocation.setter( + #m, + a, + ), + returnValueForMissingStub: null, + ); + ''')), ); }); @@ -1930,9 +1989,14 @@ void main() { } '''), _containsAllOf(dedent2(''' - set m(int? a) => super - .noSuchMethod(Invocation.setter(#m, a), returnValueForMissingStub: null); - ''')), + set m(int? a) => super.noSuchMethod( + Invocation.setter( + #m, + a, + ), + returnValueForMissingStub: null, + ); + ''')), ); }); @@ -1945,9 +2009,14 @@ void main() { class Foo extends FooBase {} '''), _containsAllOf(dedent2(''' - set m(int? a) => super - .noSuchMethod(Invocation.setter(#m, a), returnValueForMissingStub: null); - ''')), + set m(int? a) => super.noSuchMethod( + Invocation.setter( + #m, + a, + ), + returnValueForMissingStub: null, + ); + ''')), ); }); @@ -1972,12 +2041,19 @@ void main() { } '''), _containsAllOf(dedent2(''' - int get m => - (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + int get m => (super.noSuchMethod( + Invocation.getter(#m), + returnValue: 0, + ) as int); '''), dedent2(''' - set m(int? _m) => super - .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); - ''')), + set m(int? _m) => super.noSuchMethod( + Invocation.setter( + #m, + _m, + ), + returnValueForMissingStub: null, + ); + ''')), ); }); @@ -1990,12 +2066,19 @@ void main() { class Foo extends FooBase {} '''), _containsAllOf(dedent2(''' - int get m => - (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); - '''), dedent2(''' - set m(int? _m) => super - .noSuchMethod(Invocation.setter(#m, _m), returnValueForMissingStub: null); - ''')), + int get m => (super.noSuchMethod( + Invocation.getter(#m), + returnValue: 0, + ) as int); + '''), dedent2(''' + set m(int? _m) => super.noSuchMethod( + Invocation.setter( + #m, + _m, + ), + returnValueForMissingStub: null, + ); + ''')), ); }); @@ -2024,8 +2107,10 @@ void main() { } '''), _containsAllOf(dedent2(''' - int get m => - (super.noSuchMethod(Invocation.getter(#m), returnValue: 0) as int); + int get m => (super.noSuchMethod( + Invocation.getter(#m), + returnValue: 0, + ) as int); ''')), ); }); @@ -2065,9 +2150,13 @@ void main() { } '''), _containsAllOf(dedent2(''' - int operator +(_i2.Foo? other) => - (super.noSuchMethod(Invocation.method(#+, [other]), returnValue: 0) - as int); + int operator +(_i2.Foo? other) => (super.noSuchMethod( + Invocation.method( + #+, + [other], + ), + returnValue: 0, + ) as int); ''')), ); }); @@ -2080,8 +2169,13 @@ void main() { } '''), _containsAllOf(dedent2(''' - int operator [](int? x) => - (super.noSuchMethod(Invocation.method(#[], [x]), returnValue: 0) as int); + int operator [](int? x) => (super.noSuchMethod( + Invocation.method( + #[], + [x], + ), + returnValue: 0, + ) as int); ''')), ); }); @@ -2094,8 +2188,13 @@ void main() { } '''), _containsAllOf(dedent2(''' - int operator ~() => - (super.noSuchMethod(Invocation.method(#~, []), returnValue: 0) as int); + int operator ~() => (super.noSuchMethod( + Invocation.method( + #~, + [], + ), + returnValue: 0, + ) as int); ''')), ); }); @@ -2107,10 +2206,7 @@ void main() { bool m() => false; } '''), - _containsAllOf(dedent2(''' - bool m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: false) - as bool); - ''')), + _containsAllOf('returnValue: false,'), ); }); @@ -2121,10 +2217,7 @@ void main() { double m() => 3.14; } '''), - _containsAllOf(dedent2(''' - double m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: 0.0) - as double); - ''')), + _containsAllOf('returnValue: 0.0,'), ); }); @@ -2135,10 +2228,7 @@ void main() { int m() => 7; } '''), - _containsAllOf(dedent2(''' - int m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: 0) as int); - ''')), + _containsAllOf('returnValue: 0,'), ); }); @@ -2149,10 +2239,7 @@ void main() { String m() => "Hello"; } '''), - _containsAllOf(dedent2(''' - String m() => (super.noSuchMethod(Invocation.method(#m, []), returnValue: '') - as String); - ''')), + _containsAllOf("returnValue: '',"), ); }); @@ -2163,11 +2250,7 @@ void main() { List m() => [Foo()]; } '''), - _containsAllOf(dedent2(''' - List<_i2.Foo> m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: <_i2.Foo>[]) - as List<_i2.Foo>); - ''')), + _containsAllOf('returnValue: <_i2.Foo>[],'), ); }); @@ -2178,11 +2261,7 @@ void main() { Set m() => {Foo()}; } '''), - _containsAllOf(dedent2(''' - Set<_i2.Foo> m() => - (super.noSuchMethod(Invocation.method(#m, []), returnValue: <_i2.Foo>{}) - as Set<_i2.Foo>); - ''')), + _containsAllOf('returnValue: <_i2.Foo>{},'), ); }); @@ -2193,10 +2272,7 @@ void main() { Map m() => {7: Foo()}; } '''), - _containsAllOf(dedent2(''' - Map m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: {}) as Map); - ''')), + _containsAllOf('returnValue: {},'), ); }); @@ -2207,10 +2283,7 @@ void main() { Map m(); } '''), - _containsAllOf(dedent2(''' - Map m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: {}) as Map); - ''')), + _containsAllOf('returnValue: {},'), ); }); @@ -2222,10 +2295,7 @@ void main() { Future m() async => false; } '''), - _containsAllOf(dedent2(''' - _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Future.value(false)) as _i3.Future); - ''')), + _containsAllOf('returnValue: _i3.Future.value(false),'), ); }); @@ -2238,10 +2308,7 @@ void main() { Future m(); } '''), - _containsAllOf(dedent2(''' - _i3.Future m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Future.value(() {})) as _i3.Future); - ''')), + _containsAllOf('returnValue: _i3.Future.value(() {}),'), ); }); @@ -2254,10 +2321,7 @@ void main() { Future m() async => null; } '''), - _containsAllOf(dedent2(''' - _i3.Future<_i2.Bar?> m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Future<_i2.Bar?>.value()) as _i3.Future<_i2.Bar?>); - ''')), + _containsAllOf('returnValue: _i3.Future<_i2.Bar?>.value(),'), ); }); @@ -2271,12 +2335,8 @@ void main() { Future m() async => Uint8List(0); } '''), - _containsAllOf(dedent2(''' - _i3.Future<_i4.Uint8List> m() => - (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0))) - as _i3.Future<_i4.Uint8List>); - ''')), + _containsAllOf( + 'returnValue: _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0)),'), ); }); @@ -2289,12 +2349,8 @@ void main() { Future> m() async => false; } '''), - _containsAllOf(dedent2(''' - _i3.Future> m() => - (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Future>.value([])) - as _i3.Future>); - ''')), + _containsAllOf( + 'returnValue: _i3.Future>.value([]),'), ); }); @@ -2305,10 +2361,7 @@ void main() { Stream m(); } '''), - _containsAllOf(dedent2(''' - _i3.Stream m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _i3.Stream.empty()) as _i3.Stream); - ''')), + _containsAllOf('returnValue: _i3.Stream.empty(),'), ); }); @@ -2323,10 +2376,14 @@ void main() { Bar(this.name); } '''), - _containsAllOf(dedent2(''' - _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _FakeBar_0(this, Invocation.method(#m, []))) as _i2.Bar); - ''')), + _containsAllOf(''' + returnValue: _FakeBar_0( + this, + Invocation.method( + #m, + [], + ), + ),'''), ); }); @@ -2338,11 +2395,14 @@ void main() { } class Bar {} '''), - _containsAllOf(dedent2(''' - _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: _FakeBar_0(this, Invocation.method(#m, []))) - as _i2.Bar); - ''')), + _containsAllOf(''' + returnValue: _FakeBar_0( + this, + Invocation.method( + #m, + [], + ), + ),'''), ); }); @@ -2357,11 +2417,7 @@ void main() { two, } '''), - _containsAllOf(dedent2(''' - _i2.Bar m1() => - (super.noSuchMethod(Invocation.method(#m1, []), returnValue: _i2.Bar.one) - as _i2.Bar); - ''')), + _containsAllOf('returnValue: _i2.Bar.one,'), ); }); @@ -2374,12 +2430,11 @@ void main() { void Function(int, [String]) m() => (int i, [String s]) {}; } '''), - _containsAllOf(dedent2(''' - void Function(int, [String]) m() => (super.noSuchMethod( - Invocation.method(#m, []), - returnValue: (int __p0, [String __p1]) {}) - as void Function(int, [String])); - ''')), + _containsAllOf(''' + returnValue: ( + int __p0, [ + String __p1, + ]) {},'''), ); }); @@ -2392,12 +2447,11 @@ void main() { void Function(Foo, {bool b}) m() => (Foo f, {bool b}) {}; } '''), - _containsAllOf(dedent2(''' - void Function(_i2.Foo, {bool b}) m() => - (super.noSuchMethod(Invocation.method(#m, []), - returnValue: (_i2.Foo __p0, {bool b}) {}) - as void Function(_i2.Foo, {bool b})); - ''')), + _containsAllOf(''' + returnValue: ( + _i2.Foo __p0, { + bool b, + }) {},'''), ); }); @@ -2410,11 +2464,14 @@ void main() { Foo Function() m() => () => Foo(); } '''), - _containsAllOf(dedent2(''' - _i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: () => _FakeFoo_0(this, Invocation.method(#m, []))) - as _i2.Foo Function()); - ''')), + _containsAllOf(''' + returnValue: () => _FakeFoo_0( + this, + Invocation.method( + #m, + [], + ), + ),'''), ); }); @@ -2428,11 +2485,15 @@ void main() { _Callback m() => () => Foo(); } '''), - _containsAllOf(dedent2(''' - _i2.Foo Function() m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: () => _FakeFoo_0(this, Invocation.method(#m, []))) - as _i2.Foo Function()); - ''')), + _containsAllOf(''' + returnValue: () => _FakeFoo_0( + this, + Invocation.method( + #m, + [], + ), + ), + '''), ); }); @@ -2444,10 +2505,7 @@ void main() { T? Function(T) m() => (int i, [String s]) {}; } '''), - _containsAllOf(dedent2(''' - T? Function(T) m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: (T __p0) => null) as T? Function(T)); - ''')), + _containsAllOf('returnValue: (T __p0) => null,'), ); }); @@ -2461,10 +2519,13 @@ void main() { } '''), _containsAllOf(dedent2(''' - T? Function(T) m() => - (super.noSuchMethod(Invocation.method(#m, []), - returnValue: (T __p0) => null) - as T? Function(T)); + T? Function(T) m() => (super.noSuchMethod( + Invocation.method( + #m, + [], + ), + returnValue: (T __p0) => null, + ) as T? Function(T)); ''')), ); }); @@ -2480,8 +2541,13 @@ void main() { } '''), _containsAllOf(dedent2(''' - void Function(_i3.File) m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: (_i3.File __p0) {}) as void Function(_i3.File)); + void Function(_i3.File) m() => (super.noSuchMethod( + Invocation.method( + #m, + [], + ), + returnValue: (_i3.File __p0) {}, + ) as void Function(_i3.File)); ''')), ); }); @@ -2497,9 +2563,19 @@ void main() { } '''), _containsAllOf(dedent2(''' - _i2.File Function() m() => (super.noSuchMethod(Invocation.method(#m, []), - returnValue: () => _FakeFile_0(this, Invocation.method(#m, []))) - as _i2.File Function()); + _i2.File Function() m() => (super.noSuchMethod( + Invocation.method( + #m, + [], + ), + returnValue: () => _FakeFile_0( + this, + Invocation.method( + #m, + [], + ), + ), + ) as _i2.File Function()); ''')), ); }); @@ -2514,8 +2590,13 @@ void main() { '''), _containsAllOf(dedent(''' class _FakeBar_0 extends _i1.SmartFake implements _i2.Bar { - _FakeBar_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + _FakeBar_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); }''')), ); }); @@ -2625,8 +2706,13 @@ void main() { '''), _containsAllOf(dedent(''' class _FakeBar_0 extends _i1.SmartFake implements _i2.Bar { - _FakeBar_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + _FakeBar_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); @override String toString({bool? a = true}) => super.toString(); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 5f8cb792e..bc9937134 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -485,10 +485,8 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains(' int m() => (super.noSuchMethod(Invocation.method(#m, []),\n' - ' returnValue: 0, returnValueForMissingStub: 0) as int);')); + expect(mocksContent, contains('returnValue: 0,')); + expect(mocksContent, contains('returnValueForMissingStub: 0,')); }); test( @@ -515,13 +513,21 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains( - ' _i2.Bar m() => (super.noSuchMethod(Invocation.method(#m, []),\n' - ' returnValue: _FakeBar_0(this, Invocation.method(#m, [])),\n' - ' returnValueForMissingStub:\n' - ' _FakeBar_0(this, Invocation.method(#m, []))) as _i2.Bar);\n')); + expect(mocksContent, contains(''' + returnValue: _FakeBar_0( + this, + Invocation.method( + #m, + [], + ), + ), + returnValueForMissingStub: _FakeBar_0( + this, + Invocation.method( + #m, + [], + ), + ),''')); }); test('generates mock classes including a fallback generator for a getter', @@ -550,12 +556,7 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains('T get f =>\n' - ' (super.noSuchMethod(Invocation.getter(#f), returnValue: _i3.fShim())\n' - ' as T);'), - ); + expect(mocksContent, contains('returnValue: _i3.fShim(),')); }); test( @@ -586,11 +587,7 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains( - 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' - ' returnValue: _i3.mShim(a)) as T)')); + expect(mocksContent, contains('returnValue: _i3.mShim(a),')); }); test( @@ -622,11 +619,7 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains( - 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' - ' returnValue: _i3.mShim(a)) as T)')); + expect(mocksContent, contains('returnValue: _i3.mShim(a),')); }); test('generates mock classes including two fallback generators', () async { @@ -659,16 +652,8 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains( - 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' - ' returnValue: _i3.mShimA(a)) as T)')); - expect( - mocksContent, - contains( - 'T m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' - ' returnValue: _i3.mShimB(a)) as T)')); + expect(mocksContent, contains('returnValue: _i3.mShimA(a),')); + expect(mocksContent, contains('returnValue: _i3.mShimB(a),')); }); test( @@ -698,11 +683,7 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains( - '_i3.Future m(T? a) => (super.noSuchMethod(Invocation.method(#m, [a]),\n' - ' returnValue: _i4.mShim(a)) as _i3.Future)')); + expect(mocksContent, contains('returnValue: _i4.mShim(a),')); }); test( @@ -731,11 +712,7 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains( - 'T m({T? a}) => (super.noSuchMethod(Invocation.method(#m, [], {#a: a}),\n' - ' returnValue: _i3.mShim(a: a)) as T);')); + expect(mocksContent, contains('returnValue: _i3.mShim(a: a),')); }); test( @@ -764,11 +741,7 @@ void main() { void main() {} ''' }); - expect( - mocksContent, - contains('T m({T? a}) =>\n' - ' (super.noSuchMethod(Invocation.method(#m, [], {#a: a}),\n' - ' returnValue: _i3.mShim(a: a)) as T);')); + expect(mocksContent, contains('returnValue: _i3.mShim(a: a),')); }); test( @@ -799,11 +772,7 @@ void main() { void main() {} ''') }); - expect( - mocksContent, - contains('T m({List? a}) =>\n' - ' (super.noSuchMethod(Invocation.method(#m, [], {#a: a}),\n' - ' returnValue: _i3.mShim(a: a)) as T);')); + expect(mocksContent, contains('returnValue: _i3.mShim(a: a),')); }); test( From 4c0e9ae3bc81cb79963bbce4607988d396fd8c93 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 24 Aug 2022 16:04:48 -0400 Subject: [PATCH 459/607] Stop using deprecated declarations, prepare for analyzer breaking changes. PiperOrigin-RevId: 469803713 --- pkgs/mockito/lib/src/builder.dart | 56 ++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 9ea6da55f..3e9e87281 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -250,6 +250,12 @@ class _TypeVisitor extends RecursiveElementVisitor { super.visitClassElement(element); } + @override + void visitEnumElement(EnumElement element) { + _elements.add(element); + super.visitEnumElement(element); + } + @override void visitFieldElement(FieldElement element) { _addType(element.type); @@ -262,6 +268,12 @@ class _TypeVisitor extends RecursiveElementVisitor { super.visitMethodElement(element); } + @override + void visitMixinElement(MixinElement element) { + _elements.add(element); + super.visitMixinElement(element); + } + @override void visitParameterElement(ParameterElement element) { _addType(element.type); @@ -391,7 +403,7 @@ class _MockTarget { this.hasExplicitTypeArguments = false, }); - InterfaceElement get classElement => classType.element2; + InterfaceElement get interfaceElement => classType.element2; } /// This class gathers and verifies mock targets referenced in `GenerateMocks` @@ -491,7 +503,7 @@ class _MockTargetGatherer { // `type` have been instantiated to bounds here. Switch to the // declaration, which will be an uninstantiated type. final declarationType = - (type.element2.declaration as ClassElement).thisType; + (type.element2.declaration as InterfaceElement).thisType; final mockName = 'Mock${declarationType.element2.name}'; mockTargets.add(_MockTarget( declarationType, @@ -545,7 +557,7 @@ class _MockTargetGatherer { // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. - type = (type.element2.declaration as ClassElement).thisType; + type = (type.element2.declaration as InterfaceElement).thisType; } else { // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. @@ -731,7 +743,7 @@ class _MockTargetGatherer { void _checkClassesToMockAreValid() { final classesInEntryLib = - _entryLib.topLevelElements.whereType(); + _entryLib.topLevelElements.whereType(); final classNamesToMock = {}; final uniqueNameSuggestion = "use the 'customMocks' argument in @GenerateMocks to specify a unique " @@ -739,13 +751,13 @@ class _MockTargetGatherer { for (final mockTarget in _mockTargets) { final name = mockTarget.mockName; if (classNamesToMock.containsKey(name)) { - final firstClass = classNamesToMock[name]!.classElement; + final firstClass = classNamesToMock[name]!.interfaceElement; final firstSource = firstClass.source.fullName; - final secondSource = mockTarget.classElement.source.fullName; + final secondSource = mockTarget.interfaceElement.source.fullName; throw InvalidMockitoAnnotationException( 'Mockito cannot generate two mocks with the same name: $name (for ' '${firstClass.name} declared in $firstSource, and for ' - '${mockTarget.classElement.name} declared in $secondSource); ' + '${mockTarget.interfaceElement.name} declared in $secondSource); ' '$uniqueNameSuggestion.'); } classNamesToMock[name] = mockTarget; @@ -764,7 +776,7 @@ class _MockTargetGatherer { var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => c.interfaces .map((type) => type.element2) - .contains(mockTarget.classElement) && + .contains(mockTarget.interfaceElement) && _isMockClass(c.supertype!)); if (preexistingMock != null) { throw InvalidMockitoAnnotationException( @@ -787,11 +799,11 @@ class _MockTargetGatherer { /// and no corresponding dummy generator. Mockito cannot generate its own /// dummy return values for unknown types. void _checkMethodsToStubAreValid(_MockTarget mockTarget) { - final classElement = mockTarget.classElement; - final className = classElement.name; + final interfaceElement = mockTarget.interfaceElement; + final className = interfaceElement.name; final substitution = Substitution.fromInterfaceType(mockTarget.classType); final relevantMembers = _inheritanceManager - .getInterface(classElement) + .getInterface(interfaceElement) .map .values .where((m) => !m.isPrivate && !m.isStatic) @@ -968,9 +980,9 @@ class _MockLibraryInfo { /// values. final fakeClasses = []; - /// [ClassElement]s which are used in non-nullable return types, for which + /// [InterfaceElement]s which are used in non-nullable return types, for which /// fake classes are added to the generated library. - final fakedClassElements = []; + final fakedInterfaceElements = []; /// A mapping of each necessary [Element] to a URI from which it can be /// imported. @@ -1051,7 +1063,7 @@ class _MockClassInfo { Class _buildMockClass() { final typeToMock = mockTarget.classType; - final classToMock = mockTarget.classElement; + final classToMock = mockTarget.interfaceElement; final classIsImmutable = classToMock.metadata.any((it) => it.isImmutable); final className = classToMock.name; @@ -1101,7 +1113,7 @@ class _MockClassInfo { cBuilder.implements.add(TypeReference((b) { b ..symbol = classToMock.name - ..url = _typeImport(mockTarget.classElement) + ..url = _typeImport(mockTarget.interfaceElement) ..types.addAll(typeArguments); })); if (mockTarget.onMissingStub == OnMissingStub.throwException) { @@ -1471,7 +1483,7 @@ class _MockClassInfo { } else { final fakeName = mockLibraryInfo._fakeNameFor(elementToFake); // Only make one fake class for each class that needs to be faked. - if (!mockLibraryInfo.fakedClassElements.contains(elementToFake)) { + if (!mockLibraryInfo.fakedInterfaceElements.contains(elementToFake)) { _addFakeClass(fakeName, elementToFake); } final typeArguments = dartType.typeArguments; @@ -1524,7 +1536,7 @@ class _MockClassInfo { (mBuilder) => _buildOverridingMethod(mBuilder, toStringMethod))); } })); - mockLibraryInfo.fakedClassElements.add(elementToFake); + mockLibraryInfo.fakedInterfaceElements.add(elementToFake); } /// Returns a [Parameter] which matches [parameter]. @@ -1559,7 +1571,7 @@ class _MockClassInfo { final method = parameter.enclosingElement3!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' - "'${mockTarget.classElement.displayName}.${method.displayName}'; " + "'${mockTarget.interfaceElement.displayName}.${method.displayName}'; " "parameter '${parameter.displayName}' causes a problem: " '${e.message}'); } @@ -1588,7 +1600,7 @@ class _MockClassInfo { return type; } final method = parameter.enclosingElement3 as MethodElement; - final class_ = method.enclosingElement3 as ClassElement; + final class_ = method.enclosingElement3 as InterfaceElement; final name = Name(method.librarySource.uri, method.name); final overriddenMethods = inheritanceManager.getOverridden2(class_, name); if (overriddenMethods == null) { @@ -1598,7 +1610,7 @@ class _MockClassInfo { while (allOverriddenMethods.isNotEmpty) { final overriddenMethod = allOverriddenMethods.removeFirst(); final secondaryOverrides = inheritanceManager.getOverridden2( - overriddenMethod.enclosingElement3 as ClassElement, name); + overriddenMethod.enclosingElement3 as InterfaceElement, name); if (secondaryOverrides != null) { allOverriddenMethods.addAll(secondaryOverrides); } @@ -1996,9 +2008,13 @@ extension on Element { String get fullName { if (this is ClassElement) { return "The class '$name'"; + } else if (this is EnumElement) { + return "The enum '$name'"; } else if (this is MethodElement) { var className = enclosingElement3!.name; return "The method '$className.$name'"; + } else if (this is MixinElement) { + return "The mixin '$name'"; } else if (this is PropertyAccessorElement) { var className = enclosingElement3!.name; return "The property accessor '$className.$name'"; From 4e5340ea7f00a06d2f1d65b4c39db32d993cef5e Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 29 Aug 2022 11:40:53 -0400 Subject: [PATCH 460/607] Restrict the exception for CAST_FROM_NULL_ALWAYS_FAILS Also fix case in mockito. All other fixes have been Rosie'd out as cl/470591518; as these land we can tighten more. PiperOrigin-RevId: 470728313 --- pkgs/mockito/test/manual_mocks_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/test/manual_mocks_test.dart b/pkgs/mockito/test/manual_mocks_test.dart index 87e6855ac..918c2806e 100644 --- a/pkgs/mockito/test/manual_mocks_test.dart +++ b/pkgs/mockito/test/manual_mocks_test.dart @@ -95,9 +95,9 @@ void main() { 'cannot operate on method with non-nullable params without a manual ' 'mock', () { // Normally this use of `any` would be a static error. To push forward to - // reveal the runtime error, we cast as int. + // reveal the runtime error, we cast as dynamic. expect( - () => when(mock.notMockedNonNullableParam(any as int)) + () => when(mock.notMockedNonNullableParam((any as dynamic) as int)) .thenReturn('Mock'), throwsA(TypeMatcher())); }); From 11f971084be80835e46343e0fc0c9a1bf523f8a9 Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 29 Aug 2022 16:03:15 -0400 Subject: [PATCH 461/607] Allow generating a mock class which includes overriding members with private types. Such members cannot be stubbed with mockito, and will only be generated when specified in MockSpec `unsupportedMembers`. PiperOrigin-RevId: 470794362 --- pkgs/mockito/CHANGELOG.md | 3 + pkgs/mockito/lib/src/builder.dart | 134 +++++++++++++----- .../test/builder/custom_mocks_test.dart | 108 ++++++++++++++ 3 files changed, 206 insertions(+), 39 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 82dabd66a..ceb0d63c0 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,9 @@ * Fix analyzer and code_builder dependencies. * Reference `@GenerateNiceMocks` in documentation. +* Allow generating a mock class which includes overriding members with private + types in their signature. Such members cannot be stubbed with mockito, and + will only be generated when specified in MockSpec `unsupportedMembers`. ## 5.3.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 3e9e87281..7571fea98 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -836,13 +836,19 @@ class _MockTargetGatherer { } } + String get _tryUnsupportedMembersMessage => 'Try generating this mock with ' + "a MockSpec with 'unsupportedMembers' or a dummy generator (see " + 'https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).'; + /// Checks [function] for properties that would make it un-stubbable. /// /// Types are checked in the following positions: /// - return type /// - parameter types /// - bounds of type parameters - /// - type arguments on types in the above three positions + /// - recursively, written types on types in the above three positions + /// (namely, type arguments, return types of function types, and parameter + /// types of function types) /// /// If any type in the above positions is private, [function] is un-stubbable. /// If the return type is potentially non-nullable, [function] is @@ -861,14 +867,19 @@ class _MockTargetGatherer { final errorMessages = []; final returnType = function.returnType; if (returnType is analyzer.InterfaceType) { - if (returnType.element2.isPrivate) { - errorMessages.add( - '${enclosingElement.fullName} features a private return type, and ' - 'cannot be stubbed.'); + if (returnType.containsPrivateName) { + if (!allowUnsupportedMember && !hasDummyGenerator) { + errorMessages.add( + '${enclosingElement.fullName} features a private return type, ' + 'and cannot be stubbed. $_tryUnsupportedMembersMessage'); + } } errorMessages.addAll(_checkTypeArguments( - returnType.typeArguments, enclosingElement, - isParameter: isParameter)); + returnType.typeArguments, + enclosingElement, + isParameter: isParameter, + allowUnsupportedMember: allowUnsupportedMember, + )); } else if (returnType is analyzer.FunctionType) { errorMessages.addAll(_checkFunction(returnType, enclosingElement, allowUnsupportedMember: allowUnsupportedMember, @@ -878,11 +889,10 @@ class _MockTargetGatherer { !allowUnsupportedMember && !hasDummyGenerator && _entryLib.typeSystem.isPotentiallyNonNullable(returnType)) { - errorMessages.add( - '${enclosingElement.fullName} features a non-nullable unknown ' - 'return type, and cannot be stubbed. Try generating this mock with ' - "a MockSpec with 'unsupportedMembers' or a dummy generator (see " - 'https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).'); + errorMessages + .add('${enclosingElement.fullName} features a non-nullable unknown ' + 'return type, and cannot be stubbed. ' + '$_tryUnsupportedMembersMessage'); } } @@ -894,13 +904,19 @@ class _MockTargetGatherer { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not // generate such a mock. - errorMessages.add( - '${enclosingElement.fullName} features a private parameter type, ' - "'${parameterTypeElement.name}', and cannot be stubbed."); + if (!allowUnsupportedMember) { + errorMessages.add( + '${enclosingElement.fullName} features a private parameter ' + "type, '${parameterTypeElement.name}', and cannot be stubbed. " + '$_tryUnsupportedMembersMessage'); + } } errorMessages.addAll(_checkTypeArguments( - parameterType.typeArguments, enclosingElement, - isParameter: true)); + parameterType.typeArguments, + enclosingElement, + isParameter: true, + allowUnsupportedMember: allowUnsupportedMember, + )); } else if (parameterType is analyzer.FunctionType) { errorMessages.addAll( _checkFunction(parameterType, enclosingElement, isParameter: true)); @@ -928,6 +944,8 @@ class _MockTargetGatherer { var typeParameter = element.bound; if (typeParameter == null) continue; if (typeParameter is analyzer.InterfaceType) { + // TODO(srawlins): Check for private names in bound; could be + // `List<_Bar>`. if (typeParameter.element2.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type parameter ' @@ -947,18 +965,23 @@ class _MockTargetGatherer { List typeArguments, Element enclosingElement, { bool isParameter = false, + bool allowUnsupportedMember = false, }) { var errorMessages = []; for (var typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { - if (typeArgument.element2.isPrivate) { + if (typeArgument.element2.isPrivate && !allowUnsupportedMember) { errorMessages.add( '${enclosingElement.fullName} features a private type argument, ' - 'and cannot be stubbed.'); + 'and cannot be stubbed. $_tryUnsupportedMembersMessage'); } } else if (typeArgument is analyzer.FunctionType) { - errorMessages.addAll(_checkFunction(typeArgument, enclosingElement, - isParameter: isParameter)); + errorMessages.addAll(_checkFunction( + typeArgument, + enclosingElement, + isParameter: isParameter, + allowUnsupportedMember: allowUnsupportedMember, + )); } } return errorMessages; @@ -1233,11 +1256,16 @@ class _MockClassInfo { void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { var name = method.displayName; if (method.isOperator) name = 'operator$name'; + final returnType = method.returnType; builder ..name = name ..annotations.add(referImported('override', 'dart:core')) - ..returns = _typeReference(method.returnType) ..types.addAll(method.typeParameters.map(_typeParameterReference)); + // We allow overriding a method with a private return type by omitting the + // return type (which is then inherited). + if (!returnType.containsPrivateName) { + builder.returns = _typeReference(returnType); + } // These two variables store the arguments that will be passed to the // [Invocation] built for `noSuchMethod`. @@ -1246,20 +1274,16 @@ class _MockClassInfo { var position = 0; for (final parameter in method.parameters) { - if (parameter.isRequiredPositional) { - final superParameterType = - _escapeCovariance(parameter, position: position); - final matchingParameter = _matchingParameter(parameter, - superParameterType: superParameterType, forceNullable: true); - builder.requiredParameters.add(matchingParameter); - invocationPositionalArgs.add(refer(parameter.displayName)); - position++; - } else if (parameter.isOptionalPositional) { + if (parameter.isRequiredPositional || parameter.isOptionalPositional) { final superParameterType = _escapeCovariance(parameter, position: position); final matchingParameter = _matchingParameter(parameter, superParameterType: superParameterType, forceNullable: true); - builder.optionalParameters.add(matchingParameter); + if (parameter.isRequiredPositional) { + builder.requiredParameters.add(matchingParameter); + } else { + builder.optionalParameters.add(matchingParameter); + } invocationPositionalArgs.add(refer(parameter.displayName)); position++; } else if (parameter.isNamed) { @@ -1282,11 +1306,18 @@ class _MockClassInfo { return; } - final returnType = method.returnType; + final returnTypeIsTypeVariable = + typeSystem.isPotentiallyNonNullable(returnType) && + returnType is analyzer.TypeParameterType; final fallbackGenerator = fallbackGenerators[method.name]; - if (typeSystem.isPotentiallyNonNullable(returnType) && - returnType is analyzer.TypeParameterType && - fallbackGenerator == null) { + final parametersContainPrivateName = + method.parameters.any((p) => p.type.containsPrivateName); + final throwsUnsupported = fallbackGenerator == null && + (returnTypeIsTypeVariable || + returnType.containsPrivateName || + parametersContainPrivateName); + + if (throwsUnsupported) { if (!mockTarget.unsupportedMembers.contains(name)) { // We shouldn't get here as this is guarded against in // [_MockTargetGatherer._checkFunction]. @@ -1557,10 +1588,11 @@ class _MockClassInfo { '$defaultName'); final name = parameter.name.isEmpty ? defaultName! : parameter.name; return Parameter((pBuilder) { - pBuilder - ..name = name - ..type = + pBuilder.name = name; + if (!superParameterType.containsPrivateName) { + pBuilder.type = _typeReference(superParameterType, forceNullable: forceNullable); + } if (parameter.isNamed) pBuilder.named = true; if (parameter.defaultValueCode != null) { try { @@ -2025,6 +2057,30 @@ extension on Element { } extension on analyzer.DartType { + /// Whether this type contains a private name, perhaps in a type argument or a + /// function type's parameters, etc. + bool get containsPrivateName { + final self = this; + if (self is analyzer.DynamicType) { + return false; + } else if (self is analyzer.InterfaceType) { + return self.element2.isPrivate || + self.typeArguments.any((t) => t.containsPrivateName); + } else if (self is analyzer.FunctionType) { + return self.returnType.containsPrivateName || + self.parameters.any((p) => p.type.containsPrivateName); + } else if (self is analyzer.NeverType) { + return false; + } else if (self is analyzer.TypeParameterType) { + return false; + } else if (self is analyzer.VoidType) { + return false; + } else { + assert(false, 'Unexpected subtype of DartType: ${self.runtimeType}'); + return false; + } + } + /// Returns whether this type is `Future` or `Future?`. bool get isFutureOfVoid => isDartAsyncFuture && diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index bc9937134..1845addc5 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -462,6 +462,114 @@ void main() { r" '\'m\' cannot be used without a mockito fallback generator.');")); }); + test( + 'generates mock methods with private return types, given ' + 'unsupportedMembers', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + _Bar m(); + } + class _Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(unsupportedMembers: {#m}), + ]) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' m() => throw UnsupportedError(\n' + r" '\'m\' cannot be used without a mockito fallback generator.');")); + }); + + test( + 'generates mock methods with return types with private names in type ' + 'arguments, given unsupportedMembers', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + List<_Bar> m(); + } + class _Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(unsupportedMembers: {#m}), + ]) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' m() => throw UnsupportedError(\n' + r" '\'m\' cannot be used without a mockito fallback generator.');")); + }); + + test( + 'generates mock methods with return types with private names in function ' + 'types, given unsupportedMembers', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + void Function(_Bar) m(); + } + class _Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(unsupportedMembers: {#m}), + ]) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' m() => throw UnsupportedError(\n' + r" '\'m\' cannot be used without a mockito fallback generator.');")); + }); + + test( + 'generates mock methods with private parameter types, given ' + 'unsupportedMembers', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + void m(_Bar b); + } + class _Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(unsupportedMembers: {#m}), + ]) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' void m(b) => throw UnsupportedError(\n' + r" '\'m\' cannot be used without a mockito fallback generator.');")); + }); + test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for basic known types', () async { From 11abcebda0bbf560908caa0c24687c0d5437855a Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 1 Sep 2022 15:45:49 -0400 Subject: [PATCH 462/607] Expand private-in-signature support to getters and setters The original CHANGELOG entry already (incorrectly at the time) used the word "members" so it does not need an update; it is just becoming correct now. PiperOrigin-RevId: 471606853 --- pkgs/mockito/lib/src/builder.dart | 58 ++++++++++++++----- .../test/builder/custom_mocks_test.dart | 52 +++++++++++++++++ 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7571fea98..ce643083c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1323,7 +1323,8 @@ class _MockClassInfo { // [_MockTargetGatherer._checkFunction]. throw InvalidMockitoAnnotationException( "Mockito cannot generate a valid override for '$name', as it has a " - 'non-nullable unknown return type.'); + 'non-nullable unknown return type or a private type in its ' + 'signature.'); } builder.body = refer('UnsupportedError') .call([ @@ -1771,20 +1772,26 @@ class _MockClassInfo { builder ..name = getter.displayName ..annotations.add(referImported('override', 'dart:core')) - ..type = MethodType.getter - ..returns = _typeReference(getter.returnType); + ..type = MethodType.getter; + + if (!getter.returnType.containsPrivateName) { + builder.returns = _typeReference(getter.returnType); + } final returnType = getter.returnType; final fallbackGenerator = fallbackGenerators[getter.name]; - if (typeSystem.isPotentiallyNonNullable(returnType) && - returnType is analyzer.TypeParameterType && - fallbackGenerator == null) { + final returnTypeIsTypeVariable = + typeSystem.isPotentiallyNonNullable(returnType) && + returnType is analyzer.TypeParameterType; + final throwsUnsupported = fallbackGenerator == null && + (returnTypeIsTypeVariable || getter.returnType.containsPrivateName); + if (throwsUnsupported) { if (!mockTarget.unsupportedMembers.contains(getter.name)) { // We shouldn't get here as this is guarded against in // [_MockTargetGatherer._checkFunction]. throw InvalidMockitoAnnotationException( "Mockito cannot generate a valid override for '${getter.name}', as " - 'it has a non-nullable unknown type.'); + 'it has a non-nullable unknown type or a private type.'); } builder.body = refer('UnsupportedError') .call([ @@ -1824,22 +1831,45 @@ class _MockClassInfo { /// This new setter just calls `super.noSuchMethod`. void _buildOverridingSetter( MethodBuilder builder, PropertyAccessorElement setter) { + final nameWithEquals = setter.name; + final name = setter.displayName; builder - ..name = setter.displayName + ..name = name ..annotations.add(referImported('override', 'dart:core')) ..type = MethodType.setter; assert(setter.parameters.length == 1); final parameter = setter.parameters.single; - builder.requiredParameters.add(Parameter((pBuilder) => pBuilder - ..name = parameter.displayName - ..type = _typeReference(parameter.type, forceNullable: true))); - final invocationPositionalArg = refer(parameter.displayName); + builder.requiredParameters.add(Parameter((pBuilder) { + pBuilder.name = parameter.displayName; + if (!parameter.type.containsPrivateName) { + pBuilder.type = _typeReference(parameter.type, forceNullable: true); + } + })); + + if (parameter.type.containsPrivateName) { + if (!mockTarget.unsupportedMembers.contains(nameWithEquals)) { + // We shouldn't get here as this is guarded against in + // [_MockTargetGatherer._checkFunction]. + throw InvalidMockitoAnnotationException( + "Mockito cannot generate a valid override for '$nameWithEquals', " + 'as it has a private parameter type.'); + } + builder.body = refer('UnsupportedError') + .call([ + literalString( + "'$nameWithEquals' cannot be used without a mockito fallback " + 'generator.') + ]) + .thrown + .code; + return; + } final invocation = referImported('Invocation', 'dart:core').property('setter').call([ - refer('#${setter.displayName}'), - invocationPositionalArg, + refer('#$name'), + refer(parameter.displayName), ]); final returnNoSuchMethod = refer('super') .property('noSuchMethod') diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 1845addc5..0a5ab480d 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -489,6 +489,58 @@ void main() { r" '\'m\' cannot be used without a mockito fallback generator.');")); }); + test('generates mock getters with private types, given unsupportedMembers', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + _Bar get f; + } + class _Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(unsupportedMembers: {#f}), + ]) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' get f => throw UnsupportedError(\n' + r" '\'f\' cannot be used without a mockito fallback generator.');")); + }); + + test('generates mock setters with private types, given unsupportedMembers', + () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(''' + abstract class Foo { + set f(_Bar value); + } + class _Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(unsupportedMembers: {Symbol('f=')}), + ]) + void main() {} + ''' + }); + expect( + mocksContent, + contains(' set f(value) => throw UnsupportedError(\n' + r" '\'f=\' cannot be used without a mockito fallback generator.');")); + }); + test( 'generates mock methods with return types with private names in type ' 'arguments, given unsupportedMembers', () async { From 309f338f24e023342ab2bb8b5868eae45adf2db1 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 1 Sep 2022 18:28:19 -0400 Subject: [PATCH 463/607] Include `required` keyword in functions used as default return values. PiperOrigin-RevId: 471650729 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/builder.dart | 7 ++++++- .../mockito/test/builder/auto_mocks_test.dart | 19 ++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ceb0d63c0..bd0e25073 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -5,6 +5,7 @@ * Allow generating a mock class which includes overriding members with private types in their signature. Such members cannot be stubbed with mockito, and will only be generated when specified in MockSpec `unsupportedMembers`. +* Include `required` keyword in functions used as default return values. ## 5.3.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index ce643083c..a70d830c1 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1492,7 +1492,11 @@ class _MockClassInfo { superParameterType: parameter.type, defaultName: '__p$position'); b.optionalParameters.add(matchingParameter); position++; - } else if (parameter.isNamed) { + } else if (parameter.isOptionalNamed) { + final matchingParameter = + _matchingParameter(parameter, superParameterType: parameter.type); + b.optionalParameters.add(matchingParameter); + } else if (parameter.isRequiredNamed) { final matchingParameter = _matchingParameter(parameter, superParameterType: parameter.type); b.optionalParameters.add(matchingParameter); @@ -1595,6 +1599,7 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; + if (parameter.isRequiredNamed) pBuilder.required = true; if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index cb2c0849e..28e9cdfaf 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1524,7 +1524,7 @@ void main() { void m({required covariant int a}); } '''), - _containsAllOf('void m({num? a}) => super.noSuchMethod('), + _containsAllOf('void m({required num? a}) => super.noSuchMethod('), ); }); @@ -2455,6 +2455,23 @@ void main() { ); }); + test( + 'creates a dummy non-null function-typed return value, with required ' + 'named parameters', () async { + await expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void Function(Foo, {required bool b}) m(); + } + '''), + _containsAllOf(''' + returnValue: ( + _i2.Foo __p0, { + required bool b, + }) {},'''), + ); + }); + test( 'creates a dummy non-null function-typed return value, with non-core ' 'return type', () async { From b144d3f1443d46af08afbb7f927bf46d810e0560 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 2 Sep 2022 01:54:28 -0400 Subject: [PATCH 464/607] Automated g4 rollback of changelist 471650729. *** Reason for rollback *** Breaks GPay tap (pay.flutter.tap) https://fusion2.corp.google.com/ci;ids=1912127488/tap/pay.flutter.tap/activity/471652072/targets *** Original change description *** Include `required` keyword in functions used as default return values. *** PiperOrigin-RevId: 471719348 --- pkgs/mockito/CHANGELOG.md | 1 - pkgs/mockito/lib/src/builder.dart | 7 +------ .../mockito/test/builder/auto_mocks_test.dart | 19 +------------------ 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index bd0e25073..ceb0d63c0 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -5,7 +5,6 @@ * Allow generating a mock class which includes overriding members with private types in their signature. Such members cannot be stubbed with mockito, and will only be generated when specified in MockSpec `unsupportedMembers`. -* Include `required` keyword in functions used as default return values. ## 5.3.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a70d830c1..ce643083c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1492,11 +1492,7 @@ class _MockClassInfo { superParameterType: parameter.type, defaultName: '__p$position'); b.optionalParameters.add(matchingParameter); position++; - } else if (parameter.isOptionalNamed) { - final matchingParameter = - _matchingParameter(parameter, superParameterType: parameter.type); - b.optionalParameters.add(matchingParameter); - } else if (parameter.isRequiredNamed) { + } else if (parameter.isNamed) { final matchingParameter = _matchingParameter(parameter, superParameterType: parameter.type); b.optionalParameters.add(matchingParameter); @@ -1599,7 +1595,6 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; - if (parameter.isRequiredNamed) pBuilder.required = true; if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 28e9cdfaf..cb2c0849e 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1524,7 +1524,7 @@ void main() { void m({required covariant int a}); } '''), - _containsAllOf('void m({required num? a}) => super.noSuchMethod('), + _containsAllOf('void m({num? a}) => super.noSuchMethod('), ); }); @@ -2455,23 +2455,6 @@ void main() { ); }); - test( - 'creates a dummy non-null function-typed return value, with required ' - 'named parameters', () async { - await expectSingleNonNullableOutput( - dedent(r''' - abstract class Foo { - void Function(Foo, {required bool b}) m(); - } - '''), - _containsAllOf(''' - returnValue: ( - _i2.Foo __p0, { - required bool b, - }) {},'''), - ); - }); - test( 'creates a dummy non-null function-typed return value, with non-core ' 'return type', () async { From 627013a67ac1c42acb58a1a7e39ffb7130c3f30b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 7 Sep 2022 10:52:41 -0400 Subject: [PATCH 465/607] Automated g4 rollback of changelist 471719348. *** Reason for rollback *** Rolling forward after fixes *** Original change description *** Automated g4 rollback of changelist 471650729. *** Reason for rollback *** Breaks GPay tap (pay.flutter.tap) https://fusion2.corp.google.com/ci;ids=1912127488/tap/pay.flutter.tap/activity/471652072/targets *** Original change description *** Include `required` keyword in functions used as default return values. *** *** PiperOrigin-RevId: 472723817 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/builder.dart | 7 ++++++- .../mockito/test/builder/auto_mocks_test.dart | 19 ++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ceb0d63c0..bd0e25073 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -5,6 +5,7 @@ * Allow generating a mock class which includes overriding members with private types in their signature. Such members cannot be stubbed with mockito, and will only be generated when specified in MockSpec `unsupportedMembers`. +* Include `required` keyword in functions used as default return values. ## 5.3.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index ce643083c..a70d830c1 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1492,7 +1492,11 @@ class _MockClassInfo { superParameterType: parameter.type, defaultName: '__p$position'); b.optionalParameters.add(matchingParameter); position++; - } else if (parameter.isNamed) { + } else if (parameter.isOptionalNamed) { + final matchingParameter = + _matchingParameter(parameter, superParameterType: parameter.type); + b.optionalParameters.add(matchingParameter); + } else if (parameter.isRequiredNamed) { final matchingParameter = _matchingParameter(parameter, superParameterType: parameter.type); b.optionalParameters.add(matchingParameter); @@ -1595,6 +1599,7 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; + if (parameter.isRequiredNamed) pBuilder.required = true; if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index cb2c0849e..28e9cdfaf 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1524,7 +1524,7 @@ void main() { void m({required covariant int a}); } '''), - _containsAllOf('void m({num? a}) => super.noSuchMethod('), + _containsAllOf('void m({required num? a}) => super.noSuchMethod('), ); }); @@ -2455,6 +2455,23 @@ void main() { ); }); + test( + 'creates a dummy non-null function-typed return value, with required ' + 'named parameters', () async { + await expectSingleNonNullableOutput( + dedent(r''' + abstract class Foo { + void Function(Foo, {required bool b}) m(); + } + '''), + _containsAllOf(''' + returnValue: ( + _i2.Foo __p0, { + required bool b, + }) {},'''), + ); + }); + test( 'creates a dummy non-null function-typed return value, with non-core ' 'return type', () async { From b27cc6be40b0e582cfd137446a9e70b2cf87c5b7 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 8 Sep 2022 04:34:06 -0400 Subject: [PATCH 466/607] Mockito codegen: use fallbackGenerator when present to create a default 'returnValueForMissingStub'. When running in sound mode, this allows overriding the default missingStub implementation that might fail at runtime (generics for ex.). PiperOrigin-RevId: 472925798 --- pkgs/mockito/lib/src/builder.dart | 16 +++-- .../test/builder/custom_mocks_test.dart | 64 ++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a70d830c1..2935c4346 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1350,9 +1350,15 @@ class _MockClassInfo { returnValueForMissingStub = _futureReference(refer('void')).property('value').call([]); } else if (mockTarget.onMissingStub == OnMissingStub.returnDefault) { - // Return a legal default value if no stub is found which matches a real - // call. - returnValueForMissingStub = _dummyValue(returnType, invocation); + if (fallbackGenerator != null) { + // Re-use the fallback for missing stub. + returnValueForMissingStub = + _fallbackGeneratorCode(method, fallbackGenerator); + } else { + // Return a legal default value if no stub is found which matches a real + // call. + returnValueForMissingStub = _dummyValue(returnType, invocation); + } } final namedArgs = { if (fallbackGenerator != null) @@ -1819,7 +1825,9 @@ class _MockClassInfo { else if (typeSystem._returnTypeIsNonNullable(getter)) 'returnValue': _dummyValue(returnType, invocation), if (mockTarget.onMissingStub == OnMissingStub.returnDefault) - 'returnValueForMissingStub': _dummyValue(returnType, invocation), + 'returnValueForMissingStub': (fallbackGenerator != null + ? _fallbackGeneratorCode(getter, fallbackGenerator) + : _dummyValue(returnType, invocation)), }; var superNoSuchMethod = refer('super').property('noSuchMethod').call([invocation], namedArgs); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 0a5ab480d..3d653a362 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -935,6 +935,39 @@ void main() { expect(mocksContent, contains('returnValue: _i3.mShim(a: a),')); }); + test( + 'generates mock classes including a fallback generator and ' + 'OnMissingStub.returnDefault', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + T get f; + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + T fShim() { + throw 'unknown'; + } + + @GenerateMocks( + [], + customMocks: [ + MockSpec( + fallbackGenerators: {#f: fShim}, + onMissingStub: OnMissingStub.returnDefault), + ], + ) + void main() {} + ''' + }); + expect(mocksContent, contains('returnValue: _i3.fShim(),')); + expect(mocksContent, contains('returnValueForMissingStub: _i3.fShim(),')); + }); + test( 'throws when GenerateMocks is given a class with a type parameter with a ' 'private bound', () async { @@ -1265,7 +1298,36 @@ void main() { ''' }); expect(mocksContent, isNot(contains('throwOnMissingStub'))); - expect(mocksContent, contains('returnValueForMissingStub:')); + expect(mocksContent, contains('returnValue: 0')); + expect(mocksContent, contains('returnValueForMissingStub: 0')); + }); + + test( + 'generates a mock class which uses the new behavior of returning ' + 'a valid value for missing stubs, if GenerateNiceMocks and ' + 'fallbackGenerators were used', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + int m(); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + int mShim() { + return 1; + } + + @GenerateNiceMocks([MockSpec(fallbackGenerators: {#m: mShim})]) + void main() {} + ''' + }); + expect(mocksContent, isNot(contains('throwOnMissingStub'))); + expect(mocksContent, contains('returnValue: _i3.mShim(),')); + expect(mocksContent, contains('returnValueForMissingStub: _i3.mShim(),')); }); test('mixed GenerateMocks and GenerateNiceMocks annotations could be used', From 2664b3164e0f2abc2385963902353385c8702376 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 13 Sep 2022 16:32:55 -0700 Subject: [PATCH 467/607] Bump SDK to 2.17 and code_builder to 4.3.0 --- pkgs/mockito/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 3443f15e9..bf79148e1 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,12 +6,12 @@ description: >- repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.12.0-0 <3.0.0' + sdk: '>=2.17.0-0 <3.0.0' dependencies: analyzer: '>=4.6.0 <5.0.0' build: '>=1.3.0 <3.0.0' - code_builder: ^4.2.0 + code_builder: ^4.3.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 From 039a409e1f388be3491763400935464212f422b6 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 22 Sep 2022 09:37:16 -0700 Subject: [PATCH 468/607] Bump analyzer to support 5.0.0 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index bd0e25073..2fb415199 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.3.2 + +* Support analyzer 5.0.0. + ## 5.3.1 * Fix analyzer and code_builder dependencies. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 984dcec8c..cc93276fa 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.1'; +const packageVersion = '5.3.2'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index bf79148e1..df104683d 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.1 +version: 5.3.2 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -9,7 +9,7 @@ environment: sdk: '>=2.17.0-0 <3.0.0' dependencies: - analyzer: '>=4.6.0 <5.0.0' + analyzer: '>=4.7.0 <6.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.3.0 collection: ^1.15.0 From 12db5882e5727b6dde1efff20172c2004e44efeb Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 25 Oct 2022 16:35:09 -0700 Subject: [PATCH 469/607] Require analyzer 5.2.0 --- pkgs/mockito/CHANGELOG.md | 4 ++ pkgs/mockito/lib/src/builder.dart | 84 +++++++++++++++---------------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 +- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 2fb415199..ba5ee6d6c 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.3.3-dev + +Require analyzer 5.2.0. + ## 5.3.2 * Support analyzer 5.0.0. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 2935c4346..dcfb6278d 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -153,8 +153,8 @@ $rawOutput return; } seenTypes.add(type); - librariesWithTypes.add(type.element2.library); - type.element2.accept(typeVisitor); + librariesWithTypes.add(type.element.library); + type.element.accept(typeVisitor); // For a type like `Foo`, add the `Bar`. type.typeArguments .whereType() @@ -294,14 +294,14 @@ class _TypeVisitor extends RecursiveElementVisitor { if (type == null) return; if (type is analyzer.InterfaceType) { - final alreadyVisitedElement = _elements.contains(type.element2); - _elements.add(type.element2); + final alreadyVisitedElement = _elements.contains(type.element); + _elements.add(type.element); type.typeArguments.forEach(_addType); if (!alreadyVisitedElement) { - type.element2.typeParameters.forEach(visitTypeParameterElement); + type.element.typeParameters.forEach(visitTypeParameterElement); final toStringMethod = - type.element2.lookUpMethod('toString', type.element2.library); + type.element.lookUpMethod('toString', type.element.library); if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { // In a Fake class which implements a class which overrides `toString` // with additional (optional) parameters, we must also override @@ -310,7 +310,7 @@ class _TypeVisitor extends RecursiveElementVisitor { for (final parameter in toStringMethod.parameters) { final parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - parameterType.element2.accept(this); + parameterType.element.accept(this); } } } @@ -403,7 +403,7 @@ class _MockTarget { this.hasExplicitTypeArguments = false, }); - InterfaceElement get interfaceElement => classType.element2; + InterfaceElement get interfaceElement => classType.element; } /// This class gathers and verifies mock targets referenced in `GenerateMocks` @@ -440,7 +440,7 @@ class _MockTargetGatherer { // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; - final annotationClass = annotation.element!.enclosingElement3!.name; + final annotationClass = annotation.element!.enclosingElement!.name; switch (annotationClass) { case 'GenerateMocks': mockTargets @@ -503,8 +503,8 @@ class _MockTargetGatherer { // `type` have been instantiated to bounds here. Switch to the // declaration, which will be an uninstantiated type. final declarationType = - (type.element2.declaration as InterfaceElement).thisType; - final mockName = 'Mock${declarationType.element2.name}'; + (type.element.declaration as InterfaceElement).thisType; + final mockName = 'Mock${declarationType.element.name}'; mockTargets.add(_MockTarget( declarationType, mockName, @@ -557,7 +557,7 @@ class _MockTargetGatherer { // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. - type = (type.element2.declaration as InterfaceElement).thisType; + type = (type.element.declaration as InterfaceElement).thisType; } else { // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. @@ -578,7 +578,7 @@ class _MockTargetGatherer { }); } final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? - 'Mock${type.element2.name}'; + 'Mock${type.element.name}'; final mixins = []; for (final m in mockSpec.getField('mixins')!.toListValue()!) { final typeToMixin = m.toTypeValue(); @@ -702,7 +702,7 @@ class _MockTargetGatherer { static analyzer.InterfaceType _determineDartType( analyzer.DartType typeToMock, TypeProvider typeProvider) { if (typeToMock is analyzer.InterfaceType) { - final elementToMock = typeToMock.element2; + final elementToMock = typeToMock.element; if (elementToMock is EnumElement) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock an enum: ${elementToMock.displayName}'); @@ -775,7 +775,7 @@ class _MockTargetGatherer { var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => c.interfaces - .map((type) => type.element2) + .map((type) => type.element) .contains(mockTarget.interfaceElement) && _isMockClass(c.supertype!)); if (preexistingMock != null) { @@ -899,7 +899,7 @@ class _MockTargetGatherer { for (var parameter in function.parameters) { var parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - var parameterTypeElement = parameterType.element2; + var parameterTypeElement = parameterType.element; if (parameterTypeElement.isPrivate) { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not @@ -946,7 +946,7 @@ class _MockTargetGatherer { if (typeParameter is analyzer.InterfaceType) { // TODO(srawlins): Check for private names in bound; could be // `List<_Bar>`. - if (typeParameter.element2.isPrivate) { + if (typeParameter.element.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type parameter ' 'bound, and cannot be stubbed.'); @@ -970,7 +970,7 @@ class _MockTargetGatherer { var errorMessages = []; for (var typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { - if (typeArgument.element2.isPrivate && !allowUnsupportedMember) { + if (typeArgument.element.isPrivate && !allowUnsupportedMember) { errorMessages.add( '${enclosingElement.fullName} features a private type argument, ' 'and cannot be stubbed. $_tryUnsupportedMembersMessage'); @@ -989,8 +989,8 @@ class _MockTargetGatherer { /// Return whether [type] is the Mock class declared by mockito. bool _isMockClass(analyzer.InterfaceType type) => - type.element2.name == 'Mock' && - type.element2.source.fullName.endsWith('lib/src/mock.dart'); + type.element.name == 'Mock' && + type.element.source.fullName.endsWith('lib/src/mock.dart'); } class _MockLibraryInfo { @@ -1128,8 +1128,8 @@ class _MockClassInfo { for (final mixin in mockTarget.mixins) { cBuilder.mixins.add(TypeReference((b) { b - ..symbol = mixin.element2.name - ..url = _typeImport(mixin.element2) + ..symbol = mixin.element.name + ..url = _typeImport(mixin.element) ..types.addAll(mixin.typeArguments.map(_typeReference)); })); } @@ -1443,7 +1443,7 @@ class _MockClassInfo { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return literalSet({}, elementType); - } else if (type.element2.declaration == typeProvider.streamElement) { + } else if (type.element.declaration == typeProvider.streamElement) { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return TypeReference((b) { @@ -1458,7 +1458,7 @@ class _MockClassInfo { // These "List" types from dart:typed_data are "non-subtypeable", but they // have predicatble constructors; each has an unnamed constructor which // takes a single int argument. - return referImported(type.element2.name, 'dart:typed_data') + return referImported(type.element.name, 'dart:typed_data') .call([literalNum(0)]); // TODO(srawlins): Do other types from typed_data have a "non-subtypeable" // restriction as well? @@ -1518,7 +1518,7 @@ class _MockClassInfo { Expression _dummyValueImplementing( analyzer.InterfaceType dartType, Expression invocation) { - final elementToFake = dartType.element2; + final elementToFake = dartType.element; if (elementToFake is EnumElement) { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); @@ -1612,7 +1612,7 @@ class _MockClassInfo { parameter.computeConstantValue()!, parameter) .code; } on _ReviveException catch (e) { - final method = parameter.enclosingElement3!; + final method = parameter.enclosingElement!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' "'${mockTarget.interfaceElement.displayName}.${method.displayName}'; " @@ -1643,8 +1643,8 @@ class _MockClassInfo { if (!parameter.isCovariant) { return type; } - final method = parameter.enclosingElement3 as MethodElement; - final class_ = method.enclosingElement3 as InterfaceElement; + final method = parameter.enclosingElement as MethodElement; + final class_ = method.enclosingElement as InterfaceElement; final name = Name(method.librarySource.uri, method.name); final overriddenMethods = inheritanceManager.getOverridden2(class_, name); if (overriddenMethods == null) { @@ -1654,7 +1654,7 @@ class _MockClassInfo { while (allOverriddenMethods.isNotEmpty) { final overriddenMethod = allOverriddenMethods.removeFirst(); final secondaryOverrides = inheritanceManager.getOverridden2( - overriddenMethod.enclosingElement3 as InterfaceElement, name); + overriddenMethod.enclosingElement as InterfaceElement, name); if (secondaryOverrides != null) { allOverriddenMethods.addAll(secondaryOverrides); } @@ -1745,7 +1745,7 @@ class _MockClassInfo { // We can create this invocation by referring to a const field or // top-level variable. return referImported( - revivable.accessor, _typeImport(object.type!.element2)); + revivable.accessor, _typeImport(object.type!.element)); } final name = revivable.source.fragment; @@ -1757,9 +1757,9 @@ class _MockClassInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final element = parameter != null && name != object.type!.element2!.name - ? parameter.type.element2 - : object.type!.element2; + final element = parameter != null && name != object.type!.element!.name + ? parameter.type.element + : object.type!.element; final type = referImported(name, _typeImport(element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( @@ -1920,10 +1920,10 @@ class _MockClassInfo { if (type is analyzer.InterfaceType) { return TypeReference((b) { b - ..symbol = type.element2.name + ..symbol = type.element.name ..isNullable = forceNullable || type.nullabilitySuffix == NullabilitySuffix.question - ..url = _typeImport(type.element2) + ..url = _typeImport(type.element) ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { @@ -1964,13 +1964,13 @@ class _MockClassInfo { } else if (type is analyzer.TypeParameterType) { return TypeReference((b) { b - ..symbol = type.element2.name + ..symbol = type.element.name ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { return referImported( type.getDisplayString(withNullability: false), - _typeImport(type.element2), + _typeImport(type.element), ); } } @@ -2086,12 +2086,12 @@ extension on Element { } else if (this is EnumElement) { return "The enum '$name'"; } else if (this is MethodElement) { - var className = enclosingElement3!.name; + var className = enclosingElement!.name; return "The method '$className.$name'"; } else if (this is MixinElement) { return "The mixin '$name'"; } else if (this is PropertyAccessorElement) { - var className = enclosingElement3!.name; + var className = enclosingElement!.name; return "The property accessor '$className.$name'"; } else { return 'unknown element'; @@ -2107,7 +2107,7 @@ extension on analyzer.DartType { if (self is analyzer.DynamicType) { return false; } else if (self is analyzer.InterfaceType) { - return self.element2.isPrivate || + return self.element.isPrivate || self.typeArguments.any((t) => t.containsPrivateName); } else if (self is analyzer.FunctionType) { return self.returnType.containsPrivateName || @@ -2132,10 +2132,10 @@ extension on analyzer.DartType { /// Returns whether this type is a "List" type from the dart:typed_data /// library. bool get isDartTypedDataList { - if (element2!.library!.name != 'dart.typed_data') { + if (element!.library!.name != 'dart.typed_data') { return false; } - final name = element2!.name; + final name = element!.name; return name == 'Float32List' || name == 'Float64List' || name == 'Int8List' || diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index cc93276fa..453ec3a49 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.2'; +const packageVersion = '5.3.3-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index df104683d..c1b73c58a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.2 +version: 5.3.3-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -9,7 +9,7 @@ environment: sdk: '>=2.17.0-0 <3.0.0' dependencies: - analyzer: '>=4.7.0 <6.0.0' + analyzer: '>=5.2.0 <6.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.3.0 collection: ^1.15.0 From 3a812564581dd0cb13bcf0c563da9dc49fdd4d89 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 19 Sep 2022 16:38:29 -0400 Subject: [PATCH 470/607] make mockito unsupportedMembers tolerate names with $s in them. PiperOrigin-RevId: 475373527 --- pkgs/mockito/CHANGELOG.md | 8 -- pkgs/mockito/lib/src/builder.dart | 100 ++++++++++-------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 8 +- .../test/builder/custom_mocks_test.dart | 14 +-- pkgs/mockito/test/end2end/foo.dart | 1 + .../test/end2end/generated_mocks_test.dart | 6 ++ 7 files changed, 72 insertions(+), 67 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ba5ee6d6c..bd0e25073 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,11 +1,3 @@ -## 5.3.3-dev - -Require analyzer 5.2.0. - -## 5.3.2 - -* Support analyzer 5.0.0. - ## 5.3.1 * Fix analyzer and code_builder dependencies. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index dcfb6278d..58833e392 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -153,8 +153,8 @@ $rawOutput return; } seenTypes.add(type); - librariesWithTypes.add(type.element.library); - type.element.accept(typeVisitor); + librariesWithTypes.add(type.element2.library); + type.element2.accept(typeVisitor); // For a type like `Foo`, add the `Bar`. type.typeArguments .whereType() @@ -294,14 +294,14 @@ class _TypeVisitor extends RecursiveElementVisitor { if (type == null) return; if (type is analyzer.InterfaceType) { - final alreadyVisitedElement = _elements.contains(type.element); - _elements.add(type.element); + final alreadyVisitedElement = _elements.contains(type.element2); + _elements.add(type.element2); type.typeArguments.forEach(_addType); if (!alreadyVisitedElement) { - type.element.typeParameters.forEach(visitTypeParameterElement); + type.element2.typeParameters.forEach(visitTypeParameterElement); final toStringMethod = - type.element.lookUpMethod('toString', type.element.library); + type.element2.lookUpMethod('toString', type.element2.library); if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { // In a Fake class which implements a class which overrides `toString` // with additional (optional) parameters, we must also override @@ -310,7 +310,7 @@ class _TypeVisitor extends RecursiveElementVisitor { for (final parameter in toStringMethod.parameters) { final parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - parameterType.element.accept(this); + parameterType.element2.accept(this); } } } @@ -403,7 +403,7 @@ class _MockTarget { this.hasExplicitTypeArguments = false, }); - InterfaceElement get interfaceElement => classType.element; + InterfaceElement get interfaceElement => classType.element2; } /// This class gathers and verifies mock targets referenced in `GenerateMocks` @@ -440,7 +440,7 @@ class _MockTargetGatherer { // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; - final annotationClass = annotation.element!.enclosingElement!.name; + final annotationClass = annotation.element!.enclosingElement3!.name; switch (annotationClass) { case 'GenerateMocks': mockTargets @@ -503,8 +503,8 @@ class _MockTargetGatherer { // `type` have been instantiated to bounds here. Switch to the // declaration, which will be an uninstantiated type. final declarationType = - (type.element.declaration as InterfaceElement).thisType; - final mockName = 'Mock${declarationType.element.name}'; + (type.element2.declaration as InterfaceElement).thisType; + final mockName = 'Mock${declarationType.element2.name}'; mockTargets.add(_MockTarget( declarationType, mockName, @@ -557,7 +557,7 @@ class _MockTargetGatherer { // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. - type = (type.element.declaration as InterfaceElement).thisType; + type = (type.element2.declaration as InterfaceElement).thisType; } else { // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. @@ -578,7 +578,7 @@ class _MockTargetGatherer { }); } final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? - 'Mock${type.element.name}'; + 'Mock${type.element2.name}'; final mixins = []; for (final m in mockSpec.getField('mixins')!.toListValue()!) { final typeToMixin = m.toTypeValue(); @@ -702,7 +702,7 @@ class _MockTargetGatherer { static analyzer.InterfaceType _determineDartType( analyzer.DartType typeToMock, TypeProvider typeProvider) { if (typeToMock is analyzer.InterfaceType) { - final elementToMock = typeToMock.element; + final elementToMock = typeToMock.element2; if (elementToMock is EnumElement) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock an enum: ${elementToMock.displayName}'); @@ -775,7 +775,7 @@ class _MockTargetGatherer { var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => c.interfaces - .map((type) => type.element) + .map((type) => type.element2) .contains(mockTarget.interfaceElement) && _isMockClass(c.supertype!)); if (preexistingMock != null) { @@ -899,7 +899,7 @@ class _MockTargetGatherer { for (var parameter in function.parameters) { var parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - var parameterTypeElement = parameterType.element; + var parameterTypeElement = parameterType.element2; if (parameterTypeElement.isPrivate) { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not @@ -946,7 +946,7 @@ class _MockTargetGatherer { if (typeParameter is analyzer.InterfaceType) { // TODO(srawlins): Check for private names in bound; could be // `List<_Bar>`. - if (typeParameter.element.isPrivate) { + if (typeParameter.element2.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type parameter ' 'bound, and cannot be stubbed.'); @@ -970,7 +970,7 @@ class _MockTargetGatherer { var errorMessages = []; for (var typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { - if (typeArgument.element.isPrivate && !allowUnsupportedMember) { + if (typeArgument.element2.isPrivate && !allowUnsupportedMember) { errorMessages.add( '${enclosingElement.fullName} features a private type argument, ' 'and cannot be stubbed. $_tryUnsupportedMembersMessage'); @@ -989,8 +989,8 @@ class _MockTargetGatherer { /// Return whether [type] is the Mock class declared by mockito. bool _isMockClass(analyzer.InterfaceType type) => - type.element.name == 'Mock' && - type.element.source.fullName.endsWith('lib/src/mock.dart'); + type.element2.name == 'Mock' && + type.element2.source.fullName.endsWith('lib/src/mock.dart'); } class _MockLibraryInfo { @@ -1128,8 +1128,8 @@ class _MockClassInfo { for (final mixin in mockTarget.mixins) { cBuilder.mixins.add(TypeReference((b) { b - ..symbol = mixin.element.name - ..url = _typeImport(mixin.element) + ..symbol = mixin.element2.name + ..url = _typeImport(mixin.element2) ..types.addAll(mixin.typeArguments.map(_typeReference)); })); } @@ -1328,8 +1328,10 @@ class _MockClassInfo { } builder.body = refer('UnsupportedError') .call([ + // Generate a raw string since name might contain a $. literalString( - "'$name' cannot be used without a mockito fallback generator.") + '"$name" cannot be used without a mockito fallback generator.', + raw: true) ]) .thrown .code; @@ -1443,7 +1445,7 @@ class _MockClassInfo { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return literalSet({}, elementType); - } else if (type.element.declaration == typeProvider.streamElement) { + } else if (type.element2.declaration == typeProvider.streamElement) { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return TypeReference((b) { @@ -1458,7 +1460,7 @@ class _MockClassInfo { // These "List" types from dart:typed_data are "non-subtypeable", but they // have predicatble constructors; each has an unnamed constructor which // takes a single int argument. - return referImported(type.element.name, 'dart:typed_data') + return referImported(type.element2.name, 'dart:typed_data') .call([literalNum(0)]); // TODO(srawlins): Do other types from typed_data have a "non-subtypeable" // restriction as well? @@ -1518,7 +1520,7 @@ class _MockClassInfo { Expression _dummyValueImplementing( analyzer.InterfaceType dartType, Expression invocation) { - final elementToFake = dartType.element; + final elementToFake = dartType.element2; if (elementToFake is EnumElement) { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); @@ -1612,7 +1614,7 @@ class _MockClassInfo { parameter.computeConstantValue()!, parameter) .code; } on _ReviveException catch (e) { - final method = parameter.enclosingElement!; + final method = parameter.enclosingElement3!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' "'${mockTarget.interfaceElement.displayName}.${method.displayName}'; " @@ -1643,8 +1645,8 @@ class _MockClassInfo { if (!parameter.isCovariant) { return type; } - final method = parameter.enclosingElement as MethodElement; - final class_ = method.enclosingElement as InterfaceElement; + final method = parameter.enclosingElement3 as MethodElement; + final class_ = method.enclosingElement3 as InterfaceElement; final name = Name(method.librarySource.uri, method.name); final overriddenMethods = inheritanceManager.getOverridden2(class_, name); if (overriddenMethods == null) { @@ -1654,7 +1656,7 @@ class _MockClassInfo { while (allOverriddenMethods.isNotEmpty) { final overriddenMethod = allOverriddenMethods.removeFirst(); final secondaryOverrides = inheritanceManager.getOverridden2( - overriddenMethod.enclosingElement as InterfaceElement, name); + overriddenMethod.enclosingElement3 as InterfaceElement, name); if (secondaryOverrides != null) { allOverriddenMethods.addAll(secondaryOverrides); } @@ -1745,7 +1747,7 @@ class _MockClassInfo { // We can create this invocation by referring to a const field or // top-level variable. return referImported( - revivable.accessor, _typeImport(object.type!.element)); + revivable.accessor, _typeImport(object.type!.element2)); } final name = revivable.source.fragment; @@ -1757,9 +1759,9 @@ class _MockClassInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final element = parameter != null && name != object.type!.element!.name - ? parameter.type.element - : object.type!.element; + final element = parameter != null && name != object.type!.element2!.name + ? parameter.type.element2 + : object.type!.element2; final type = referImported(name, _typeImport(element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( @@ -1806,9 +1808,11 @@ class _MockClassInfo { } builder.body = refer('UnsupportedError') .call([ + // Generate a raw string since getter.name might contain a $. literalString( - "'${getter.name}' cannot be used without a mockito fallback " - 'generator.') + '"${getter.name}" cannot be used without a mockito fallback ' + 'generator.', + raw: true) ]) .thrown .code; @@ -1870,9 +1874,11 @@ class _MockClassInfo { } builder.body = refer('UnsupportedError') .call([ + // Generate a raw string since nameWithEquals might contain a $. literalString( - "'$nameWithEquals' cannot be used without a mockito fallback " - 'generator.') + '"$nameWithEquals" cannot be used without a mockito fallback ' + 'generator.', + raw: true) ]) .thrown .code; @@ -1920,10 +1926,10 @@ class _MockClassInfo { if (type is analyzer.InterfaceType) { return TypeReference((b) { b - ..symbol = type.element.name + ..symbol = type.element2.name ..isNullable = forceNullable || type.nullabilitySuffix == NullabilitySuffix.question - ..url = _typeImport(type.element) + ..url = _typeImport(type.element2) ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { @@ -1964,13 +1970,13 @@ class _MockClassInfo { } else if (type is analyzer.TypeParameterType) { return TypeReference((b) { b - ..symbol = type.element.name + ..symbol = type.element2.name ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { return referImported( type.getDisplayString(withNullability: false), - _typeImport(type.element), + _typeImport(type.element2), ); } } @@ -2086,12 +2092,12 @@ extension on Element { } else if (this is EnumElement) { return "The enum '$name'"; } else if (this is MethodElement) { - var className = enclosingElement!.name; + var className = enclosingElement3!.name; return "The method '$className.$name'"; } else if (this is MixinElement) { return "The mixin '$name'"; } else if (this is PropertyAccessorElement) { - var className = enclosingElement!.name; + var className = enclosingElement3!.name; return "The property accessor '$className.$name'"; } else { return 'unknown element'; @@ -2107,7 +2113,7 @@ extension on analyzer.DartType { if (self is analyzer.DynamicType) { return false; } else if (self is analyzer.InterfaceType) { - return self.element.isPrivate || + return self.element2.isPrivate || self.typeArguments.any((t) => t.containsPrivateName); } else if (self is analyzer.FunctionType) { return self.returnType.containsPrivateName || @@ -2132,10 +2138,10 @@ extension on analyzer.DartType { /// Returns whether this type is a "List" type from the dart:typed_data /// library. bool get isDartTypedDataList { - if (element!.library!.name != 'dart.typed_data') { + if (element2!.library!.name != 'dart.typed_data') { return false; } - final name = element!.name; + final name = element2!.name; return name == 'Float32List' || name == 'Float64List' || name == 'Int8List' || diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 453ec3a49..984dcec8c 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.3-dev'; +const packageVersion = '5.3.1'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c1b73c58a..3443f15e9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,17 +1,17 @@ name: mockito -version: 5.3.3-dev +version: 5.3.1 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.17.0-0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=5.2.0 <6.0.0' + analyzer: '>=4.6.0 <5.0.0' build: '>=1.3.0 <3.0.0' - code_builder: ^4.3.0 + code_builder: ^4.2.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 3d653a362..4f17585ce 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -459,7 +459,7 @@ void main() { expect( mocksContent, contains(' T m(T? a) => throw UnsupportedError(\n' - r" '\'m\' cannot be used without a mockito fallback generator.');")); + ' r\'"m" cannot be used without a mockito fallback generator.\');')); }); test( @@ -486,7 +486,7 @@ void main() { expect( mocksContent, contains(' m() => throw UnsupportedError(\n' - r" '\'m\' cannot be used without a mockito fallback generator.');")); + ' r\'"m" cannot be used without a mockito fallback generator.\');')); }); test('generates mock getters with private types, given unsupportedMembers', @@ -512,7 +512,7 @@ void main() { expect( mocksContent, contains(' get f => throw UnsupportedError(\n' - r" '\'f\' cannot be used without a mockito fallback generator.');")); + ' r\'"f" cannot be used without a mockito fallback generator.\');')); }); test('generates mock setters with private types, given unsupportedMembers', @@ -538,7 +538,7 @@ void main() { expect( mocksContent, contains(' set f(value) => throw UnsupportedError(\n' - r" '\'f=\' cannot be used without a mockito fallback generator.');")); + ' r\'"f=" cannot be used without a mockito fallback generator.\');')); }); test( @@ -565,7 +565,7 @@ void main() { expect( mocksContent, contains(' m() => throw UnsupportedError(\n' - r" '\'m\' cannot be used without a mockito fallback generator.');")); + ' r\'"m" cannot be used without a mockito fallback generator.\');')); }); test( @@ -592,7 +592,7 @@ void main() { expect( mocksContent, contains(' m() => throw UnsupportedError(\n' - r" '\'m\' cannot be used without a mockito fallback generator.');")); + ' r\'"m" cannot be used without a mockito fallback generator.\');')); }); test( @@ -619,7 +619,7 @@ void main() { expect( mocksContent, contains(' void m(b) => throw UnsupportedError(\n' - r" '\'m\' cannot be used without a mockito fallback generator.');")); + ' r\'"m" cannot be used without a mockito fallback generator.\');')); }); test( diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index fb9108a31..e57b92531 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -34,6 +34,7 @@ abstract class Baz { T returnsTypeVariableFromTwo(); S Function(S) returnsGenericFunction(); S get typeVariableField; + T $hasDollarInName(); } class HasPrivate { diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 20c685b53..37049084b 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -37,6 +37,7 @@ T Function(T) returnsGenericFunctionShim() => (T _) => null as T; #returnsTypeVariableFromTwo, #returnsGenericFunction, #typeVariableField, + #$hasDollarInName, }, ), MockSpec( @@ -47,6 +48,7 @@ T Function(T) returnsGenericFunctionShim() => (T _) => null as T; #returnsTypeVariableFromTwo: returnsTypeVariableFromTwoShim, #returnsGenericFunction: returnsGenericFunctionShim, #typeVariableField: typeVariableFieldShim, + #$hasDollarInName: returnsTypeVariableShim, }, ), MockSpec(mixingIn: [HasPrivateMixin]), @@ -198,6 +200,10 @@ void main() { test('a real getter call (or field access) throws', () { expect(() => baz.typeVariableField, throwsUnsupportedError); }); + + test('a real call to a method whose name has a \$ in it throws', () { + expect(() => baz.$hasDollarInName(), throwsUnsupportedError); + }); }); group('for a generated mock using fallbackGenerators,', () { From 044e69e29f995daedfebcd7f206dee040f9be2d1 Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 27 Sep 2022 04:12:59 -0400 Subject: [PATCH 471/607] Import https://github.com/dart-lang/mockito/pull/572 Bump analyzer to support 5.0.0 with mockito 5.3.2 PiperOrigin-RevId: 477101887 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index bd0e25073..2fb415199 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.3.2 + +* Support analyzer 5.0.0. + ## 5.3.1 * Fix analyzer and code_builder dependencies. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 984dcec8c..cc93276fa 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.1'; +const packageVersion = '5.3.2'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 3443f15e9..75bfb6cee 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.1 +version: 5.3.2 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -9,7 +9,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=4.6.0 <5.0.0' + analyzer: '>=4.7.0 <6.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.2.0 collection: ^1.15.0 From 53ab453dc2ee150d9b0eed9fd937c0a3ae6f72b8 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 30 Sep 2022 11:05:11 -0400 Subject: [PATCH 472/607] Add `ByteData` from `dart:typed_data` to special cases This class can't be extended, so we have to be able to create some dummy value. PiperOrigin-RevId: 477995133 --- pkgs/mockito/lib/src/builder.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 58833e392..a9284821b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1456,8 +1456,9 @@ class _MockClassInfo { }).property('empty').call([]); } else if (type.isDartCoreString) { return literalString(''); - } else if (type.isDartTypedDataList) { - // These "List" types from dart:typed_data are "non-subtypeable", but they + } else if (type.isDartTypedDataSealed) { + // These types (XXXList + ByteData) from dart:typed_data are + // sealed, e.g. "non-subtypeable", but they // have predicatble constructors; each has an unnamed constructor which // takes a single int argument. return referImported(type.element2.name, 'dart:typed_data') @@ -2135,9 +2136,9 @@ extension on analyzer.DartType { isDartAsyncFuture && (this as analyzer.InterfaceType).typeArguments.first.isVoid; - /// Returns whether this type is a "List" type from the dart:typed_data + /// Returns whether this type is a sealed type from the dart:typed_data /// library. - bool get isDartTypedDataList { + bool get isDartTypedDataSealed { if (element2!.library!.name != 'dart.typed_data') { return false; } @@ -2151,7 +2152,8 @@ extension on analyzer.DartType { name == 'Uint8List' || name == 'Uint16List' || name == 'Uint32List' || - name == 'Uint64List'; + name == 'Uint64List' || + name == 'ByteData'; } } From ae4c31577b55bb59e01530d1040734b3ca7b39c1 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 13 Oct 2022 07:13:16 -0400 Subject: [PATCH 473/607] Generate method overrides even then source lib is not null-safe If the class to mock comes from a null-safe library. Otherwise we don't get nice mocks (as niceness depends on overrides). We need to be careful to generate a valid pre-null-safety code, but so far I only found we need to skip generating `required`. PiperOrigin-RevId: 480854047 --- pkgs/mockito/CHANGELOG.md | 5 ++++ pkgs/mockito/lib/src/builder.dart | 30 +++++++++++++++---- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 28 +++++++++++++++++ 5 files changed, 59 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 2fb415199..9b5e02041 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.3.3 + +* Fix nice mocks generation in mixed mode (generated code is pre null-safety, + while mocked class is null-safe). + ## 5.3.2 * Support analyzer 5.0.0. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a9284821b..29803bbd0 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1149,7 +1149,14 @@ class _MockClassInfo { return ExecutableMember.from2(member, substitution); }); - if (sourceLibIsNonNullable) { + // The test can be pre-null-safety but if the class + // we want to mock is defined in a null safe library, + // we still need to override methods to get nice mocks. + final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == + OnMissingStub.returnDefault && + typeToMock.element2.enclosingElement.library.isNonNullableByDefault; + + if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { cBuilder.methods.addAll( fieldOverrides(members.whereType())); cBuilder.methods @@ -1193,12 +1200,23 @@ class _MockClassInfo { if (accessor.isGetter && typeSystem._returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } - if (accessor.isSetter) { + if (accessor.isSetter && sourceLibIsNonNullable) { yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); } } } + bool _methodNeedsOverride(MethodElement method) { + if (!sourceLibIsNonNullable) { + // If we get here, we are adding overrides only to make + // nice mocks work. We only care about return types then. + return typeSystem._returnTypeIsNonNullable(method); + } + return typeSystem._returnTypeIsNonNullable(method) || + typeSystem._hasNonNullableParameter(method) || + _needsOverrideForVoidStub(method); + } + /// Yields all of the method overrides required for [methods]. /// /// This includes methods of supertypes and mixed in types. @@ -1225,9 +1243,7 @@ class _MockClassInfo { // narrow the return type. continue; } - if (typeSystem._returnTypeIsNonNullable(method) || - typeSystem._hasNonNullableParameter(method) || - _needsOverrideForVoidStub(method)) { + if (_methodNeedsOverride(method)) { _checkForConflictWithCore(method.name); yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } @@ -1608,7 +1624,9 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; - if (parameter.isRequiredNamed) pBuilder.required = true; + if (parameter.isRequiredNamed && sourceLibIsNonNullable) { + pBuilder.required = true; + } if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index cc93276fa..7747aca42 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.2'; +const packageVersion = '5.3.3'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 75bfb6cee..268c0ed86 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.2 +version: 5.3.3 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 4f17585ce..daae016e4 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -649,6 +649,34 @@ void main() { expect(mocksContent, contains('returnValueForMissingStub: 0,')); }); + test( + 'generates mock methods with non-nullable return types, specifying ' + 'legal default values for basic known types, in mixed mode', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + int m({required int x, double? y}); + } + '''), + 'foo|test/foo_test.dart': ''' + // @dart=2.9 + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks( + [], + customMocks: [ + MockSpec(onMissingStub: OnMissingStub.returnDefault), + ], + ) + void main() {} + ''' + }); + expect(mocksContent, contains('returnValue: 0,')); + expect(mocksContent, contains('returnValueForMissingStub: 0,')); + }); + test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for unknown types', () async { From 4f418ae4514b20d3844bb8120c95b1e4cb1e858a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 13 Oct 2022 08:38:11 -0400 Subject: [PATCH 474/607] Automated g4 rollback of changelist 480854047. *** Reason for rollback *** Breaks lots of tests across G3 *** Original change description *** Generate method overrides even then source lib is not null-safe If the class to mock comes from a null-safe library. Otherwise we don't get nice mocks (as niceness depends on overrides). We need to be careful to generate a valid pre-null-safety code, but so far I only found we need to skip generating `required`. *** PiperOrigin-RevId: 480867585 --- pkgs/mockito/CHANGELOG.md | 5 ---- pkgs/mockito/lib/src/builder.dart | 30 ++++--------------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 28 ----------------- 5 files changed, 8 insertions(+), 59 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9b5e02041..2fb415199 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,3 @@ -## 5.3.3 - -* Fix nice mocks generation in mixed mode (generated code is pre null-safety, - while mocked class is null-safe). - ## 5.3.2 * Support analyzer 5.0.0. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 29803bbd0..a9284821b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1149,14 +1149,7 @@ class _MockClassInfo { return ExecutableMember.from2(member, substitution); }); - // The test can be pre-null-safety but if the class - // we want to mock is defined in a null safe library, - // we still need to override methods to get nice mocks. - final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == - OnMissingStub.returnDefault && - typeToMock.element2.enclosingElement.library.isNonNullableByDefault; - - if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { + if (sourceLibIsNonNullable) { cBuilder.methods.addAll( fieldOverrides(members.whereType())); cBuilder.methods @@ -1200,23 +1193,12 @@ class _MockClassInfo { if (accessor.isGetter && typeSystem._returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } - if (accessor.isSetter && sourceLibIsNonNullable) { + if (accessor.isSetter) { yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); } } } - bool _methodNeedsOverride(MethodElement method) { - if (!sourceLibIsNonNullable) { - // If we get here, we are adding overrides only to make - // nice mocks work. We only care about return types then. - return typeSystem._returnTypeIsNonNullable(method); - } - return typeSystem._returnTypeIsNonNullable(method) || - typeSystem._hasNonNullableParameter(method) || - _needsOverrideForVoidStub(method); - } - /// Yields all of the method overrides required for [methods]. /// /// This includes methods of supertypes and mixed in types. @@ -1243,7 +1225,9 @@ class _MockClassInfo { // narrow the return type. continue; } - if (_methodNeedsOverride(method)) { + if (typeSystem._returnTypeIsNonNullable(method) || + typeSystem._hasNonNullableParameter(method) || + _needsOverrideForVoidStub(method)) { _checkForConflictWithCore(method.name); yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } @@ -1624,9 +1608,7 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; - if (parameter.isRequiredNamed && sourceLibIsNonNullable) { - pBuilder.required = true; - } + if (parameter.isRequiredNamed) pBuilder.required = true; if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 7747aca42..cc93276fa 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.3'; +const packageVersion = '5.3.2'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 268c0ed86..75bfb6cee 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.3 +version: 5.3.2 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index daae016e4..4f17585ce 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -649,34 +649,6 @@ void main() { expect(mocksContent, contains('returnValueForMissingStub: 0,')); }); - test( - 'generates mock methods with non-nullable return types, specifying ' - 'legal default values for basic known types, in mixed mode', () async { - var mocksContent = await buildWithNonNullable({ - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - int m({required int x, double? y}); - } - '''), - 'foo|test/foo_test.dart': ''' - // @dart=2.9 - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - - @GenerateMocks( - [], - customMocks: [ - MockSpec(onMissingStub: OnMissingStub.returnDefault), - ], - ) - void main() {} - ''' - }); - expect(mocksContent, contains('returnValue: 0,')); - expect(mocksContent, contains('returnValueForMissingStub: 0,')); - }); - test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for unknown types', () async { From a133dec5622802e8b0680b8ff29179a870cf1e3c Mon Sep 17 00:00:00 2001 From: srawlins Date: Mon, 24 Oct 2022 15:04:21 -0400 Subject: [PATCH 475/607] Avoid `Future.value(null)` as a dummy return value. We allow stubbing a method with a potentially non-nullable type argument argument because we can use a dummy value of `Future.value(null)`. This object is never awaited by mockito, and never exposed to the user, so it is more-or-less safe. However, `Future.value(null)` is now statically invalid for a potentially non-nullable `T`, so it is cleaner if we avoid it in mockito. Subtyping `Future` is allowed, so let's just do it. PiperOrigin-RevId: 483448979 --- pkgs/mockito/lib/src/builder.dart | 39 +++++++++++--- .../mockito/test/builder/auto_mocks_test.dart | 54 ++----------------- 2 files changed, 36 insertions(+), 57 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a9284821b..2c04c5c7d 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -143,7 +143,10 @@ $rawOutput List<_MockTarget> mockTargets, String entryAssetPath, LibraryElement entryLib) async { - final typeVisitor = _TypeVisitor(); + // We pass in the `Future` type so that an asset URI is known for + // `Future`, which is needed when overriding some methods which return + // `FutureOr`. + final typeVisitor = _TypeVisitor(entryLib.typeProvider.futureDynamicType); final seenTypes = {}; final librariesWithTypes = {}; @@ -244,6 +247,10 @@ $rawOutput class _TypeVisitor extends RecursiveElementVisitor { final _elements = {}; + final analyzer.DartType _futureType; + + _TypeVisitor(this._futureType); + @override void visitClassElement(ClassElement element) { _elements.add(element); @@ -315,6 +322,11 @@ class _TypeVisitor extends RecursiveElementVisitor { } } } + if (type.isDartAsyncFutureOr) { + // For some methods which return `FutureOr`, we need a dummy `Future` + // subclass and value. + _addType(_futureType); + } } else if (type is analyzer.FunctionType) { _addType(type.returnType); @@ -1421,13 +1433,24 @@ class _MockClassInfo { return refer('() {}'); } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { final typeArgument = typeArguments.first; - final futureValueArguments = - typeSystem.isPotentiallyNonNullable(typeArgument) - ? [_dummyValue(typeArgument, invocation)] - : []; - return _futureReference(_typeReference(typeArgument)) - .property('value') - .call(futureValueArguments); + final typeArgumentIsPotentiallyNonNullable = + typeSystem.isPotentiallyNonNullable(typeArgument); + if (typeArgument is analyzer.TypeParameterType && + typeArgumentIsPotentiallyNonNullable) { + // We cannot create a valid Future for this unknown, potentially + // non-nullable type, so we'll use a `_FakeFuture`, which will throw + // if awaited. + var futureType = typeProvider.futureType(typeArguments.first); + return _dummyValueImplementing(futureType, invocation); + } else { + // Create a real Future with a legal value, via [Future.value]. + final futureValueArguments = typeArgumentIsPotentiallyNonNullable + ? [_dummyValue(typeArgument, invocation)] + : []; + return _futureReference(_typeReference(typeArgument)) + .property('value') + .call(futureValueArguments); + } } else if (type.isDartCoreInt) { return literalNum(0); } else if (type.isDartCoreIterable || type.isDartCoreList) { diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 28e9cdfaf..9162f5f82 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1572,7 +1572,7 @@ void main() { FutureOr m(); } '''), - _containsAllOf('_i3.FutureOr m() => (super.noSuchMethod('), + _containsAllOf('_i2.FutureOr m() => (super.noSuchMethod('), ); }); @@ -2287,8 +2287,7 @@ void main() { ); }); - test('creates dummy non-null return values for Futures of known core classes', - () async { + test('creates dummy non-null return values for Futures', () async { await expectSingleNonNullableOutput( dedent(r''' class Foo { @@ -2299,58 +2298,15 @@ void main() { ); }); - test( - 'creates dummy non-null return values for Futures of core Function class', + test('creates dummy non-null return values for Futures of unknown types', () async { - await expectSingleNonNullableOutput( - dedent(''' - abstract class Foo { - Future m(); - } - '''), - _containsAllOf('returnValue: _i3.Future.value(() {}),'), - ); - }); - - test('creates dummy non-null return values for Futures of nullable types', - () async { - await expectSingleNonNullableOutput( - dedent(''' - class Bar {} - class Foo { - Future m() async => null; - } - '''), - _containsAllOf('returnValue: _i3.Future<_i2.Bar?>.value(),'), - ); - }); - - test( - 'creates dummy non-null return values for Futures of known typed_data classes', - () async { - await expectSingleNonNullableOutput( - dedent(''' - import 'dart:typed_data'; - class Foo { - Future m() async => Uint8List(0); - } - '''), - _containsAllOf( - 'returnValue: _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0)),'), - ); - }); - - test( - 'creates dummy non-null return values for Futures of known generic core ' - 'classes', () async { await expectSingleNonNullableOutput( dedent(r''' class Foo { - Future> m() async => false; + Future m() async => false; } '''), - _containsAllOf( - 'returnValue: _i3.Future>.value([]),'), + _containsAllOf('returnValue: _FakeFuture_0('), ); }); From a044bd4ddf2a260f9b65d925df669321826ee4aa Mon Sep 17 00:00:00 2001 From: srawlins Date: Wed, 26 Oct 2022 11:23:33 -0400 Subject: [PATCH 476/607] Import https://github.com/dart-lang/mockito/pull/578 Require analyzer 5.2.0 This just deals with the deprecations new in analyzer 5.2.0. PiperOrigin-RevId: 483974480 --- pkgs/mockito/CHANGELOG.md | 4 ++ pkgs/mockito/lib/src/builder.dart | 84 +++++++++++++++---------------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 +- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 2fb415199..ba5ee6d6c 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.3.3-dev + +Require analyzer 5.2.0. + ## 5.3.2 * Support analyzer 5.0.0. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 2c04c5c7d..b47a8fa84 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -156,8 +156,8 @@ $rawOutput return; } seenTypes.add(type); - librariesWithTypes.add(type.element2.library); - type.element2.accept(typeVisitor); + librariesWithTypes.add(type.element.library); + type.element.accept(typeVisitor); // For a type like `Foo`, add the `Bar`. type.typeArguments .whereType() @@ -301,14 +301,14 @@ class _TypeVisitor extends RecursiveElementVisitor { if (type == null) return; if (type is analyzer.InterfaceType) { - final alreadyVisitedElement = _elements.contains(type.element2); - _elements.add(type.element2); + final alreadyVisitedElement = _elements.contains(type.element); + _elements.add(type.element); type.typeArguments.forEach(_addType); if (!alreadyVisitedElement) { - type.element2.typeParameters.forEach(visitTypeParameterElement); + type.element.typeParameters.forEach(visitTypeParameterElement); final toStringMethod = - type.element2.lookUpMethod('toString', type.element2.library); + type.element.lookUpMethod('toString', type.element.library); if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { // In a Fake class which implements a class which overrides `toString` // with additional (optional) parameters, we must also override @@ -317,7 +317,7 @@ class _TypeVisitor extends RecursiveElementVisitor { for (final parameter in toStringMethod.parameters) { final parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - parameterType.element2.accept(this); + parameterType.element.accept(this); } } } @@ -415,7 +415,7 @@ class _MockTarget { this.hasExplicitTypeArguments = false, }); - InterfaceElement get interfaceElement => classType.element2; + InterfaceElement get interfaceElement => classType.element; } /// This class gathers and verifies mock targets referenced in `GenerateMocks` @@ -452,7 +452,7 @@ class _MockTargetGatherer { // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; - final annotationClass = annotation.element!.enclosingElement3!.name; + final annotationClass = annotation.element!.enclosingElement!.name; switch (annotationClass) { case 'GenerateMocks': mockTargets @@ -515,8 +515,8 @@ class _MockTargetGatherer { // `type` have been instantiated to bounds here. Switch to the // declaration, which will be an uninstantiated type. final declarationType = - (type.element2.declaration as InterfaceElement).thisType; - final mockName = 'Mock${declarationType.element2.name}'; + (type.element.declaration as InterfaceElement).thisType; + final mockName = 'Mock${declarationType.element.name}'; mockTargets.add(_MockTarget( declarationType, mockName, @@ -569,7 +569,7 @@ class _MockTargetGatherer { // this case the type argument(s) on `type` have been instantiated to // bounds. Switch to the declaration, which will be an uninstantiated // type. - type = (type.element2.declaration as InterfaceElement).thisType; + type = (type.element.declaration as InterfaceElement).thisType; } else { // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. @@ -590,7 +590,7 @@ class _MockTargetGatherer { }); } final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? - 'Mock${type.element2.name}'; + 'Mock${type.element.name}'; final mixins = []; for (final m in mockSpec.getField('mixins')!.toListValue()!) { final typeToMixin = m.toTypeValue(); @@ -714,7 +714,7 @@ class _MockTargetGatherer { static analyzer.InterfaceType _determineDartType( analyzer.DartType typeToMock, TypeProvider typeProvider) { if (typeToMock is analyzer.InterfaceType) { - final elementToMock = typeToMock.element2; + final elementToMock = typeToMock.element; if (elementToMock is EnumElement) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock an enum: ${elementToMock.displayName}'); @@ -787,7 +787,7 @@ class _MockTargetGatherer { var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => c.interfaces - .map((type) => type.element2) + .map((type) => type.element) .contains(mockTarget.interfaceElement) && _isMockClass(c.supertype!)); if (preexistingMock != null) { @@ -911,7 +911,7 @@ class _MockTargetGatherer { for (var parameter in function.parameters) { var parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - var parameterTypeElement = parameterType.element2; + var parameterTypeElement = parameterType.element; if (parameterTypeElement.isPrivate) { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not @@ -958,7 +958,7 @@ class _MockTargetGatherer { if (typeParameter is analyzer.InterfaceType) { // TODO(srawlins): Check for private names in bound; could be // `List<_Bar>`. - if (typeParameter.element2.isPrivate) { + if (typeParameter.element.isPrivate) { errorMessages.add( '${enclosingElement.fullName} features a private type parameter ' 'bound, and cannot be stubbed.'); @@ -982,7 +982,7 @@ class _MockTargetGatherer { var errorMessages = []; for (var typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { - if (typeArgument.element2.isPrivate && !allowUnsupportedMember) { + if (typeArgument.element.isPrivate && !allowUnsupportedMember) { errorMessages.add( '${enclosingElement.fullName} features a private type argument, ' 'and cannot be stubbed. $_tryUnsupportedMembersMessage'); @@ -1001,8 +1001,8 @@ class _MockTargetGatherer { /// Return whether [type] is the Mock class declared by mockito. bool _isMockClass(analyzer.InterfaceType type) => - type.element2.name == 'Mock' && - type.element2.source.fullName.endsWith('lib/src/mock.dart'); + type.element.name == 'Mock' && + type.element.source.fullName.endsWith('lib/src/mock.dart'); } class _MockLibraryInfo { @@ -1140,8 +1140,8 @@ class _MockClassInfo { for (final mixin in mockTarget.mixins) { cBuilder.mixins.add(TypeReference((b) { b - ..symbol = mixin.element2.name - ..url = _typeImport(mixin.element2) + ..symbol = mixin.element.name + ..url = _typeImport(mixin.element) ..types.addAll(mixin.typeArguments.map(_typeReference)); })); } @@ -1468,7 +1468,7 @@ class _MockClassInfo { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return literalSet({}, elementType); - } else if (type.element2.declaration == typeProvider.streamElement) { + } else if (type.element.declaration == typeProvider.streamElement) { assert(typeArguments.length == 1); final elementType = _typeReference(typeArguments[0]); return TypeReference((b) { @@ -1484,7 +1484,7 @@ class _MockClassInfo { // sealed, e.g. "non-subtypeable", but they // have predicatble constructors; each has an unnamed constructor which // takes a single int argument. - return referImported(type.element2.name, 'dart:typed_data') + return referImported(type.element.name, 'dart:typed_data') .call([literalNum(0)]); // TODO(srawlins): Do other types from typed_data have a "non-subtypeable" // restriction as well? @@ -1544,7 +1544,7 @@ class _MockClassInfo { Expression _dummyValueImplementing( analyzer.InterfaceType dartType, Expression invocation) { - final elementToFake = dartType.element2; + final elementToFake = dartType.element; if (elementToFake is EnumElement) { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); @@ -1638,7 +1638,7 @@ class _MockClassInfo { parameter.computeConstantValue()!, parameter) .code; } on _ReviveException catch (e) { - final method = parameter.enclosingElement3!; + final method = parameter.enclosingElement!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' "'${mockTarget.interfaceElement.displayName}.${method.displayName}'; " @@ -1669,8 +1669,8 @@ class _MockClassInfo { if (!parameter.isCovariant) { return type; } - final method = parameter.enclosingElement3 as MethodElement; - final class_ = method.enclosingElement3 as InterfaceElement; + final method = parameter.enclosingElement as MethodElement; + final class_ = method.enclosingElement as InterfaceElement; final name = Name(method.librarySource.uri, method.name); final overriddenMethods = inheritanceManager.getOverridden2(class_, name); if (overriddenMethods == null) { @@ -1680,7 +1680,7 @@ class _MockClassInfo { while (allOverriddenMethods.isNotEmpty) { final overriddenMethod = allOverriddenMethods.removeFirst(); final secondaryOverrides = inheritanceManager.getOverridden2( - overriddenMethod.enclosingElement3 as InterfaceElement, name); + overriddenMethod.enclosingElement as InterfaceElement, name); if (secondaryOverrides != null) { allOverriddenMethods.addAll(secondaryOverrides); } @@ -1771,7 +1771,7 @@ class _MockClassInfo { // We can create this invocation by referring to a const field or // top-level variable. return referImported( - revivable.accessor, _typeImport(object.type!.element2)); + revivable.accessor, _typeImport(object.type!.element)); } final name = revivable.source.fragment; @@ -1783,9 +1783,9 @@ class _MockClassInfo { for (var pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; - final element = parameter != null && name != object.type!.element2!.name - ? parameter.type.element2 - : object.type!.element2; + final element = parameter != null && name != object.type!.element!.name + ? parameter.type.element + : object.type!.element; final type = referImported(name, _typeImport(element)); if (revivable.accessor.isNotEmpty) { return type.constInstanceNamed( @@ -1950,10 +1950,10 @@ class _MockClassInfo { if (type is analyzer.InterfaceType) { return TypeReference((b) { b - ..symbol = type.element2.name + ..symbol = type.element.name ..isNullable = forceNullable || type.nullabilitySuffix == NullabilitySuffix.question - ..url = _typeImport(type.element2) + ..url = _typeImport(type.element) ..types.addAll(type.typeArguments.map(_typeReference)); }); } else if (type is analyzer.FunctionType) { @@ -1994,13 +1994,13 @@ class _MockClassInfo { } else if (type is analyzer.TypeParameterType) { return TypeReference((b) { b - ..symbol = type.element2.name + ..symbol = type.element.name ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { return referImported( type.getDisplayString(withNullability: false), - _typeImport(type.element2), + _typeImport(type.element), ); } } @@ -2116,12 +2116,12 @@ extension on Element { } else if (this is EnumElement) { return "The enum '$name'"; } else if (this is MethodElement) { - var className = enclosingElement3!.name; + var className = enclosingElement!.name; return "The method '$className.$name'"; } else if (this is MixinElement) { return "The mixin '$name'"; } else if (this is PropertyAccessorElement) { - var className = enclosingElement3!.name; + var className = enclosingElement!.name; return "The property accessor '$className.$name'"; } else { return 'unknown element'; @@ -2137,7 +2137,7 @@ extension on analyzer.DartType { if (self is analyzer.DynamicType) { return false; } else if (self is analyzer.InterfaceType) { - return self.element2.isPrivate || + return self.element.isPrivate || self.typeArguments.any((t) => t.containsPrivateName); } else if (self is analyzer.FunctionType) { return self.returnType.containsPrivateName || @@ -2162,10 +2162,10 @@ extension on analyzer.DartType { /// Returns whether this type is a sealed type from the dart:typed_data /// library. bool get isDartTypedDataSealed { - if (element2!.library!.name != 'dart.typed_data') { + if (element!.library!.name != 'dart.typed_data') { return false; } - final name = element2!.name; + final name = element!.name; return name == 'Float32List' || name == 'Float64List' || name == 'Int8List' || diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index cc93276fa..453ec3a49 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.2'; +const packageVersion = '5.3.3-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 75bfb6cee..93d1779b1 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.2 +version: 5.3.3-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -9,7 +9,7 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - analyzer: '>=4.7.0 <6.0.0' + analyzer: '>=5.2.0 <6.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.2.0 collection: ^1.15.0 From a74af682dedb71e4beaebc96dbdc0586f5cd233e Mon Sep 17 00:00:00 2001 From: srawlins Date: Tue, 8 Nov 2022 04:20:35 -0500 Subject: [PATCH 477/607] Bump minimum SDK version for use of enhanced enums. PiperOrigin-RevId: 486879305 --- pkgs/mockito/CHANGELOG.md | 3 ++- pkgs/mockito/pubspec.yaml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index ba5ee6d6c..848dc043b 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,7 @@ ## 5.3.3-dev -Require analyzer 5.2.0. +* Require analyzer 5.2.0. +* Require Dart >= 2.17.0. ## 5.3.2 diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 93d1779b1..c1b73c58a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,12 +6,12 @@ description: >- repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.12.0-0 <3.0.0' + sdk: '>=2.17.0-0 <3.0.0' dependencies: analyzer: '>=5.2.0 <6.0.0' build: '>=1.3.0 <3.0.0' - code_builder: ^4.2.0 + code_builder: ^4.3.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 From 498c7693797e1168604dcacac10bb31ec49bd577 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 9 Nov 2022 15:07:25 -0800 Subject: [PATCH 478/607] blast_repo fixes (dart-lang/mockito#584) Dependabot --- pkgs/mockito/.github/dependabot.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pkgs/mockito/.github/dependabot.yml diff --git a/pkgs/mockito/.github/dependabot.yml b/pkgs/mockito/.github/dependabot.yml new file mode 100644 index 000000000..1603cdd9e --- /dev/null +++ b/pkgs/mockito/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Dependabot configuration file. +# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" From d9d3a7f4d1a98886f52d760f28953cab616b6aa0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Nov 2022 23:13:31 +0000 Subject: [PATCH 479/607] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index f63563bb4..43344a41f 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest] sdk: [dev] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} @@ -72,7 +72,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.0 with: sdk: dev From 648783a0a56b620c662ecbadd5fad817b8c259ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Nov 2022 23:20:05 +0000 Subject: [PATCH 480/607] Bump dart-lang/setup-dart from 1.0 to 1.3 Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.0 to 1.3. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/v1.0...v1.3) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 43344a41f..c8f4aaa61 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: ${{ matrix.sdk }} - id: install @@ -73,7 +73,7 @@ jobs: os: [ubuntu-latest] steps: - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: dev - id: install From 1333d27ea706435d9c14a6266a461c967beb1c1e Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 14 Nov 2022 09:17:51 -0800 Subject: [PATCH 481/607] blast_repo fixes (dart-lang/mockito#587) GitHub Action --- pkgs/mockito/.github/workflows/test-package.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index c8f4aaa61..96c960609 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -22,8 +22,8 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1.3 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install @@ -48,8 +48,8 @@ jobs: os: [ubuntu-latest] sdk: [dev] steps: - - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1.3 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install @@ -72,8 +72,8 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1.3 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: dev - id: install From 0f5e696c910723b0113939fc8dee2113ef86bbcb Mon Sep 17 00:00:00 2001 From: yanok Date: Fri, 2 Dec 2022 04:42:06 -0500 Subject: [PATCH 482/607] Generate method overrides even then source lib is not null-safe If the class to mock comes from a null-safe library. Otherwise we don't get nice mocks (as niceness depends on overrides). We need to be careful to generate a valid pre-null-safety code, but so far I only found we need to skip generating `required`. PiperOrigin-RevId: 492410078 --- pkgs/mockito/.github/dependabot.yml | 9 ------ pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/builder.dart | 30 +++++++++++++++---- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 28 +++++++++++++++++ 5 files changed, 55 insertions(+), 16 deletions(-) delete mode 100644 pkgs/mockito/.github/dependabot.yml diff --git a/pkgs/mockito/.github/dependabot.yml b/pkgs/mockito/.github/dependabot.yml deleted file mode 100644 index 1603cdd9e..000000000 --- a/pkgs/mockito/.github/dependabot.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Dependabot configuration file. -# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates -version: 2 - -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 848dc043b..7423fc397 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,8 @@ ## 5.3.3-dev * Require analyzer 5.2.0. +* Fix nice mocks generation in mixed mode (generated code is pre null-safety, + while mocked class is null-safe). * Require Dart >= 2.17.0. ## 5.3.2 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b47a8fa84..aea75071f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1161,7 +1161,14 @@ class _MockClassInfo { return ExecutableMember.from2(member, substitution); }); - if (sourceLibIsNonNullable) { + // The test can be pre-null-safety but if the class + // we want to mock is defined in a null safe library, + // we still need to override methods to get nice mocks. + final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == + OnMissingStub.returnDefault && + typeToMock.element2.enclosingElement.library.isNonNullableByDefault; + + if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { cBuilder.methods.addAll( fieldOverrides(members.whereType())); cBuilder.methods @@ -1205,12 +1212,23 @@ class _MockClassInfo { if (accessor.isGetter && typeSystem._returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } - if (accessor.isSetter) { + if (accessor.isSetter && sourceLibIsNonNullable) { yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); } } } + bool _methodNeedsOverride(MethodElement method) { + if (!sourceLibIsNonNullable) { + // If we get here, we are adding overrides only to make + // nice mocks work. We only care about return types then. + return typeSystem._returnTypeIsNonNullable(method); + } + return typeSystem._returnTypeIsNonNullable(method) || + typeSystem._hasNonNullableParameter(method) || + _needsOverrideForVoidStub(method); + } + /// Yields all of the method overrides required for [methods]. /// /// This includes methods of supertypes and mixed in types. @@ -1237,9 +1255,7 @@ class _MockClassInfo { // narrow the return type. continue; } - if (typeSystem._returnTypeIsNonNullable(method) || - typeSystem._hasNonNullableParameter(method) || - _needsOverrideForVoidStub(method)) { + if (_methodNeedsOverride(method)) { _checkForConflictWithCore(method.name); yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } @@ -1631,7 +1647,9 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; - if (parameter.isRequiredNamed) pBuilder.required = true; + if (parameter.isRequiredNamed && sourceLibIsNonNullable) { + pBuilder.required = true; + } if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c1b73c58a..c1a568015 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: analyzer: '>=5.2.0 <6.0.0' build: '>=1.3.0 <3.0.0' - code_builder: ^4.3.0 + code_builder: ^4.2.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.10 diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 4f17585ce..daae016e4 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -649,6 +649,34 @@ void main() { expect(mocksContent, contains('returnValueForMissingStub: 0,')); }); + test( + 'generates mock methods with non-nullable return types, specifying ' + 'legal default values for basic known types, in mixed mode', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + int m({required int x, double? y}); + } + '''), + 'foo|test/foo_test.dart': ''' + // @dart=2.9 + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks( + [], + customMocks: [ + MockSpec(onMissingStub: OnMissingStub.returnDefault), + ], + ) + void main() {} + ''' + }); + expect(mocksContent, contains('returnValue: 0,')); + expect(mocksContent, contains('returnValueForMissingStub: 0,')); + }); + test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for unknown types', () async { From aa23400bc2d8163f8fe5d3e141939107946731bc Mon Sep 17 00:00:00 2001 From: yanok Date: Fri, 2 Dec 2022 17:33:58 -0500 Subject: [PATCH 483/607] Automated g4 rollback of changelist 492410078. *** Reason for rollback *** Breaks more tests than expected. *** Original change description *** Generate method overrides even then source lib is not null-safe If the class to mock comes from a null-safe library. Otherwise we don't get nice mocks (as niceness depends on overrides). We need to be careful to generate a valid pre-null-safety code, but so far I only found we need to skip generating `required`. *** PiperOrigin-RevId: 492562666 --- pkgs/mockito/CHANGELOG.md | 2 -- pkgs/mockito/lib/src/builder.dart | 30 ++++--------------- .../test/builder/custom_mocks_test.dart | 28 ----------------- 3 files changed, 6 insertions(+), 54 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 7423fc397..848dc043b 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,8 +1,6 @@ ## 5.3.3-dev * Require analyzer 5.2.0. -* Fix nice mocks generation in mixed mode (generated code is pre null-safety, - while mocked class is null-safe). * Require Dart >= 2.17.0. ## 5.3.2 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index aea75071f..b47a8fa84 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1161,14 +1161,7 @@ class _MockClassInfo { return ExecutableMember.from2(member, substitution); }); - // The test can be pre-null-safety but if the class - // we want to mock is defined in a null safe library, - // we still need to override methods to get nice mocks. - final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == - OnMissingStub.returnDefault && - typeToMock.element2.enclosingElement.library.isNonNullableByDefault; - - if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { + if (sourceLibIsNonNullable) { cBuilder.methods.addAll( fieldOverrides(members.whereType())); cBuilder.methods @@ -1212,23 +1205,12 @@ class _MockClassInfo { if (accessor.isGetter && typeSystem._returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } - if (accessor.isSetter && sourceLibIsNonNullable) { + if (accessor.isSetter) { yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); } } } - bool _methodNeedsOverride(MethodElement method) { - if (!sourceLibIsNonNullable) { - // If we get here, we are adding overrides only to make - // nice mocks work. We only care about return types then. - return typeSystem._returnTypeIsNonNullable(method); - } - return typeSystem._returnTypeIsNonNullable(method) || - typeSystem._hasNonNullableParameter(method) || - _needsOverrideForVoidStub(method); - } - /// Yields all of the method overrides required for [methods]. /// /// This includes methods of supertypes and mixed in types. @@ -1255,7 +1237,9 @@ class _MockClassInfo { // narrow the return type. continue; } - if (_methodNeedsOverride(method)) { + if (typeSystem._returnTypeIsNonNullable(method) || + typeSystem._hasNonNullableParameter(method) || + _needsOverrideForVoidStub(method)) { _checkForConflictWithCore(method.name); yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } @@ -1647,9 +1631,7 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; - if (parameter.isRequiredNamed && sourceLibIsNonNullable) { - pBuilder.required = true; - } + if (parameter.isRequiredNamed) pBuilder.required = true; if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index daae016e4..4f17585ce 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -649,34 +649,6 @@ void main() { expect(mocksContent, contains('returnValueForMissingStub: 0,')); }); - test( - 'generates mock methods with non-nullable return types, specifying ' - 'legal default values for basic known types, in mixed mode', () async { - var mocksContent = await buildWithNonNullable({ - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - int m({required int x, double? y}); - } - '''), - 'foo|test/foo_test.dart': ''' - // @dart=2.9 - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - - @GenerateMocks( - [], - customMocks: [ - MockSpec(onMissingStub: OnMissingStub.returnDefault), - ], - ) - void main() {} - ''' - }); - expect(mocksContent, contains('returnValue: 0,')); - expect(mocksContent, contains('returnValueForMissingStub: 0,')); - }); - test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for unknown types', () async { From 8c07d0eabab9c229a427834ba7ba44da509bac14 Mon Sep 17 00:00:00 2001 From: yanok Date: Tue, 6 Dec 2022 02:28:19 -0500 Subject: [PATCH 484/607] Add override for `Object.operator==` in `SmartFake` PiperOrigin-RevId: 493215781 --- pkgs/mockito/lib/src/mock.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 320170907..4b2c9e90a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -228,6 +228,13 @@ class SmartFake { final Object _parent; final Invocation _parentInvocation; final StackTrace _createdStackTrace; + + // Override [Object.operator==] to accept `Object?`. This is needed to + // make Analyzer happy, if we fake classes that override `==` to + // accept `Object?` or `dynamic` (most notably [Interceptor]). + @override + bool operator ==(Object? other) => identical(this, other); + @override dynamic noSuchMethod(Invocation invocation) => throw FakeUsedError( _parentInvocation, invocation, _parent, _createdStackTrace); From 13c7ee564a975fced2e107c3003482cbbcd07915 Mon Sep 17 00:00:00 2001 From: yanok Date: Tue, 6 Dec 2022 02:28:40 -0500 Subject: [PATCH 485/607] Generate method overrides even then source lib is not null-safe If the class to mock comes from a null-safe library. Otherwise we don't get nice mocks (as niceness depends on overrides). We need to be careful to generate a valid pre-null-safety code, but so far I only found we need to skip generating `required`. PiperOrigin-RevId: 493215835 --- pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/builder.dart | 30 +++++++++++++++---- .../test/builder/custom_mocks_test.dart | 28 +++++++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 848dc043b..7423fc397 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,8 @@ ## 5.3.3-dev * Require analyzer 5.2.0. +* Fix nice mocks generation in mixed mode (generated code is pre null-safety, + while mocked class is null-safe). * Require Dart >= 2.17.0. ## 5.3.2 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b47a8fa84..aea75071f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1161,7 +1161,14 @@ class _MockClassInfo { return ExecutableMember.from2(member, substitution); }); - if (sourceLibIsNonNullable) { + // The test can be pre-null-safety but if the class + // we want to mock is defined in a null safe library, + // we still need to override methods to get nice mocks. + final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == + OnMissingStub.returnDefault && + typeToMock.element2.enclosingElement.library.isNonNullableByDefault; + + if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { cBuilder.methods.addAll( fieldOverrides(members.whereType())); cBuilder.methods @@ -1205,12 +1212,23 @@ class _MockClassInfo { if (accessor.isGetter && typeSystem._returnTypeIsNonNullable(accessor)) { yield Method((mBuilder) => _buildOverridingGetter(mBuilder, accessor)); } - if (accessor.isSetter) { + if (accessor.isSetter && sourceLibIsNonNullable) { yield Method((mBuilder) => _buildOverridingSetter(mBuilder, accessor)); } } } + bool _methodNeedsOverride(MethodElement method) { + if (!sourceLibIsNonNullable) { + // If we get here, we are adding overrides only to make + // nice mocks work. We only care about return types then. + return typeSystem._returnTypeIsNonNullable(method); + } + return typeSystem._returnTypeIsNonNullable(method) || + typeSystem._hasNonNullableParameter(method) || + _needsOverrideForVoidStub(method); + } + /// Yields all of the method overrides required for [methods]. /// /// This includes methods of supertypes and mixed in types. @@ -1237,9 +1255,7 @@ class _MockClassInfo { // narrow the return type. continue; } - if (typeSystem._returnTypeIsNonNullable(method) || - typeSystem._hasNonNullableParameter(method) || - _needsOverrideForVoidStub(method)) { + if (_methodNeedsOverride(method)) { _checkForConflictWithCore(method.name); yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } @@ -1631,7 +1647,9 @@ class _MockClassInfo { _typeReference(superParameterType, forceNullable: forceNullable); } if (parameter.isNamed) pBuilder.named = true; - if (parameter.isRequiredNamed) pBuilder.required = true; + if (parameter.isRequiredNamed && sourceLibIsNonNullable) { + pBuilder.required = true; + } if (parameter.defaultValueCode != null) { try { pBuilder.defaultTo = _expressionFromDartObject( diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 4f17585ce..daae016e4 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -649,6 +649,34 @@ void main() { expect(mocksContent, contains('returnValueForMissingStub: 0,')); }); + test( + 'generates mock methods with non-nullable return types, specifying ' + 'legal default values for basic known types, in mixed mode', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + int m({required int x, double? y}); + } + '''), + 'foo|test/foo_test.dart': ''' + // @dart=2.9 + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks( + [], + customMocks: [ + MockSpec(onMissingStub: OnMissingStub.returnDefault), + ], + ) + void main() {} + ''' + }); + expect(mocksContent, contains('returnValue: 0,')); + expect(mocksContent, contains('returnValueForMissingStub: 0,')); + }); + test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for unknown types', () async { From a67b65848dc3d4dcf876b807472868a385077858 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 9 Dec 2022 13:16:20 -0500 Subject: [PATCH 486/607] Stop using deprecated analyzer APIs PiperOrigin-RevId: 494203168 --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index aea75071f..7735a9f5c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1166,7 +1166,7 @@ class _MockClassInfo { // we still need to override methods to get nice mocks. final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == OnMissingStub.returnDefault && - typeToMock.element2.enclosingElement.library.isNonNullableByDefault; + typeToMock.element.enclosingElement.library.isNonNullableByDefault; if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { cBuilder.methods.addAll( From a0a7b61bbd9cfc7405a500b46986854c6d315270 Mon Sep 17 00:00:00 2001 From: yanok Date: Mon, 12 Dec 2022 11:12:10 -0500 Subject: [PATCH 487/607] Override `SmartFake.toString` to be super-verbose Ideally I'd like it to throw, but I found that some tests use these fakes as throw-away values: they got rendered in some HTML or logged somewhere but never checked by the test. Changing `toString` to throw forces such tests to add meaningless expectations, which makes tests less readable. So instead of throwing I decided to make `toString` just convey the same amount of information that the exception has. This way if the test doesn't care about the value, it will keep passing. And if it does care, test failure will contain useful information, suggesting what needs to be stubbed. PiperOrigin-RevId: 494732315 --- pkgs/mockito/lib/src/mock.dart | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 4b2c9e90a..f4b52d21f 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -228,6 +228,7 @@ class SmartFake { final Object _parent; final Invocation _parentInvocation; final StackTrace _createdStackTrace; + late final StackTrace _toStringFirstCalled = StackTrace.current; // Override [Object.operator==] to accept `Object?`. This is needed to // make Analyzer happy, if we fake classes that override `==` to @@ -240,6 +241,26 @@ class SmartFake { _parentInvocation, invocation, _parent, _createdStackTrace); SmartFake(this._parent, this._parentInvocation) : _createdStackTrace = StackTrace.current; + + @override + String toString() { + final memberName = _symbolToString(_parentInvocation.memberName); + return '''Fake object created as a result of calling unstubbed member +$memberName of a mock. + +Here is the stack trace where $memberName was called: + +$_createdStackTrace + +Normally you would never see this message, if it breaks your test, +consider adding a stub for ${_parent.runtimeType}.$memberName using Mockito's +'when' API. + +Here is the stack trace where toString() was first called: + +$_toStringFirstCalled +'''; + } } class FakeUsedError extends Error { From bb3a4571168d94c1735e2124e0f85c7682335e66 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 9 Jan 2023 13:45:19 -0800 Subject: [PATCH 488/607] Migrate from no-implicit-casts to strict-casts --- pkgs/mockito/analysis_options.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 5a16769b2..076a3d292 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,7 +1,7 @@ include: package:pedantic/analysis_options.yaml analyzer: - strong-mode: - implicit-casts: false + language: + strict-casts: true linter: rules: From 263d65dd51c533926c5e5839ad49dfffebdbd202 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Thu, 19 Jan 2023 09:17:02 -0800 Subject: [PATCH 489/607] blast_repo fixes dependabot --- pkgs/mockito/.github/dependabot.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pkgs/mockito/.github/dependabot.yml diff --git a/pkgs/mockito/.github/dependabot.yml b/pkgs/mockito/.github/dependabot.yml new file mode 100644 index 000000000..952351121 --- /dev/null +++ b/pkgs/mockito/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Dependabot configuration file. +# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates + +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly From c64fce816dedc3a06cd885e9dc78bd14d2b6abe0 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 20 Jan 2023 10:13:52 -0800 Subject: [PATCH 490/607] GitHub Sync (dart-lang/mockito#602) * Added support for generated mocks from typedef-aliased classes. PiperOrigin-RevId: 501338177 * Update the mockito readme file. PiperOrigin-RevId: 503229973 * Ignore an upcoming diagnostic for `operator ==`. The comment on the `operator ==` override explains why it is needed. PiperOrigin-RevId: 503381552 --- pkgs/mockito/.github/dependabot.yml | 2 +- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/README.md | 9 +- pkgs/mockito/lib/src/builder.dart | 125 +++++--- pkgs/mockito/lib/src/mock.dart | 1 + .../mockito/test/builder/auto_mocks_test.dart | 206 +++++++++++- .../test/builder/custom_mocks_test.dart | 297 +++++++++++++++++- 7 files changed, 575 insertions(+), 66 deletions(-) diff --git a/pkgs/mockito/.github/dependabot.yml b/pkgs/mockito/.github/dependabot.yml index 952351121..cf8e11086 100644 --- a/pkgs/mockito/.github/dependabot.yml +++ b/pkgs/mockito/.github/dependabot.yml @@ -2,7 +2,7 @@ # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates version: 2 -updates: +updates: - package-ecosystem: github-actions directory: / schedule: diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 7423fc397..42496d5d7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -4,6 +4,7 @@ * Fix nice mocks generation in mixed mode (generated code is pre null-safety, while mocked class is null-safe). * Require Dart >= 2.17.0. +* Support typedef-aliased classes in `@GenerateMocks` and `@GenerateNiceMocks` ## 5.3.2 diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 47d394fcf..a658dbb24 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,10 +1,9 @@ -# mockito +[![Dart CI](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml) +[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito) +[![package publisher](https://img.shields.io/pub/publisher/mockito.svg)](https://pub.dev/packages/mockito/publisher) Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito). -[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito) -[![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito) - ## Let's create mocks Mockito 5.0.0 supports Dart's new **null safety** language feature in Dart 2.12, @@ -330,7 +329,7 @@ void main() { } ``` -The `Cat.sound` method retuns a non-nullable String, but no stub has been made +The `Cat.sound` method returns a non-nullable String, but no stub has been made with `when(cat.sound())`, so what should the code do? What is the "missing stub" behavior? diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7735a9f5c..422be12de 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -158,6 +158,7 @@ $rawOutput seenTypes.add(type); librariesWithTypes.add(type.element.library); type.element.accept(typeVisitor); + if (type.alias != null) type.alias!.element.accept(typeVisitor); // For a type like `Foo`, add the `Bar`. type.typeArguments .whereType() @@ -296,6 +297,12 @@ class _TypeVisitor extends RecursiveElementVisitor { super.visitTypeParameterElement(element); } + @override + void visitTypeAliasElement(TypeAliasElement element) { + _elements.add(element); + super.visitTypeAliasElement(element); + } + /// Adds [type] to the collected [_elements]. void _addType(analyzer.DartType? type) { if (type == null) return; @@ -406,14 +413,15 @@ class _MockTarget { final bool hasExplicitTypeArguments; _MockTarget( - this.classType, - this.mockName, { + this.classType, { required this.mixins, required this.onMissingStub, required this.unsupportedMembers, required this.fallbackGenerators, this.hasExplicitTypeArguments = false, - }); + String? mockName, + }) : mockName = mockName ?? + 'Mock${classType.alias?.element.name ?? classType.element.name}'; InterfaceElement get interfaceElement => classType.element; } @@ -509,17 +517,17 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'Mockito cannot mock `dynamic`'); } - final type = _determineDartType(typeToMock, entryLib.typeProvider); - // For a generic class like `Foo` or `Foo`, a type - // literal (`Foo`) cannot express type arguments. The type argument(s) on - // `type` have been instantiated to bounds here. Switch to the - // declaration, which will be an uninstantiated type. - final declarationType = - (type.element.declaration as InterfaceElement).thisType; - final mockName = 'Mock${declarationType.element.name}'; + var type = _determineDartType(typeToMock, entryLib.typeProvider); + if (type.alias == null) { + // For a generic class without an alias like `Foo` or + // `Foo`, a type literal (`Foo`) cannot express type + // arguments. The type argument(s) on `type` have been instantiated to + // bounds here. Switch to the declaration, which will be an + // uninstantiated type. + type = (type.element.declaration as InterfaceElement).thisType; + } mockTargets.add(_MockTarget( - declarationType, - mockName, + type, mixins: [], onMissingStub: OnMissingStub.throwException, unsupportedMembers: {}, @@ -562,35 +570,41 @@ class _MockTargetGatherer { } } var type = _determineDartType(typeToMock, entryLib.typeProvider); - final mockTypeArguments = mockType?.typeArguments; - if (mockTypeArguments == null) { - // The type was given without explicit type arguments. In - // this case the type argument(s) on `type` have been instantiated to - // bounds. Switch to the declaration, which will be an uninstantiated - // type. - type = (type.element.declaration as InterfaceElement).thisType; - } else { + if (mockTypeArguments != null) { + final typeName = + type.alias?.element.getDisplayString(withNullability: false) ?? + 'type $type'; + final typeArguments = type.alias?.typeArguments ?? type.typeArguments; // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. - type.typeArguments.forEachIndexed((typeArgIdx, typeArgument) { + typeArguments.forEachIndexed((typeArgIdx, typeArgument) { if (!typeArgument.isDynamic) return; if (typeArgIdx >= mockTypeArguments.arguments.length) return; final typeArgAst = mockTypeArguments.arguments[typeArgIdx]; if (typeArgAst is! ast.NamedType) { // Is this even possible? throw InvalidMockitoAnnotationException( - 'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type'); + 'Undefined type $typeArgAst passed as the ' + '${(typeArgIdx + 1).ordinal} type argument for mocked ' + '$typeName.'); } if (typeArgAst.name.name == 'dynamic') return; throw InvalidMockitoAnnotationException( - 'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type. ' - 'Are you trying to pass to-be-generated mock class as a type argument? Mockito does not support that (yet).', + 'Undefined type $typeArgAst passed as the ' + '${(typeArgIdx + 1).ordinal} type argument for mocked $typeName. Are ' + 'you trying to pass to-be-generated mock class as a type argument? ' + 'Mockito does not support that (yet).', ); }); + } else if (type.alias == null) { + // The mock type was given without explicit type arguments. In this case + // the type argument(s) on `type` have been instantiated to bounds if this + // isn't a type alias. Switch to the declaration, which will be an + // uninstantiated type. + type = (type.element.declaration as InterfaceElement).thisType; } - final mockName = mockSpec.getField('mockName')!.toSymbolValue() ?? - 'Mock${type.element.name}'; + final mockName = mockSpec.getField('mockName')!.toSymbolValue(); final mixins = []; for (final m in mockSpec.getField('mixins')!.toListValue()!) { final typeToMixin = m.toTypeValue(); @@ -662,7 +676,7 @@ class _MockTargetGatherer { mockSpec.getField('fallbackGenerators')!.toMapValue()!; return _MockTarget( type, - mockName, + mockName: mockName, mixins: mixins, onMissingStub: onMissingStub, unsupportedMembers: unsupportedMembers, @@ -740,17 +754,17 @@ class _MockTargetGatherer { "'${elementToMock.displayName}' for the following reasons:\n" '$joinedMessages'); } + if (typeToMock.alias != null && + typeToMock.nullabilitySuffix == NullabilitySuffix.question) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock a type-aliased nullable type: ' + '${typeToMock.alias!.element.name}'); + } return typeToMock; } - var aliasElement = typeToMock.alias?.element; - if (aliasElement != null) { - throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: ' - '${aliasElement.displayName}'); - } else { - throw InvalidMockitoAnnotationException( - 'Mockito cannot mock a non-class: $typeToMock'); - } + throw InvalidMockitoAnnotationException('Mockito cannot mock a non-class: ' + '${typeToMock.alias?.element.name ?? typeToMock.toString()}'); } void _checkClassesToMockAreValid() { @@ -1097,10 +1111,14 @@ class _MockClassInfo { }); Class _buildMockClass() { - final typeToMock = mockTarget.classType; + final typeAlias = mockTarget.classType.alias; + final aliasedElement = typeAlias?.element; + final aliasedType = + typeAlias?.element.aliasedType as analyzer.InterfaceType?; + final typeToMock = aliasedType ?? mockTarget.classType; final classToMock = mockTarget.interfaceElement; final classIsImmutable = classToMock.metadata.any((it) => it.isImmutable); - final className = classToMock.name; + final className = aliasedElement?.name ?? classToMock.name; return Class((cBuilder) { cBuilder @@ -1118,25 +1136,32 @@ class _MockClassInfo { // For each type parameter on [classToMock], the Mock class needs a type // parameter with same type variables, and a mirrored type argument for // the "implements" clause. - var typeArguments = []; + final typeReferences = []; + + final typeParameters = + aliasedElement?.typeParameters ?? classToMock.typeParameters; + final typeArguments = + typeAlias?.typeArguments ?? typeToMock.typeArguments; + if (mockTarget.hasExplicitTypeArguments) { // [typeToMock] is a reference to a type with type arguments (for // example: `Foo`). Generate a non-generic mock class which // implements the mock target with said type arguments. For example: // `class MockFoo extends Mock implements Foo {}` - for (var typeArgument in typeToMock.typeArguments) { - typeArguments.add(_typeReference(typeArgument)); + for (var typeArgument in typeArguments) { + typeReferences.add(_typeReference(typeArgument)); } } else { // [typeToMock] is a simple reference to a generic type (for example: // `Foo`, a reference to `class Foo {}`). Generate a generic mock // class which perfectly mirrors the type parameters on [typeToMock], // forwarding them to the "implements" clause. - for (var typeParameter in classToMock.typeParameters) { + for (var typeParameter in typeParameters) { cBuilder.types.add(_typeParameterReference(typeParameter)); - typeArguments.add(refer(typeParameter.name)); + typeReferences.add(refer(typeParameter.name)); } } + for (final mixin in mockTarget.mixins) { cBuilder.mixins.add(TypeReference((b) { b @@ -1147,15 +1172,21 @@ class _MockClassInfo { } cBuilder.implements.add(TypeReference((b) { b - ..symbol = classToMock.name - ..url = _typeImport(mockTarget.interfaceElement) - ..types.addAll(typeArguments); + ..symbol = className + ..url = _typeImport(aliasedElement ?? classToMock) + ..types.addAll(typeReferences); })); if (mockTarget.onMissingStub == OnMissingStub.throwException) { cBuilder.constructors.add(_constructorWithThrowOnMissingStub); } - final substitution = Substitution.fromInterfaceType(typeToMock); + final substitution = Substitution.fromPairs([ + ...classToMock.typeParameters, + ...?aliasedElement?.typeParameters, + ], [ + ...typeToMock.typeArguments, + ...?typeAlias?.typeArguments, + ]); final members = inheritanceManager.getInterface(classToMock).map.values.map((member) { return ExecutableMember.from2(member, substitution); diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index f4b52d21f..7fc4833a3 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -234,6 +234,7 @@ class SmartFake { // make Analyzer happy, if we fake classes that override `==` to // accept `Object?` or `dynamic` (most notably [Interceptor]). @override + // ignore: non_nullable_equals_parameter bool operator ==(Object? other) => identical(this, other); @override diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 9162f5f82..fcfcca027 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -118,11 +118,15 @@ void main() { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 12)) + languageVersion: LanguageVersion(2, 13)) ]); - await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, - writer: writer, packageConfig: packageConfig); + await withEnabledExperiments( + () async => await testBuilder( + buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, packageConfig: packageConfig), + ['nonfunction-type-aliases'], + ); var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]!); } @@ -1653,7 +1657,7 @@ void main() { void m( T? a, T? b, - ) => + ) => ''')), ); }); @@ -2069,7 +2073,7 @@ void main() { int get m => (super.noSuchMethod( Invocation.getter(#m), returnValue: 0, - ) as int); + ) as int); '''), dedent2(''' set m(int? _m) => super.noSuchMethod( Invocation.setter( @@ -2156,7 +2160,7 @@ void main() { [other], ), returnValue: 0, - ) as int); + ) as int); ''')), ); }); @@ -3240,7 +3244,7 @@ void main() { ); }); - test('throws when GenerateMocks references a typedef', () async { + test('throws when GenerateMocks references a function typedef', () async { _expectBuilderThrows( assets: { ...annotationsAsset, @@ -3249,7 +3253,7 @@ void main() { typedef Foo = void Function(); '''), }, - message: 'Mockito cannot mock a typedef: Foo', + message: 'Mockito cannot mock a non-class: Foo', ); }); @@ -3366,6 +3370,178 @@ void main() { _containsAllOf('// ignore: must_be_immutable\nclass MockFoo'), ); }); + + group('typedef mocks', () { + group('are generated properly', () { + test('when aliased type parameters are instantiated', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Bar]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when no aliased type parameters are instantiated', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Bar]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when the aliased type has no type parameters', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Bar]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when the typedef defines a type', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Bar]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when the typedef defines a bounded type', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Bar]) + void main() {} + ''' + }); + + expect(mocksContent, + contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when the aliased type is a mixin', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + mixin Foo { + String get value; + } + + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Bar]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + expect(mocksContent, contains('String get value')); + }); + + test('when the aliased type is another typedef', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + + typedef Bar = Foo; + typedef Baz = Bar; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Baz]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBaz extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Baz')); + }); + }); + + test('generation throws when the aliased type is nullable', () { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + T get value; + } + + typedef Bar = Foo?; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Bar]) + void main() {} + ''' + }, + message: + contains('Mockito cannot mock a type-aliased nullable type: Bar'), + enabledExperiments: ['nonfunction-type-aliases'], + languageVersion: LanguageVersion(2, 13), + ); + }); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( @@ -3377,16 +3553,24 @@ TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( void _expectBuilderThrows({ required Map assets, required dynamic /*String|Matcher>*/ message, + List enabledExperiments = const [], + LanguageVersion? languageVersion, }) { var packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 12)) + languageVersion: languageVersion ?? LanguageVersion(2, 12)) ]); expect( - () async => await testBuilder(buildMocks(BuilderOptions({})), assets, - packageConfig: packageConfig), + () => withEnabledExperiments( + () => testBuilder( + buildMocks(BuilderOptions({})), + assets, + packageConfig: packageConfig, + ), + enabledExperiments, + ), throwsA(TypeMatcher() .having((e) => e.message, 'message', message))); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index daae016e4..4db4961e1 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1137,7 +1137,7 @@ void main() { ); }); - test('throws when MockSpec references a typedef', () async { + test('throws when MockSpec references a function typedef', () async { _expectBuilderThrows( assets: { ...annotationsAsset, @@ -1146,7 +1146,7 @@ void main() { typedef Foo = void Function(); '''), }, - message: 'Mockito cannot mock a typedef: Foo', + message: 'Mockito cannot mock a non-class: Foo', ); }); @@ -1377,6 +1377,299 @@ void main() { expect(mocksContent, contains('class MockFoo')); expect(mocksContent, contains('class MockBar')); }); + + group('typedef mocks', () { + group('are generated properly', () { + test('when all aliased type parameters are instantiated', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when no aliased type parameters are instantiated', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test( + 'when the typedef defines a type and it corresponds to a different ' + 'index of the aliased type', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec>(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when the aliased type has no type parameters', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when the mock instantiates another typedef', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + + class Baz {} + typedef Qux = Baz; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec>>(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockQux extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Qux<_i2.Foo>')); + }); + + test( + 'when the typedef defines a bounded class type and it is NOT ' + 'instantiated', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + class Bar {} + typedef Baz = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, + contains('class MockBaz extends _i2.Mock')); + expect(mocksContent, contains('implements _i1.Baz')); + }); + + test( + 'when the typedef defines a bounded type and the mock instantiates ' + 'it', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec>(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + }); + + test('when the aliased type has a parameterized method', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + A get value; + } + '''), + 'bar|lib/bar.dart': dedent(r''' + import 'package:foo/foo.dart'; + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:bar/bar.dart'; + import 'package:mockito/annotations.dart'; + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('String get value')); + }); + + test( + 'when the typedef is parameterized and the aliased type has a ' + 'parameterized method', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + A get value; + } + '''), + 'bar|lib/bar.dart': dedent(r''' + import 'package:foo/foo.dart'; + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:bar/bar.dart'; + import 'package:mockito/annotations.dart'; + + X fallbackGenerator() { + throw 'unknown'; + } + + @GenerateNiceMocks([ + MockSpec(fallbackGenerators: {#value: fallbackGenerator}), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('T get value')); + }); + + test('when the aliased type is a mixin', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + mixin Foo { + String get value; + } + + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBar extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Bar')); + expect(mocksContent, contains('String get value')); + }); + + test('when the aliased type is another typedef', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + + typedef Bar = Foo; + typedef Baz = Bar; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockBaz extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Baz')); + }); + }); + + test('generation throws when the aliased type is nullable', () { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + T get value; + } + + typedef Bar = Foo?; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([MockSpec()]) + void main() {} + ''' + }, + message: + contains('Mockito cannot mock a type-aliased nullable type: Bar'), + ); + }); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From 39954baaee7c0225f69ca712146f59ea2765e312 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 20 Jan 2023 10:31:34 -0800 Subject: [PATCH 491/607] generate code before we analyze (dart-lang/mockito#601) * generate code before we analyze * fix config --- .../.github/workflows/test-package.yml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 96c960609..a01eaa414 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -12,15 +12,16 @@ on: env: PUB_ENVIRONMENT: bot.github +permissions: read-all + jobs: - # Check code formatting and static analysis on a single OS (linux) - # against Dart dev. + # Check code formatting and static analysis against stable and dev SDKs. analyze: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - sdk: [dev] + sdk: [stable, dev] steps: - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d @@ -31,14 +32,12 @@ jobs: run: dart pub get - name: Check formatting run: dart format --output=none --set-exit-if-changed . - if: always() && steps.install.outcome == 'success' + - name: Build generated artifacts + run: dart pub run build_runner build - name: Analyze code - run: dart analyze lib - if: always() && steps.install.outcome == 'success' + run: dart analyze - # Run tests on a matrix consisting of two dimensions: - # 1. OS: ubuntu-latest, (macos-latest, windows-latest) - # 2. release channel: dev + # Run tests against stable and dev SDKs. test: needs: analyze runs-on: ${{ matrix.os }} @@ -46,7 +45,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [dev] + sdk: [stable, dev] steps: - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d @@ -64,6 +63,7 @@ jobs: - name: Run DDC tests run: dart run build_runner test -- --platform chrome if: always() && steps.install.outcome == 'success' + document: needs: analyze runs-on: ${{ matrix.os }} From cba0ae7eb201f464f18e8683e3752e22a00369b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Jan 2023 22:44:35 -0800 Subject: [PATCH 492/607] Bump actions/checkout from 3.1.0 to 3.3.0 (dart-lang/mockito#599) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.3.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8...ac593985615ec2ede58e132d2e21d2b1cbd6127c) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index a01eaa414..1e1210160 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: matrix: sdk: [stable, dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -47,7 +47,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -72,7 +72,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: dev From 6727ea6d614928aa954e0006ee534bbd9084a443 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 08:42:59 -0800 Subject: [PATCH 493/607] Bump dart-lang/setup-dart from 1.3 to 1.4 (dart-lang/mockito#600) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.3 to 1.4. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/6a218f2413a3e78e9087f638a238f6b40893203d...a57a6c04cf7d4840e88432aad6281d1e125f0d46) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 1e1210160..d02771f8e 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -24,7 +24,7 @@ jobs: sdk: [stable, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install @@ -48,7 +48,7 @@ jobs: sdk: [stable, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install @@ -73,7 +73,7 @@ jobs: os: [ubuntu-latest] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: dev - id: install From 848a4d4ab10f06a15f0637aec80224bf9d3e55dc Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 30 Jan 2023 10:17:34 -0800 Subject: [PATCH 494/607] Latest build_web_compilers, move to pkg:lints, fix breaks (dart-lang/mockito#605) Require Dart 2.18 --- .../.github/workflows/test-package.yml | 4 ++-- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/analysis_options.yaml | 11 +-------- pkgs/mockito/lib/src/mock.dart | 24 ++++++++----------- pkgs/mockito/pubspec.yaml | 6 ++--- 5 files changed, 17 insertions(+), 30 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index d02771f8e..05c53f829 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [stable, dev] + sdk: [2.18.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 @@ -45,7 +45,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [stable, dev] + sdk: [2.18.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 42496d5d7..eb338ee0f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,7 +3,7 @@ * Require analyzer 5.2.0. * Fix nice mocks generation in mixed mode (generated code is pre null-safety, while mocked class is null-safe). -* Require Dart >= 2.17.0. +* Require Dart >= 2.18.0. * Support typedef-aliased classes in `@GenerateMocks` and `@GenerateNiceMocks` ## 5.3.2 diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 076a3d292..860c620e8 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,19 +1,10 @@ -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml analyzer: language: strict-casts: true linter: rules: - # Errors - comment_references - - control_flow_in_finally - - empty_statements - - hash_and_equals - test_types_in_equals - throw_in_finally - - # Style - - await_only_futures - - camel_case_types - - non_constant_identifier_names diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 7fc4833a3..55d68ca24 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -947,9 +947,6 @@ typedef Answering = T Function(Invocation realInvocation); typedef Verification = VerificationResult Function(T matchingInvocations); -typedef _InOrderVerification = List Function( - List recordedInvocations); - /// Verify that a method on a mock object was never called with the given /// arguments. /// @@ -1058,7 +1055,8 @@ Verification _makeVerify(bool never) { /// given, but not that those were the only calls. In the example above, if /// other calls were made to `eatFood` or `sound` between the three given /// calls, or before or after them, the verification will still succeed. -_InOrderVerification get verifyInOrder { +List Function(List recordedInvocations) + get verifyInOrder { if (_verifyCalls.isNotEmpty) { throw StateError(_verifyCalls.join()); } @@ -1113,7 +1111,7 @@ void verifyNoMoreInteractions(var mock) { if (mock is Mock) { var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); if (unverified.isNotEmpty) { - fail('No more calls expected, but following found: ' + unverified.join()); + fail('No more calls expected, but following found: ${unverified.join()}'); } } else { _throwMockArgumentError('verifyNoMoreInteractions', mock); @@ -1123,8 +1121,8 @@ void verifyNoMoreInteractions(var mock) { void verifyZeroInteractions(var mock) { if (mock is Mock) { if (mock._realCalls.isNotEmpty) { - fail('No interaction expected, but following found: ' + - mock._realCalls.join()); + fail( + 'No interaction expected, but following found: ${mock._realCalls.join()}'); } } else { _throwMockArgumentError('verifyZeroInteractions', mock); @@ -1189,9 +1187,9 @@ void logInvocations(List mocks) { var allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); - allInvocations.forEach((inv) { + for (var inv in allInvocations) { print(inv.toString()); - }); + } } /// Reset the state of Mockito, typically for use between tests. @@ -1227,10 +1225,8 @@ extension on Invocation { if (args.any((arg) => arg.contains('\n'))) { // As one or more arg contains newlines, put each on its own line, and // indent each, for better readability. - argString = '\n' + - args - .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')) - .join(',\n'); + argString = + '\n${args.map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')).join(',\n')}'; } else { // A compact String should be perfect. argString = args.join(', '); @@ -1255,7 +1251,7 @@ extension on Invocation { if (isMethod) { method = '$method($argString)'; } else if (isGetter) { - method = '$method'; + method = method; } else if (isSetter) { method = '$method=$argString'; } else { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c1a568015..a503d9635 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,7 +6,7 @@ description: >- repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.17.0-0 <3.0.0' + sdk: '>=2.18.0 <3.0.0' dependencies: analyzer: '>=5.2.0 <6.0.0' @@ -23,8 +23,8 @@ dependencies: dev_dependencies: build_runner: ^2.0.0 build_test: ^2.0.0 - build_web_compilers: ^3.0.0 + build_web_compilers: '>=3.0.0 <5.0.0' http: ^0.13.0 + lints: ^2.0.0 package_config: '>=1.9.3 <3.0.0' - pedantic: ^1.10.0 test: ^1.16.0 From 0ed0ddf7d446940b64a9d4633943aeffd3769b89 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 8 Feb 2023 05:34:36 -0500 Subject: [PATCH 495/607] Fix violations of `unnecessary_parenthesis` lint https://dart-lang.github.io/linter/lints/unnecessary_parenthesis.html Remove parentheses where they are not necessary. Discussion: https://groups.google.com/a/google.com/g/dart-lints/c/8l7liE-khQk/m/z8qKlIQYBAAJ go/dart-trivial-lint-cleanup-lsc Tested: TAP train for global presubmit queue http://test/OCL:507704828:BASE:507730771:1675786959002:dcee7be6 PiperOrigin-RevId: 508021974 --- pkgs/mockito/lib/src/builder.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 422be12de..d9cc579d7 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -2252,11 +2252,11 @@ extension on int { String get ordinal { final remainder = this % 10; switch (remainder) { - case (1): + case 1: return '${this}st'; - case (2): + case 2: return '${this}nd'; - case (3): + case 3: return '${this}rd'; default: return '${this}th'; From 6ada44e28b1f3726aceebaa0ea342c02cda0928e Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 10 Feb 2023 10:12:51 -0500 Subject: [PATCH 496/607] Add `ignore_for_file: use_of_void_result` Mockito has to override methods, and if a method has a `void` argument, it is passed down to `noSuchMethod`, triggering `use_of_void_result` diagnostic. This is fine and intended, so disable it for the generated files. PiperOrigin-RevId: 508652458 --- pkgs/mockito/lib/src/builder.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index d9cc579d7..33d36c674 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -110,7 +110,9 @@ class MockBuilder implements Builder { // The generator appends a suffix to fake classes b.body.add(Code('// ignore_for_file: camel_case_types\n')); // The generator has to occasionally implement sealed classes - b.body.add(Code('// ignore_for_file: subtype_of_sealed_class\n\n')); + b.body.add(Code('// ignore_for_file: subtype_of_sealed_class\n')); + // The generator has to use `void`-typed arguments. + b.body.add(Code('// ignore_for_file: use_of_void_result\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); From 59bcd68334d6b3b56dd26b9ae0cbbb00ac048839 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 13 Feb 2023 11:51:57 -0500 Subject: [PATCH 497/607] Change `void` to `dynamic` when overriding method arguments We pass the arguments further to `noSuchMethod`, so to make the compiler happy we need to override the type. Also reverts the previous attempt to fix it by suppressing analyzer diagnostic. PiperOrigin-RevId: 509240249 --- pkgs/mockito/lib/src/builder.dart | 16 +++++++++------- pkgs/mockito/test/builder/auto_mocks_test.dart | 18 ++++++++++++++++++ .../test/builder/custom_mocks_test.dart | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 33d36c674..71bbeb521 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -110,9 +110,7 @@ class MockBuilder implements Builder { // The generator appends a suffix to fake classes b.body.add(Code('// ignore_for_file: camel_case_types\n')); // The generator has to occasionally implement sealed classes - b.body.add(Code('// ignore_for_file: subtype_of_sealed_class\n')); - // The generator has to use `void`-typed arguments. - b.body.add(Code('// ignore_for_file: use_of_void_result\n\n')); + b.body.add(Code('// ignore_for_file: subtype_of_sealed_class\n\n')); b.body.addAll(mockLibraryInfo.fakeClasses); b.body.addAll(mockLibraryInfo.mockClasses); }); @@ -1676,8 +1674,8 @@ class _MockClassInfo { return Parameter((pBuilder) { pBuilder.name = name; if (!superParameterType.containsPrivateName) { - pBuilder.type = - _typeReference(superParameterType, forceNullable: forceNullable); + pBuilder.type = _typeReference(superParameterType, + forceNullable: forceNullable, overrideVoid: true); } if (parameter.isNamed) pBuilder.named = true; if (parameter.isRequiredNamed && sourceLibIsNonNullable) { @@ -1935,7 +1933,8 @@ class _MockClassInfo { builder.requiredParameters.add(Parameter((pBuilder) { pBuilder.name = parameter.displayName; if (!parameter.type.containsPrivateName) { - pBuilder.type = _typeReference(parameter.type, forceNullable: true); + pBuilder.type = _typeReference(parameter.type, + forceNullable: true, overrideVoid: true); } })); @@ -1997,7 +1996,10 @@ class _MockClassInfo { // TODO(srawlins): Contribute this back to a common location, like // package:source_gen? Reference _typeReference(analyzer.DartType type, - {bool forceNullable = false}) { + {bool forceNullable = false, bool overrideVoid = false}) { + if (overrideVoid && type.isVoid) { + return TypeReference((b) => b..symbol = 'dynamic'); + } if (type is analyzer.InterfaceType) { return TypeReference((b) { b diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index fcfcca027..a6bc6907b 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3542,6 +3542,24 @@ void main() { ); }); }); + test('Void in argument type gets overriden to dynamic', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + void m(void x) {} + } + '''), + 'foo|test/foo_test.dart': dedent(r''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Foo]) + void main() {} + '''), + }); + expect(mocksContent, contains('void m(dynamic x)')); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 4db4961e1..71dc963fe 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1670,6 +1670,24 @@ void main() { ); }); }); + test('Void in argument type coming from type arg becomes dynamic', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo { + T m(T x) => x; + } + '''), + 'foo|test/foo_test.dart': dedent(r''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([], customMocks: [MockSpec>()]) + void main() {} + '''), + }); + expect(mocksContent, contains('void m(dynamic x)')); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From 091302908c21d4b6dc440ab857225e011a6ddbbf Mon Sep 17 00:00:00 2001 From: Ross Wang Date: Mon, 9 Jan 2023 14:52:42 -0800 Subject: [PATCH 498/607] Relax mixin criteria Remove the restriction that mixins must implement the mocked type so that [MockPlatformInterfaceMixin](https://pub.dev/documentation/plugin_platform_interface/latest/plugin_platform_interface/MockPlatformInterfaceMixin-class.html) can be used. Fixes dart-lang/mockito#603 --- pkgs/mockito/lib/src/builder.dart | 5 --- .../test/builder/custom_mocks_test.dart | 44 ++++++++++--------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 71bbeb521..7ee303f21 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -618,11 +618,6 @@ class _MockTargetGatherer { } final mixinInterfaceType = _determineDartType(typeToMixin, entryLib.typeProvider); - if (!mixinInterfaceType.interfaces.contains(type)) { - throw InvalidMockitoAnnotationException('The "mixingIn" type, ' - '${typeToMixin.getDisplayString(withNullability: false)}, must ' - 'implement the class to mock, ${typeToMock.getDisplayString(withNullability: false)}'); - } mixins.add(mixinInterfaceType); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 71dc963fe..81999230d 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -415,6 +415,29 @@ void main() { ); }); + test('generates a mock class with a marker mixin', () async { + var mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': ''' + class Foo {} + class FooMarkerMixin {} + ''', + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([], customMocks: [ + MockSpec(mixingIn: [FooMarkerMixin]) + ]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'class MockFoo extends _i1.Mock with _i2.FooMarkerMixin implements _i2.Foo {'), + ); + }); + test( 'generates a mock class which uses the old behavior of returning null on ' 'missing stubs', () async { @@ -1215,27 +1238,6 @@ void main() { ); }); - test('throws when MockSpec mixes in a non-mixinable type', () async { - _expectBuilderThrows( - assets: { - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(''' - class Foo {} - '''), - 'foo|test/foo_test.dart': dedent(''' - import 'package:mockito/annotations.dart'; - import 'package:foo/foo.dart'; - @GenerateMocks([], customMocks: [MockSpec(mixingIn: [FooMixin])]) - void main() {} - - mixin FooMixin {} - '''), - }, - message: contains( - 'The "mixingIn" type, FooMixin, must implement the class to mock, Foo'), - ); - }); - test('throws when type argument is unknown type', () async { _expectBuilderThrows( assets: { From 2e8b27a636ffcf8e921d63c3894e6ccb146032cf Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 23 Mar 2023 17:52:50 -0700 Subject: [PATCH 499/607] GitHub Sync (dart-lang/mockito#614) * Import https://github.com/dart-lang/mockito/pull/605 Latest build_web_compilers, move to pkg:lints, fix breaks PiperOrigin-RevId: 511423456 * Import 091302908c21d4b6dc440ab857225e011a6ddbbf Relax mixin criteria Remove the restriction that mixins must implement the mocked type so that [MockPlatformInterfaceMixin](https://pub.dev/documentation/plugin_platform_interface/latest/plugin_platform_interface/MockPlatformInterfaceMixin-class.html) can be used. Fixes dart-lang/mockito#603 PiperOrigin-RevId: 511486389 * Tidy up some fixes which were automatically applied and made lines longer than 80 chars. These are easier to read when wrapped consistently like all other strings in the code. PiperOrigin-RevId: 511490379 * Add a default generate_for Closes dart-lang/mockito#502 Most users only need mocks for test files. The mockito generator performs resolution, so running the generator across all files in `lib/` that are unlikley to use mocks is not worthwhile. Users who do want mocks generated for files in `lib/` will need to manually add a `generate_for` configuration. PiperOrigin-RevId: 512017662 * Prevent '?' on 'Null' type. Generated code: - before: "void trigger(Null? event)" - after: "void trigger(Null event)" PiperOrigin-RevId: 512927949 --------- Co-authored-by: srawlins Co-authored-by: Googler --- pkgs/mockito/CHANGELOG.md | 9 ++++++--- pkgs/mockito/analysis_options.yaml | 3 ++- pkgs/mockito/build.yaml | 2 ++ pkgs/mockito/lib/src/builder.dart | 5 +++-- pkgs/mockito/lib/src/mock.dart | 18 ++++++++++-------- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index eb338ee0f..a08f8a6ba 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,10 +1,13 @@ -## 5.3.3-dev +## 5.4.0-dev -* Require analyzer 5.2.0. * Fix nice mocks generation in mixed mode (generated code is pre null-safety, while mocked class is null-safe). -* Require Dart >= 2.18.0. * Support typedef-aliased classes in `@GenerateMocks` and `@GenerateNiceMocks` +* **Breaking** Default `generate_for` to `test/**`. Projects that are generating + mocks for libraries outside of the `test/` directory should add an explicit + `generate_for` argument to include files with mock generating annotations. +* Require Dart >= 2.17.0. +* Require analyzer 5.2.0. ## 5.3.2 diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 860c620e8..0cfb93b34 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -1,4 +1,5 @@ include: package:lints/recommended.yaml + analyzer: language: strict-casts: true @@ -7,4 +8,4 @@ linter: rules: - comment_references - test_types_in_equals - - throw_in_finally + - throw_in_finally \ No newline at end of file diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index baa5dc23e..0b4fb40d9 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -13,3 +13,5 @@ builders: build_extensions: {".dart": [".mocks.dart"]} build_to: source auto_apply: dependents + defaults: + generate_for: ['test/**'] diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7ee303f21..1ad3ea8ab 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1999,8 +1999,9 @@ class _MockClassInfo { return TypeReference((b) { b ..symbol = type.element.name - ..isNullable = forceNullable || - type.nullabilitySuffix == NullabilitySuffix.question + ..isNullable = !type.isDartCoreNull && + (forceNullable || + type.nullabilitySuffix == NullabilitySuffix.question) ..url = _typeImport(type.element) ..types.addAll(type.typeArguments.map(_typeReference)); }); diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 55d68ca24..21c2d5d15 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -338,7 +338,8 @@ class _InvocationForMatchedArguments extends Invocation { factory _InvocationForMatchedArguments(Invocation invocation) { if (_storedArgs.isEmpty && _storedNamedArgs.isEmpty) { throw StateError( - '_InvocationForMatchedArguments called when no ArgMatchers have been saved.'); + '_InvocationForMatchedArguments called when no ArgMatchers have been ' + 'saved.'); } // Handle named arguments first, so that we can provide useful errors for @@ -854,9 +855,9 @@ Null _registerMatcher(Matcher matcher, bool capture, _storedNamedArgs.clear(); throw ArgumentError( 'The "$argumentMatcher" argument matcher is used outside of method ' - 'stubbing (via `when`) or verification (via `verify` or `untilCalled`). ' - 'This is invalid, and results in bad behavior during the next stubbing ' - 'or verification.'); + 'stubbing (via `when`) or verification (via `verify` or ' + '`untilCalled`). This is invalid, and results in bad behavior during ' + 'the next stubbing or verification.'); } var argMatcher = ArgMatcher(matcher, capture); if (named == null) { @@ -1121,8 +1122,8 @@ void verifyNoMoreInteractions(var mock) { void verifyZeroInteractions(var mock) { if (mock is Mock) { if (mock._realCalls.isNotEmpty) { - fail( - 'No interaction expected, but following found: ${mock._realCalls.join()}'); + fail('No interaction expected, but following found: ' + '${mock._realCalls.join()}'); } } else { _throwMockArgumentError('verifyZeroInteractions', mock); @@ -1225,8 +1226,9 @@ extension on Invocation { if (args.any((arg) => arg.contains('\n'))) { // As one or more arg contains newlines, put each on its own line, and // indent each, for better readability. - argString = - '\n${args.map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')).join(',\n')}'; + var indentedArgs = args + .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')); + argString = '\n${indentedArgs.join(',\n')}'; } else { // A compact String should be perfect. argString = args.join(', '); diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 453ec3a49..8e2c41a6d 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.3.3-dev'; +const packageVersion = '5.4.0-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index a503d9635..b9857953b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.3.3-dev +version: 5.4.0-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From 8f7bc0ca1267923509411e135f1f4a1393349f90 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 23 Mar 2023 19:44:18 -0700 Subject: [PATCH 500/607] Expand pub constraint on test_api (dart-lang/mockito#615) This package is not impacted by the breaking changes, and they are already running together internally. Prepare to publish. This is necessary to unblock the package roll for the flutter repository. https://github.com/flutter/flutter/pull/123350 PiperOrigin-RevId: 519012847 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a08f8a6ba..53456eaf4 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.0-dev +## 5.4.0 * Fix nice mocks generation in mixed mode (generated code is pre null-safety, while mocked class is null-safe). diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 8e2c41a6d..3dfd2c8ed 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.0-dev'; +const packageVersion = '5.4.0'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index b9857953b..060205fb2 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.0-dev +version: 5.4.0 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -18,7 +18,7 @@ dependencies: meta: ^1.3.0 path: ^1.8.0 source_gen: '>=0.9.6 <2.0.0' - test_api: '>=0.2.1 <0.5.0' + test_api: '>=0.2.1 <0.6.0' dev_dependencies: build_runner: ^2.0.0 From 19bef79f4862e2bd845b03768182760055ad3900 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Apr 2023 20:59:36 +0000 Subject: [PATCH 501/607] Bump actions/checkout from 3.3.0 to 3.5.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/ac593985615ec2ede58e132d2e21d2b1cbd6127c...8f4b7f84864484a7bf31766abe9204da3cbe65b3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 05c53f829..5e1fe7212 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: matrix: sdk: [2.18.0, dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} @@ -47,7 +47,7 @@ jobs: os: [ubuntu-latest] sdk: [2.18.0, dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} @@ -72,7 +72,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: dev From 94e8bc3368358dbd2d0cd0a72457e683c5476df7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 00:27:50 +0000 Subject: [PATCH 502/607] Bump dart-lang/setup-dart from 1.4.0 to 1.5.0 Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/a57a6c04cf7d4840e88432aad6281d1e125f0d46...d6a63dab3335f427404425de0fbfed4686d93c4f) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 5e1fe7212..8fab16155 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -24,7 +24,7 @@ jobs: sdk: [2.18.0, dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install @@ -48,7 +48,7 @@ jobs: sdk: [2.18.0, dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install @@ -73,7 +73,7 @@ jobs: os: [ubuntu-latest] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: dev - id: install From f78adfd8316582ca69c70045ec1f9140f381c01e Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 26 Apr 2023 16:29:21 +0200 Subject: [PATCH 503/607] Run CI with Dart 2.19 and dev --- pkgs/mockito/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 8fab16155..a08661069 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [2.18.0, dev] + sdk: [2.19.0, dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f @@ -45,7 +45,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [2.18.0, dev] + sdk: [2.19.0, dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f From d0e0f963a68375d0f03ef7f6b83fc8bf5721a450 Mon Sep 17 00:00:00 2001 From: oprypin Date: Tue, 11 Apr 2023 08:46:36 +0000 Subject: [PATCH 504/607] Fix violations of `prefer_final_locals`, `prefer_final_in_for_each` lints * https://dart-lang.github.io/linter/lints/prefer_final_locals.html * https://dart-lang.github.io/linter/lints/prefer_final_in_for_each.html Replace `var` with `final` where appropriate for local variables. Discussion: https://groups.google.com/a/google.com/g/dart-lints/c/4qVMucGmlJ8 go/dart-trivial-lint-cleanup-lsc Tested: Local presubmit tests passed. PiperOrigin-RevId: 523335328 --- pkgs/mockito/example/example.dart | 6 +- pkgs/mockito/example/iss/iss.dart | 28 ++-- pkgs/mockito/example/iss/iss_test.dart | 28 ++-- pkgs/mockito/lib/src/builder.dart | 78 +++++------ pkgs/mockito/lib/src/mock.dart | 88 ++++++------- .../mockito/test/builder/auto_mocks_test.dart | 122 +++++++++--------- .../test/builder/custom_mocks_test.dart | 82 ++++++------ .../test/end2end/generated_mocks_test.dart | 12 +- .../mockito/test/invocation_matcher_test.dart | 34 ++--- pkgs/mockito/test/mockito_test.dart | 12 +- pkgs/mockito/test/until_called_test.dart | 2 +- pkgs/mockito/test/verify_test.dart | 4 +- 12 files changed, 248 insertions(+), 248 deletions(-) diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 21a4336ca..77cd00bcf 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -80,7 +80,7 @@ void main() { expect(() => cat.lives, throwsRangeError); // We can calculate a response at call time. - var responses = ['Purr', 'Meow']; + final responses = ['Purr', 'Meow']; when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); expect(cat.sound(), 'Purr'); expect(cat.sound(), 'Meow'); @@ -208,7 +208,7 @@ void main() { test('Fake class', () { // Create a new fake Cat at runtime. - var cat = FakeCat(); + final cat = FakeCat(); cat.eatFood('Milk'); // Prints 'Fake eat Milk'. expect(() => cat.sleep(), throwsUnimplementedError); @@ -216,7 +216,7 @@ void main() { test('Relaxed mock class', () { // Create a new mock Cat at runtime. - var cat = MockCatRelaxed(); + final cat = MockCatRelaxed(); // You can call it without stubbing. cat.sleep(); diff --git a/pkgs/mockito/example/iss/iss.dart b/pkgs/mockito/example/iss/iss.dart index abf647dc8..9a8ad9f69 100644 --- a/pkgs/mockito/example/iss/iss.dart +++ b/pkgs/mockito/example/iss/iss.dart @@ -39,11 +39,11 @@ class IssLocator { Future _doUpdate() async { // Returns the point on the earth directly under the space station // at this moment. - var uri = Uri.parse('http://api.open-notify.org/iss-now.json'); - var rs = await client.get(uri); - var data = jsonDecode(rs.body); - var latitude = double.parse(data['iss_position']['latitude'] as String); - var longitude = double.parse(data['iss_position']['longitude'] as String); + final uri = Uri.parse('http://api.open-notify.org/iss-now.json'); + final rs = await client.get(uri); + final data = jsonDecode(rs.body); + final latitude = double.parse(data['iss_position']['latitude'] as String); + final longitude = double.parse(data['iss_position']['longitude'] as String); _position = Point(latitude, longitude); } } @@ -58,7 +58,7 @@ class IssSpotter { // The ISS is defined to be visible if the distance from the observer to // the point on the earth directly under the space station is less than 80km. bool get isVisible { - var distance = sphericalDistanceKm(locator.currentPosition, observer); + final distance = sphericalDistanceKm(locator.currentPosition, observer); return distance < 80.0; } } @@ -66,14 +66,14 @@ class IssSpotter { // Returns the distance, in kilometers, between p1 and p2 along the earth's // curved surface. double sphericalDistanceKm(Point p1, Point p2) { - var dLat = _toRadian(p1.x - p2.x); - var sLat = pow(sin(dLat / 2), 2); - var dLng = _toRadian(p1.y - p2.y); - var sLng = pow(sin(dLng / 2), 2); - var cosALat = cos(_toRadian(p1.x)); - var cosBLat = cos(_toRadian(p2.x)); - var x = sLat + cosALat * cosBLat * sLng; - var d = 2 * atan2(sqrt(x), sqrt(1 - x)) * _radiusOfEarth; + final dLat = _toRadian(p1.x - p2.x); + final sLat = pow(sin(dLat / 2), 2); + final dLng = _toRadian(p1.y - p2.y); + final sLng = pow(sin(dLng / 2), 2); + final cosALat = cos(_toRadian(p1.x)); + final cosBLat = cos(_toRadian(p2.x)); + final x = sLat + cosALat * cosBLat * sLng; + final d = 2 * atan2(sqrt(x), sqrt(1 - x)) * _radiusOfEarth; return d; } diff --git a/pkgs/mockito/example/iss/iss_test.dart b/pkgs/mockito/example/iss/iss_test.dart index 2ec901a0c..1d241218d 100644 --- a/pkgs/mockito/example/iss/iss_test.dart +++ b/pkgs/mockito/example/iss/iss_test.dart @@ -27,18 +27,18 @@ void main() { // verify the calculated distance between them. group('Spherical distance', () { test('London - Paris', () { - var london = Point(51.5073, -0.1277); - var paris = Point(48.8566, 2.3522); - var d = sphericalDistanceKm(london, paris); + final london = Point(51.5073, -0.1277); + final paris = Point(48.8566, 2.3522); + final d = sphericalDistanceKm(london, paris); // London should be approximately 343.5km // (+/- 0.1km) from Paris. expect(d, closeTo(343.5, 0.1)); }); test('San Francisco - Mountain View', () { - var sf = Point(37.783333, -122.416667); - var mtv = Point(37.389444, -122.081944); - var d = sphericalDistanceKm(sf, mtv); + final sf = Point(37.783333, -122.416667); + final mtv = Point(37.389444, -122.081944); + final d = sphericalDistanceKm(sf, mtv); // San Francisco should be approximately 52.8km // (+/- 0.1km) from Mountain View. expect(d, closeTo(52.8, 0.1)); @@ -52,24 +52,24 @@ void main() { // second predefined location. This test runs asynchronously. group('ISS spotter', () { test('ISS visible', () async { - var sf = Point(37.783333, -122.416667); - var mtv = Point(37.389444, -122.081944); - IssLocator locator = MockIssLocator(); + final sf = Point(37.783333, -122.416667); + final mtv = Point(37.389444, -122.081944); + final IssLocator locator = MockIssLocator(); // Mountain View should be visible from San Francisco. when(locator.currentPosition).thenReturn(sf); - var spotter = IssSpotter(locator, mtv); + final spotter = IssSpotter(locator, mtv); expect(spotter.isVisible, true); }); test('ISS not visible', () async { - var london = Point(51.5073, -0.1277); - var mtv = Point(37.389444, -122.081944); - IssLocator locator = MockIssLocator(); + final london = Point(51.5073, -0.1277); + final mtv = Point(37.389444, -122.081944); + final IssLocator locator = MockIssLocator(); // London should not be visible from Mountain View. when(locator.currentPosition).thenReturn(london); - var spotter = IssSpotter(locator, mtv); + final spotter = IssSpotter(locator, mtv); expect(spotter.isVisible, false); }); }); diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 1ad3ea8ab..12782f3ba 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -72,7 +72,7 @@ class MockBuilder implements Builder { final mockTargetGatherer = _MockTargetGatherer(entryLib, inheritanceManager); - var entryAssetId = await buildStep.resolver.assetIdForElement(entryLib); + final entryAssetId = await buildStep.resolver.assetIdForElement(entryLib); final assetUris = await _resolveAssetUris(buildStep.resolver, mockTargetGatherer._mockTargets, entryAssetId.path, entryLib); @@ -340,13 +340,13 @@ class _TypeVisitor extends RecursiveElementVisitor { // [RecursiveElementVisitor] does not "step out of" the element model, // into types, while traversing, so we must explicitly traverse [type] // here, in order to visit contained elements. - for (var typeParameter in type.typeFormals) { + for (final typeParameter in type.typeFormals) { typeParameter.accept(this); } - for (var parameter in type.parameters) { + for (final parameter in type.parameters) { parameter.accept(this); } - var aliasElement = type.alias?.element; + final aliasElement = type.alias?.element; if (aliasElement != null) { _elements.add(aliasElement); } @@ -364,15 +364,15 @@ class _TypeVisitor extends RecursiveElementVisitor { // No types to add from a literal. return; } else if (constant.isList) { - for (var element in constant.listValue) { + for (final element in constant.listValue) { _addTypesFromConstant(element); } } else if (constant.isSet) { - for (var element in constant.setValue) { + for (final element in constant.setValue) { _addTypesFromConstant(element); } } else if (constant.isMap) { - for (var pair in constant.mapValue.entries) { + for (final pair in constant.mapValue.entries) { _addTypesFromConstant(pair.key!); _addTypesFromConstant(pair.value!); } @@ -381,11 +381,11 @@ class _TypeVisitor extends RecursiveElementVisitor { } else { // If [constant] is not null, a literal, or a type, then it must be an // object constructed with `const`. Revive it. - var revivable = constant.revive(); - for (var argument in revivable.positionalArguments) { + final revivable = constant.revive(); + for (final argument in revivable.positionalArguments) { _addTypesFromConstant(argument); } - for (var pair in revivable.namedArguments.entries) { + for (final pair in revivable.namedArguments.entries) { _addTypesFromConstant(pair.value); } _addType(object.type); @@ -507,7 +507,7 @@ class _MockTargetGatherer { 'unknown type, or includes an extension'); } final mockTargets = <_MockTarget>[]; - for (var objectToMock in classesField.toListValue()!) { + for (final objectToMock in classesField.toListValue()!) { final typeToMock = objectToMock.toTypeValue(); if (typeToMock == null) { throw InvalidMockitoAnnotationException( @@ -739,10 +739,10 @@ class _MockTargetGatherer { 'Mockito cannot mock a private type: ' '${elementToMock.displayName}.'); } - var typeParameterErrors = + final typeParameterErrors = _checkTypeParameters(elementToMock.typeParameters, elementToMock); if (typeParameterErrors.isNotEmpty) { - var joinedMessages = + final joinedMessages = typeParameterErrors.map((m) => ' $m').join('\n'); throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid mock class which implements ' @@ -785,7 +785,7 @@ class _MockTargetGatherer { } classNamesToMock.forEach((name, mockTarget) { - var conflictingClass = + final conflictingClass = classesInEntryLib.firstWhereOrNull((c) => c.name == name); if (conflictingClass != null) { throw InvalidMockitoAnnotationException( @@ -794,7 +794,7 @@ class _MockTargetGatherer { '$uniqueNameSuggestion.'); } - var preexistingMock = classesInEntryLib.firstWhereOrNull((c) => + final preexistingMock = classesInEntryLib.firstWhereOrNull((c) => c.interfaces .map((type) => type.element) .contains(mockTarget.interfaceElement) && @@ -917,10 +917,10 @@ class _MockTargetGatherer { } } - for (var parameter in function.parameters) { - var parameterType = parameter.type; + for (final parameter in function.parameters) { + final parameterType = parameter.type; if (parameterType is analyzer.InterfaceType) { - var parameterTypeElement = parameterType.element; + final parameterTypeElement = parameterType.element; if (parameterTypeElement.isPrivate) { // Technically, we can expand the type in the mock to something like // `Object?`. However, until there is a decent use case, we will not @@ -947,7 +947,7 @@ class _MockTargetGatherer { errorMessages .addAll(_checkTypeParameters(function.typeFormals, enclosingElement)); - var aliasArguments = function.alias?.typeArguments; + final aliasArguments = function.alias?.typeArguments; if (aliasArguments != null) { errorMessages.addAll(_checkTypeArguments(aliasArguments, enclosingElement, isParameter: isParameter)); @@ -960,9 +960,9 @@ class _MockTargetGatherer { /// enclosing method un-stubbable. static List _checkTypeParameters( List typeParameters, Element enclosingElement) { - var errorMessages = []; - for (var element in typeParameters) { - var typeParameter = element.bound; + final errorMessages = []; + for (final element in typeParameters) { + final typeParameter = element.bound; if (typeParameter == null) continue; if (typeParameter is analyzer.InterfaceType) { // TODO(srawlins): Check for private names in bound; could be @@ -988,8 +988,8 @@ class _MockTargetGatherer { bool isParameter = false, bool allowUnsupportedMember = false, }) { - var errorMessages = []; - for (var typeArgument in typeArguments) { + final errorMessages = []; + for (final typeArgument in typeArguments) { if (typeArgument is analyzer.InterfaceType) { if (typeArgument.element.isPrivate && !allowUnsupportedMember) { errorMessages.add( @@ -1143,7 +1143,7 @@ class _MockClassInfo { // example: `Foo`). Generate a non-generic mock class which // implements the mock target with said type arguments. For example: // `class MockFoo extends Mock implements Foo {}` - for (var typeArgument in typeArguments) { + for (final typeArgument in typeArguments) { typeReferences.add(_typeReference(typeArgument)); } } else { @@ -1151,7 +1151,7 @@ class _MockClassInfo { // `Foo`, a reference to `class Foo {}`). Generate a generic mock // class which perfectly mirrors the type parameters on [typeToMock], // forwarding them to the "implements" clause. - for (var typeParameter in typeParameters) { + for (final typeParameter in typeParameters) { cBuilder.types.add(_typeParameterReference(typeParameter)); typeReferences.add(refer(typeParameter.name)); } @@ -1204,7 +1204,7 @@ class _MockClassInfo { // members for the purpose of expanding their parameter types. However, // we may need to include an implementation of `toString()`, if the // class-to-mock has added optional parameters. - var toStringMethod = members + final toStringMethod = members .whereType() .firstWhereOrNull((m) => m.name == 'toString'); if (toStringMethod != null) { @@ -1466,7 +1466,7 @@ class _MockClassInfo { return literalNull; } - var typeArguments = type.typeArguments; + final typeArguments = type.typeArguments; if (type.isDartCoreBool) { return literalFalse; } else if (type.isDartCoreDouble) { @@ -1482,7 +1482,7 @@ class _MockClassInfo { // We cannot create a valid Future for this unknown, potentially // non-nullable type, so we'll use a `_FakeFuture`, which will throw // if awaited. - var futureType = typeProvider.futureType(typeArguments.first); + final futureType = typeProvider.futureType(typeArguments.first); return _dummyValueImplementing(futureType, invocation); } else { // Create a real Future with a legal value, via [Future.value]. @@ -1615,7 +1615,7 @@ class _MockClassInfo { cBuilder ..name = fakeName ..extend = referImported('SmartFake', 'package:mockito/mockito.dart'); - for (var typeParameter in elementToFake.typeParameters) { + for (final typeParameter in elementToFake.typeParameters) { cBuilder.types.add(_typeParameterReference(typeParameter)); typeParameters.add(refer(typeParameter.name)); } @@ -1792,13 +1792,13 @@ class _MockClassInfo { } else if (constant.isType) { // TODO(srawlins): It seems like this might be revivable, but Angular // does not revive Types; we should investigate this if users request it. - var type = object.toTypeValue()!; - var typeStr = type.getDisplayString(withNullability: false); + final type = object.toTypeValue()!; + final typeStr = type.getDisplayString(withNullability: false); throw _ReviveException('default value is a Type: $typeStr.'); } else { // If [constant] is not null, a literal, or a type, then it must be an // object constructed with `const`. Revive it. - var revivable = constant.revive(); + final revivable = constant.revive(); if (revivable.isPrivate) { final privateReference = revivable.accessor.isNotEmpty ? '${revivable.source}::${revivable.accessor}' @@ -1809,7 +1809,7 @@ class _MockClassInfo { if (object.toFunctionValue() != null) { // A top-level function, like `void f() {}` must be referenced by its // identifier, rather than a revived value. - var element = object.toFunctionValue(); + final element = object.toFunctionValue(); return referImported(revivable.accessor, _typeImport(element)); } else if (revivable.source.fragment.isEmpty) { // We can create this invocation by referring to a const field or @@ -2019,11 +2019,11 @@ class _MockClassInfo { .addAll(type.normalParameterTypes.map(_typeReference)) ..optionalParameters .addAll(type.optionalParameterTypes.map(_typeReference)); - for (var parameter + for (final parameter in type.parameters.where((p) => p.isOptionalNamed)) { b.namedParameters[parameter.name] = _typeReference(parameter.type); } - for (var parameter + for (final parameter in type.parameters.where((p) => p.isRequiredNamed)) { b.namedRequiredParameters[parameter.name] = _typeReference(parameter.type); @@ -2036,7 +2036,7 @@ class _MockClassInfo { ..symbol = alias.element.name ..url = _typeImport(alias.element) ..isNullable = forceNullable || typeSystem.isNullable(type); - for (var typeArgument in alias.typeArguments) { + for (final typeArgument in alias.typeArguments) { b.types.add(_typeReference(typeArgument)); } }); @@ -2165,12 +2165,12 @@ extension on Element { } else if (this is EnumElement) { return "The enum '$name'"; } else if (this is MethodElement) { - var className = enclosingElement!.name; + final className = enclosingElement!.name; return "The method '$className.$name'"; } else if (this is MixinElement) { return "The mixin '$name'"; } else if (this is PropertyAccessorElement) { - var className = enclosingElement!.name; + final className = enclosingElement!.name; return "The property accessor '$className.$name'"; } else { return 'unknown element'; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 21c2d5d15..58a9cdab3 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -179,10 +179,10 @@ class Mock { } _realCalls.add(RealCall(this, invocation)); _invocationStreamController.add(invocation); - var cannedResponse = _responses.lastWhere( + final cannedResponse = _responses.lastWhere( (cr) => cr.call.matches(invocation, {}), orElse: defaultResponse); - var response = cannedResponse.response(invocation); + final response = cannedResponse.response(invocation); return response; } } @@ -202,7 +202,7 @@ class Mock { String toString() => _givenName ?? runtimeType.toString(); String _realCallsToString([Iterable? realCalls]) { - var stringRepresentations = + final stringRepresentations = (realCalls ?? _realCalls).map((call) => call.toString()); if (stringRepresentations.any((s) => s.contains('\n'))) { // As each call contains newlines, put each on its own line, for better @@ -346,8 +346,8 @@ class _InvocationForMatchedArguments extends Invocation { // the various bad states. If all is well with the named arguments, then we // can process the positional arguments, and resort to more general errors // if the state is still bad. - var namedArguments = _reconstituteNamedArgs(invocation); - var positionalArguments = _reconstitutePositionalArgs(invocation); + final namedArguments = _reconstituteNamedArgs(invocation); + final positionalArguments = _reconstitutePositionalArgs(invocation); _storedArgs.clear(); _storedNamedArgs.clear(); @@ -390,7 +390,7 @@ class _InvocationForMatchedArguments extends Invocation { // Iterate through the stored named args, validate them, and add them to // the return map. _storedNamedArgs.forEach((name, arg) { - var nameSymbol = Symbol(name); + final nameSymbol = Symbol(name); if (!invocation.namedArguments.containsKey(nameSymbol)) { // Clear things out for the next call. _storedArgs.clear(); @@ -449,7 +449,7 @@ class _InvocationForMatchedArguments extends Invocation { var positionalIndex = 0; while (storedIndex < _storedArgs.length && positionalIndex < invocation.positionalArguments.length) { - var arg = _storedArgs[storedIndex]; + final arg = _storedArgs[storedIndex]; if (invocation.positionalArguments[positionalIndex] == null) { // Add the [ArgMatcher] given to the argument matching helper. positionalArguments.add(arg); @@ -544,7 +544,7 @@ class InvocationMatcher { InvocationMatcher(this.roleInvocation); bool matches(Invocation invocation) { - var isMatching = + final isMatching = _isMethodMatches(invocation) && _isArgumentsMatches(invocation); if (isMatching) { _captureArguments(invocation); @@ -566,16 +566,16 @@ class InvocationMatcher { void _captureArguments(Invocation invocation) { var index = 0; - for (var roleArg in roleInvocation.positionalArguments) { - var actArg = invocation.positionalArguments[index]; + for (final roleArg in roleInvocation.positionalArguments) { + final actArg = invocation.positionalArguments[index]; if (roleArg is ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } index++; } - for (var roleKey in roleInvocation.namedArguments.keys) { - var roleArg = roleInvocation.namedArguments[roleKey]; - var actArg = invocation.namedArguments[roleKey]; + for (final roleKey in roleInvocation.namedArguments.keys) { + final roleArg = roleInvocation.namedArguments[roleKey]; + final actArg = invocation.namedArguments[roleKey]; if (roleArg is ArgMatcher && roleArg._capture) { _capturedArgs.add(actArg); } @@ -592,22 +592,22 @@ class InvocationMatcher { return false; } var index = 0; - for (var roleArg in roleInvocation.positionalArguments) { - var actArg = invocation.positionalArguments[index]; + for (final roleArg in roleInvocation.positionalArguments) { + final actArg = invocation.positionalArguments[index]; if (!isMatchingArg(roleArg, actArg)) { return false; } index++; } - Set roleKeys = roleInvocation.namedArguments.keys.toSet(); - Set actKeys = invocation.namedArguments.keys.toSet(); + final Set roleKeys = roleInvocation.namedArguments.keys.toSet(); + final Set actKeys = invocation.namedArguments.keys.toSet(); if (roleKeys.difference(actKeys).isNotEmpty || actKeys.difference(roleKeys).isNotEmpty) { return false; } - for (var roleKey in roleInvocation.namedArguments.keys) { - var roleArg = roleInvocation.namedArguments[roleKey]; - var actArg = invocation.namedArguments[roleKey]; + for (final roleKey in roleInvocation.namedArguments.keys) { + final roleArg = roleInvocation.namedArguments[roleKey]; + final actArg = invocation.namedArguments[roleKey]; if (!isMatchingArg(roleArg, actArg)) { return false; } @@ -647,7 +647,7 @@ class RealCall { @override String toString() { - var verifiedText = verified ? '[VERIFIED] ' : ''; + final verifiedText = verified ? '[VERIFIED] ' : ''; return '$verifiedText$mock.${invocation.toPrettyString()}'; } } @@ -703,9 +703,9 @@ class _VerifyCall { final List matchingCapturedArgs; factory _VerifyCall(Mock mock, Invocation verifyInvocation) { - var expectedMatcher = InvocationMatcher(verifyInvocation); - var matchingInvocations = <_RealCallWithCapturedArgs>[]; - for (var realCall in mock._realCalls) { + final expectedMatcher = InvocationMatcher(verifyInvocation); + final matchingInvocations = <_RealCallWithCapturedArgs>[]; + for (final realCall in mock._realCalls) { if (!realCall.verified && expectedMatcher.matches(realCall.invocation)) { // [Invocation.matcher] collects captured arguments if // [verifyInvocation] included capturing matchers. @@ -715,7 +715,7 @@ class _VerifyCall { } } - var matchingCapturedArgs = [ + final matchingCapturedArgs = [ for (var invocation in matchingInvocations) ...invocation.capturedArgs, ]; @@ -738,7 +738,7 @@ class _VerifyCall { if (mock._realCalls.isEmpty) { message = 'No matching calls (actually, no calls at all).'; } else { - var otherCalls = mock._realCallsToString(); + final otherCalls = mock._realCallsToString(); message = 'No matching calls. All calls: $otherCalls'; } fail('$message\n' @@ -746,10 +746,10 @@ class _VerifyCall { '`verifyNever(...);`.)'); } if (never && matchingInvocations.isNotEmpty) { - var calls = mock._unverifiedCallsToString(); + final calls = mock._unverifiedCallsToString(); fail('Unexpected calls: $calls'); } - for (var invocation in matchingInvocations) { + for (final invocation in matchingInvocations) { invocation.realCall.verified = true; } } @@ -859,7 +859,7 @@ Null _registerMatcher(Matcher matcher, bool capture, '`untilCalled`). This is invalid, and results in bad behavior during ' 'the next stubbing or verification.'); } - var argMatcher = ArgMatcher(matcher, capture); + final argMatcher = ArgMatcher(matcher, capture); if (named == null) { _storedArgs.add(argMatcher); } else { @@ -1012,8 +1012,8 @@ Verification _makeVerify(bool never) { return (T mock) { _verificationInProgress = false; if (_verifyCalls.length == 1) { - var verifyCall = _verifyCalls.removeLast(); - var result = VerificationResult._(verifyCall.matchingInvocations.length, + final verifyCall = _verifyCalls.removeLast(); + final result = VerificationResult._(verifyCall.matchingInvocations.length, verifyCall.matchingCapturedArgs); verifyCall._checkWith(never); return result; @@ -1069,20 +1069,20 @@ List Function(List recordedInvocations) 'were stored: $_verifyCalls'); } _verificationInProgress = false; - var verificationResults = []; + final verificationResults = []; var time = DateTime.fromMillisecondsSinceEpoch(0); - var tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); + final tmpVerifyCalls = List<_VerifyCall>.from(_verifyCalls); _verifyCalls.clear(); - var matchedCalls = []; - for (var verifyCall in tmpVerifyCalls) { + final matchedCalls = []; + for (final verifyCall in tmpVerifyCalls) { try { - var matched = verifyCall._findAfter(time); + final matched = verifyCall._findAfter(time); matchedCalls.add(matched.realCall); verificationResults.add(VerificationResult._(1, matched.capturedArgs)); time = matched.realCall.timeStamp; } on StateError { - var mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); - var allInvocations = + final mocks = tmpVerifyCalls.map((vc) => vc.mock).toSet(); + final allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations .sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); @@ -1094,7 +1094,7 @@ List Function(List recordedInvocations) 'not found.$otherCalls'); } } - for (var call in matchedCalls) { + for (final call in matchedCalls) { call.verified = true; } return verificationResults; @@ -1110,7 +1110,7 @@ void _throwMockArgumentError(String method, var nonMockInstance) { void verifyNoMoreInteractions(var mock) { if (mock is Mock) { - var unverified = mock._realCalls.where((inv) => !inv.verified).toList(); + final unverified = mock._realCalls.where((inv) => !inv.verified).toList(); if (unverified.isNotEmpty) { fail('No more calls expected, but following found: ${unverified.join()}'); } @@ -1185,10 +1185,10 @@ InvocationLoader get untilCalled { /// Print all collected invocations of any mock methods of [mocks]. void logInvocations(List mocks) { - var allInvocations = + final allInvocations = mocks.expand((m) => m._realCalls).toList(growable: false); allInvocations.sort((inv1, inv2) => inv1.timeStamp.compareTo(inv2.timeStamp)); - for (var inv in allInvocations) { + for (final inv in allInvocations) { print(inv.toString()); } } @@ -1222,11 +1222,11 @@ extension on Invocation { String argString; // Add quotes around strings to clarify the type of the argument to the user // and so the empty string is represented. - var args = positionalArguments.map((v) => v is String ? "'$v'" : '$v'); + final args = positionalArguments.map((v) => v is String ? "'$v'" : '$v'); if (args.any((arg) => arg.contains('\n'))) { // As one or more arg contains newlines, put each on its own line, and // indent each, for better readability. - var indentedArgs = args + final indentedArgs = args .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m')); argString = '\n${indentedArgs.join(',\n')}'; } else { diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index a6bc6907b..c2e9211d0 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -87,7 +87,7 @@ void main() { /// Test [MockBuilder] in a package which has not opted into null safety. Future testPreNonNullable(Map sourceAssets, {Map*/ Object>? outputs}) async { - var packageConfig = PackageConfig([ + final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 7)) @@ -99,7 +99,7 @@ void main() { /// Test [MockBuilder] in a package which has opted into null safety. Future testWithNonNullable(Map sourceAssets, {Map>*/ Object>? outputs}) async { - var packageConfig = PackageConfig([ + final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 13)) @@ -115,7 +115,7 @@ void main() { /// Builds with [MockBuilder] in a package which has opted into null safety, /// returning the content of the generated mocks library. Future buildWithNonNullable(Map sourceAssets) async { - var packageConfig = PackageConfig([ + final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 13)) @@ -127,7 +127,7 @@ void main() { writer: writer, packageConfig: packageConfig), ['nonfunction-type-aliases'], ); - var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); + final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]!); } @@ -155,7 +155,7 @@ void main() { ...simpleTestAsset, 'foo|lib/foo.dart': sourceAssetText, }); - var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); + final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]!); } @@ -166,7 +166,7 @@ void main() { test( 'generates a mock class but does not override methods w/ zero parameters', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { dynamic method1() => 7; } @@ -178,7 +178,7 @@ void main() { test('generates a mock class but does not override private methods', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int _method1(int x) => 8; } @@ -189,7 +189,7 @@ void main() { }); test('generates a mock class but does not override static methods', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { static int method1(int y) => 9; } @@ -199,7 +199,7 @@ void main() { test('generates a mock class but does not override any extension methods', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' extension X on Foo { dynamic x(int m, String n) => n + 1; } @@ -791,7 +791,7 @@ void main() { test( 'does not override `toString` if the class does not override `toString` ' 'with additional parameters', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' abstract class Foo { String toString() => 'Foo'; } @@ -818,7 +818,7 @@ void main() { test('does not override `operator==`, even if the class overrides it', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' class Foo { bool operator==(Object? other); } @@ -828,7 +828,7 @@ void main() { test('does not override `hashCode`, even if the class overrides it', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' class Foo { final int hashCode = 7; } @@ -837,7 +837,7 @@ void main() { }); test('generates mock classes from part files', () async { - var mocksOutput = await buildWithNonNullable({ + final mocksOutput = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -871,7 +871,7 @@ void main() { test('generates mock classes from an annotation on an import directive', () async { - var mocksOutput = await buildWithNonNullable({ + final mocksOutput = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r'class Foo {} class Bar {}'), 'foo|test/foo_test.dart': ''' @@ -886,7 +886,7 @@ void main() { test('generates mock classes from an annotation on an export directive', () async { - var mocksOutput = await buildWithNonNullable({ + final mocksOutput = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -904,7 +904,7 @@ void main() { }); test('generates multiple mock classes', () async { - var mocksOutput = await buildWithNonNullable({ + final mocksOutput = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -924,7 +924,7 @@ void main() { }); test('generates mock classes from multiple annotations', () async { - var mocksOutput = await buildWithNonNullable({ + final mocksOutput = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -947,7 +947,7 @@ void main() { test('generates mock classes from multiple annotations on a single element', () async { - var mocksOutput = await buildWithNonNullable({ + final mocksOutput = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -968,7 +968,7 @@ void main() { }); test('generates generic mock classes', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo {} ''')); expect( @@ -978,7 +978,7 @@ void main() { }); test('generates generic mock classes with type bounds', () async { - var mocksOutput = await buildWithNonNullable({ + final mocksOutput = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -1038,7 +1038,7 @@ void main() { }); test('imports libraries for external class types', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { dynamic f(List list) {} @@ -1051,7 +1051,7 @@ void main() { test('imports libraries for external class types declared in parts', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' part 'foo_part.dart'; @@ -1075,7 +1075,7 @@ void main() { test( 'imports libraries for external class types found in a method return ' 'type', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { Future f() async {} @@ -1087,7 +1087,7 @@ void main() { test('imports libraries for external class types found in a type argument', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { List f() => []; @@ -1100,7 +1100,7 @@ void main() { test( 'imports libraries for external class types found in the return type of ' 'a function-typed parameter', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { void f(Future a()) {} @@ -1113,7 +1113,7 @@ void main() { test( 'imports libraries for external class types found in a parameter type of ' 'a function-typed parameter', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { void f(void a(Future b)) {} @@ -1126,7 +1126,7 @@ void main() { test( 'imports libraries for external class types found in a function-typed ' 'parameter', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { void f(Future a()) {} @@ -1139,7 +1139,7 @@ void main() { test( 'imports libraries for external class types found in a FunctionType ' 'parameter', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { void f(Future Function() a) {} @@ -1152,7 +1152,7 @@ void main() { test( 'imports libraries for external class types found nested in a ' 'function-typed parameter', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { void f(void a(Future b)) {} @@ -1165,7 +1165,7 @@ void main() { test( 'imports libraries for external class types found in the bound of a ' 'type parameter of a method', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; class Foo { void f(T a) {} @@ -1178,7 +1178,7 @@ void main() { test( 'imports libraries for external class types found in the default value ' 'of a parameter', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:convert'; class Foo { void f([Object a = utf8]) {} @@ -1205,8 +1205,8 @@ void main() { } ''', }); - var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); - var mocksContent = utf8.decode(writer.assets[mocksAsset]!); + final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); + final mocksContent = utf8.decode(writer.assets[mocksAsset]!); expect(mocksContent, contains("import 'dart:async' as _i3;")); expect(mocksContent, contains('m(_i3.Future? a)')); }); @@ -1228,14 +1228,14 @@ void main() { } ''', }); - var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); - var mocksContent = utf8.decode(writer.assets[mocksAsset]!); + final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); + final mocksContent = utf8.decode(writer.assets[mocksAsset]!); expect(mocksContent, contains("import 'dart:async' as _i3;")); expect(mocksContent, contains('m(_i3.Future? a)')); }); test('imports libraries for type aliases with external types', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' import 'dart:async'; typedef Callback = void Function(); typedef void Callback2(); @@ -1255,7 +1255,7 @@ void main() { test('imports libraries for types declared in private SDK libraries', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' import 'dart:io'; abstract class Foo { HttpClient f() {} @@ -1268,7 +1268,7 @@ void main() { test( 'imports libraries for types declared in private SDK libraries exported ' 'in dart:io', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' import 'dart:io'; abstract class Foo { HttpStatus f() {} @@ -1281,7 +1281,7 @@ void main() { test( 'imports libraries for types declared in private SDK libraries exported ' 'in dart:html', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' import 'dart:html'; abstract class Foo { HttpStatus f() {} @@ -1747,7 +1747,7 @@ void main() { }); test('does not override methods with all nullable parameters', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? method1(int? p) => null; } @@ -1757,7 +1757,7 @@ void main() { test('does not override methods with all nullable parameters (dynamic)', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? method1(dynamic p) => null; } @@ -1767,7 +1767,7 @@ void main() { test('does not override methods with all nullable parameters (var untyped)', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? method1(var p) => null; } @@ -1777,7 +1777,7 @@ void main() { test('does not override methods with all nullable parameters (final untyped)', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? method1(final p) => null; } @@ -1787,7 +1787,7 @@ void main() { test('does not override methods with all nullable parameters (type variable)', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? method1(T? p) => null; } @@ -1798,7 +1798,7 @@ void main() { test( 'does not override methods with all nullable parameters (function-typed)', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? method1(int Function()? p) => null; } @@ -1808,7 +1808,7 @@ void main() { test('does not override methods with an implicit dynamic return type', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { method1(); } @@ -1818,7 +1818,7 @@ void main() { test('does not override methods with an explicit dynamic return type', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { dynamic method1(); } @@ -1827,7 +1827,7 @@ void main() { }); test('does not override methods with a nullable return type', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { int? method1(); } @@ -1837,7 +1837,7 @@ void main() { test('does not override methods with a nullable return type (type variable)', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' abstract class Foo { T? method1(); } @@ -1865,7 +1865,7 @@ void main() { }); test('overrides inherited methods with a non-nullable return type', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class FooBase { num m() => 7; } @@ -1901,7 +1901,7 @@ void main() { }); test('overrides generic methods', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { dynamic f(int a) {} dynamic g(int a) {} @@ -1928,7 +1928,7 @@ void main() { }); test('does not override nullable instance getters', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? get getter1 => 7; } @@ -1954,7 +1954,7 @@ void main() { }); test('overrides inherited instance getters only once', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' class FooBase { num get m => 7; } @@ -2025,7 +2025,7 @@ void main() { }); test('overrides inherited non-nullable instance setters only once', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' class FooBase { set m(int a) {} } @@ -2087,7 +2087,7 @@ void main() { }); test('overrides inherited non-nullable fields only once', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(''' class FooBase { num m; } @@ -2120,7 +2120,7 @@ void main() { }); test('does not override getters for nullable fields', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int? field1; } @@ -2129,7 +2129,7 @@ void main() { }); test('does not override private fields', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { int _field1; } @@ -2138,7 +2138,7 @@ void main() { }); test('does not override static fields', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { static int field1; } @@ -2714,7 +2714,7 @@ void main() { }); test('deduplicates fake classes', () async { - var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo { Bar m1() => Bar('name1'); Bar m2() => Bar('name2'); @@ -2724,7 +2724,7 @@ void main() { Bar(this.name); } ''')); - var mocksContentLines = mocksContent.split('\n'); + final mocksContentLines = mocksContent.split('\n'); // The _FakeBar_0 class should be generated exactly once. expect(mocksContentLines.where((line) => line.contains('class _FakeBar_0')), hasLength(1)); @@ -3574,7 +3574,7 @@ void _expectBuilderThrows({ List enabledExperiments = const [], LanguageVersion? languageVersion, }) { - var packageConfig = PackageConfig([ + final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: languageVersion ?? LanguageVersion(2, 12)) diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 81999230d..0674684bf 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -95,7 +95,7 @@ void main() { /// Test [MockBuilder] in a package which has not opted into null safety. Future testPreNonNullable(Map sourceAssets, {Map*/ Object>? outputs}) async { - var packageConfig = PackageConfig([ + final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 7)) @@ -107,14 +107,14 @@ void main() { /// Builds with [MockBuilder] in a package which has opted into null safety, /// returning the content of the generated mocks library. Future buildWithNonNullable(Map sourceAssets) async { - var packageConfig = PackageConfig([ + final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 15)) ]); await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); - var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); + final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]!); } @@ -123,7 +123,7 @@ void main() { }); test('generates a generic mock class without type arguments', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -140,7 +140,7 @@ void main() { }); test('without type arguments, generates generic method types', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo { @@ -158,7 +158,7 @@ void main() { }); test('generates a generic mock class with deep type arguments', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -179,7 +179,7 @@ void main() { }); test('generates a generic mock class with type arguments', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -201,7 +201,7 @@ void main() { test('generates a generic mock class with lower bound type arguments', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -222,7 +222,7 @@ void main() { }); test('generates a generic mock class with nullable type arguments', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -243,7 +243,7 @@ void main() { }); test('generates a generic mock class with nested type arguments', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -264,7 +264,7 @@ void main() { test('generates a generic mock class with type arguments but no name', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -282,7 +282,7 @@ void main() { test('generates a generic, bounded mock class without type arguments', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -301,7 +301,7 @@ void main() { }); test('generates mock classes from multiple annotations', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -324,7 +324,7 @@ void main() { test('generates mock classes from multiple annotations on a single element', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/a.dart': dedent(r''' class Foo {} @@ -348,7 +348,7 @@ void main() { }); test('generates a mock class with a declared mixin', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' class Foo {} @@ -370,7 +370,7 @@ void main() { }); test('generates a mock class with multiple declared mixins', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' class Foo {} @@ -394,7 +394,7 @@ void main() { test('generates a mock class with a declared mixin with a type arg', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' class Foo {} @@ -416,7 +416,7 @@ void main() { }); test('generates a mock class with a marker mixin', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': ''' class Foo {} @@ -441,7 +441,7 @@ void main() { test( 'generates a mock class which uses the old behavior of returning null on ' 'missing stubs', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} @@ -459,7 +459,7 @@ void main() { test( 'generates mock methods with non-nullable unknown types, given ' 'unsupportedMembers', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -488,7 +488,7 @@ void main() { test( 'generates mock methods with private return types, given ' 'unsupportedMembers', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -514,7 +514,7 @@ void main() { test('generates mock getters with private types, given unsupportedMembers', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' abstract class Foo { @@ -540,7 +540,7 @@ void main() { test('generates mock setters with private types, given unsupportedMembers', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' abstract class Foo { @@ -567,7 +567,7 @@ void main() { test( 'generates mock methods with return types with private names in type ' 'arguments, given unsupportedMembers', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -594,7 +594,7 @@ void main() { test( 'generates mock methods with return types with private names in function ' 'types, given unsupportedMembers', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -621,7 +621,7 @@ void main() { test( 'generates mock methods with private parameter types, given ' 'unsupportedMembers', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -648,7 +648,7 @@ void main() { test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for basic known types', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -675,7 +675,7 @@ void main() { test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for basic known types, in mixed mode', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -703,7 +703,7 @@ void main() { test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for unknown types', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -743,7 +743,7 @@ void main() { test('generates mock classes including a fallback generator for a getter', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -773,7 +773,7 @@ void main() { test( 'generates mock classes including a fallback generator for a generic ' 'method with positional parameters', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -804,7 +804,7 @@ void main() { test( 'generates mock classes including a fallback generator for a generic ' 'method on a super class', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' abstract class FooBase { @@ -834,7 +834,7 @@ void main() { }); test('generates mock classes including two fallback generators', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' abstract class Foo { @@ -871,7 +871,7 @@ void main() { 'generates mock classes including a fallback generator for a generic ' 'method with positional parameters returning a Future of the generic', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -900,7 +900,7 @@ void main() { test( 'generates mock classes including a fallback generator for a generic ' 'method with named parameters', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -929,7 +929,7 @@ void main() { test( 'generates mock classes including a fallback generator for a bounded ' 'generic method with named parameters', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -959,7 +959,7 @@ void main() { 'generates mock classes including a fallback generator for a generic ' 'method with a parameter with a function-typed type argument with ' 'unknown return type', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(''' abstract class Foo { @@ -989,7 +989,7 @@ void main() { test( 'generates mock classes including a fallback generator and ' 'OnMissingStub.returnDefault', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' abstract class Foo { @@ -1313,7 +1313,7 @@ void main() { 'generates a mock class which uses the new behavior of returning ' 'a valid value for missing stubs, if GenerateNiceMocks were used', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo { @@ -1336,7 +1336,7 @@ void main() { 'generates a mock class which uses the new behavior of returning ' 'a valid value for missing stubs, if GenerateNiceMocks and ' 'fallbackGenerators were used', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo { @@ -1362,7 +1362,7 @@ void main() { test('mixed GenerateMocks and GenerateNiceMocks annotations could be used', () async { - var mocksContent = await buildWithNonNullable({ + final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Foo {} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 37049084b..62c9c7a92 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -164,7 +164,7 @@ void main() { test('a method with a non-nullable parameter can capture an argument', () { when(foo.positionalParameter(any)).thenReturn('Stubbed'); foo.positionalParameter(42); - var captured = verify(foo.positionalParameter(captureAny)).captured; + final captured = verify(foo.positionalParameter(captureAny)).captured; expect(captured[0], equals(42)); }); @@ -318,8 +318,8 @@ void main() { }); test('a generated mock can be used as a stub argument', () { - var foo = MockFoo(); - var bar = MockBar(); + final foo = MockFoo(); + final bar = MockBar(); when(foo.methodWithBarArg(bar)).thenReturn('mocked result'); expect(foo.methodWithBarArg(bar), equals('mocked result')); }); @@ -327,14 +327,14 @@ void main() { test( 'a generated mock which returns null on missing stubs can be used as a ' 'stub argument', () { - var foo = MockFooRelaxed(); - var bar = MockBarRelaxed(); + final foo = MockFooRelaxed(); + final bar = MockBarRelaxed(); when(foo.methodWithBarArg(bar)).thenReturn('mocked result'); expect(foo.methodWithBarArg(bar), equals('mocked result')); }); test('a generated mock with a mixed in type can use mixed in members', () { - var hasPrivate = MockHasPrivate(); + final hasPrivate = MockHasPrivate(); // This should not throw, when `setPrivate` accesses a private member on // `hasPrivate`. setPrivate(hasPrivate); diff --git a/pkgs/mockito/test/invocation_matcher_test.dart b/pkgs/mockito/test/invocation_matcher_test.dart index 9b139abdb..baa8838a3 100644 --- a/pkgs/mockito/test/invocation_matcher_test.dart +++ b/pkgs/mockito/test/invocation_matcher_test.dart @@ -21,11 +21,11 @@ void main() { group('$isInvocation', () { test('positional arguments', () { stub.say('Hello'); - var call1 = Stub.lastInvocation; + final call1 = Stub.lastInvocation; stub.say('Hello'); - var call2 = Stub.lastInvocation; + final call2 = Stub.lastInvocation; stub.say('Guten Tag'); - var call3 = Stub.lastInvocation; + final call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -38,11 +38,11 @@ void main() { test('named arguments', () { stub.eat('Chicken', alsoDrink: true); - var call1 = Stub.lastInvocation; + final call1 = Stub.lastInvocation; stub.eat('Chicken', alsoDrink: true); - var call2 = Stub.lastInvocation; + final call2 = Stub.lastInvocation; stub.eat('Chicken', alsoDrink: false); - var call3 = Stub.lastInvocation; + final call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -55,11 +55,11 @@ void main() { test('optional arguments', () { stub.lie(true); - var call1 = Stub.lastInvocation; + final call1 = Stub.lastInvocation; stub.lie(true); - var call2 = Stub.lastInvocation; + final call2 = Stub.lastInvocation; stub.lie(false); - var call3 = Stub.lastInvocation; + final call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -72,11 +72,11 @@ void main() { test('getter', () { stub.value; - var call1 = Stub.lastInvocation; + final call1 = Stub.lastInvocation; stub.value; - var call2 = Stub.lastInvocation; + final call2 = Stub.lastInvocation; stub.value = true; - var call3 = Stub.lastInvocation; + final call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -90,11 +90,11 @@ void main() { test('setter', () { stub.value = true; - var call1 = Stub.lastInvocation; + final call1 = Stub.lastInvocation; stub.value = true; - var call2 = Stub.lastInvocation; + final call2 = Stub.lastInvocation; stub.value = false; - var call3 = Stub.lastInvocation; + final call3 = Stub.lastInvocation; shouldPass(call1, isInvocation(call2)); shouldFail( call1, @@ -110,7 +110,7 @@ void main() { group('$invokes', () { test('positional arguments', () { stub.say('Hello'); - var call = Stub.lastInvocation; + final call = Stub.lastInvocation; shouldPass(call, invokes(#say, positionalArguments: ['Hello'])); shouldPass(call, invokes(#say, positionalArguments: [anything])); shouldFail( @@ -124,7 +124,7 @@ void main() { test('named arguments', () { stub.fly(miles: 10); - var call = Stub.lastInvocation; + final call = Stub.lastInvocation; shouldPass(call, invokes(#fly, namedArguments: {#miles: 10})); shouldPass(call, invokes(#fly, namedArguments: {#miles: greaterThan(5)})); shouldFail( diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index 0f7f1dd7f..b9072c3f8 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -76,7 +76,7 @@ void main() { group('mixin support', () { test('should work', () { - var foo = _MockFoo(); + final foo = _MockFoo(); when(foo.baz()).thenReturn('baz'); expect(foo.bar(), 'baz'); }); @@ -95,7 +95,7 @@ void main() { }); test('should mock method with mock args', () { - var m1 = _MockedClass(); + final m1 = _MockedClass(); when(mock.methodWithObjArgs(m1)).thenReturn('Ultimate Answer'); expect(mock.methodWithObjArgs(_MockedClass()), isNull); expect(mock.methodWithObjArgs(m1), equals('Ultimate Answer')); @@ -177,7 +177,7 @@ void main() { }); test('should use identical equality between it is not mocked', () { - var anotherMock = _MockedClass(); + final anotherMock = _MockedClass(); expect(mock == anotherMock, isFalse); expect(mock == mock, isTrue); }); @@ -195,7 +195,7 @@ void main() { }); test('should return mock to make simple oneline mocks', () { - _RealClass mockWithSetup = _MockedClass(); + final _RealClass mockWithSetup = _MockedClass(); when(mockWithSetup.methodWithoutArgs()).thenReturn('oneline'); expect(mockWithSetup.methodWithoutArgs(), equals('oneline')); }); @@ -216,7 +216,7 @@ void main() { test('should throw if `when` is called while stubbing', () { expect(() { _MockedClass responseHelper() { - var mock2 = _MockedClass(); + final mock2 = _MockedClass(); when(mock2.getter).thenReturn('A'); return mock2; } @@ -261,7 +261,7 @@ void main() { }); test('should throw if attempting to stub a real method', () { - var foo = _MockFoo(); + final foo = _MockFoo(); expect(() { when(foo.quux()).thenReturn('Stub'); }, throwsStateError); diff --git a/pkgs/mockito/test/until_called_test.dart b/pkgs/mockito/test/until_called_test.dart index 0adca2146..6c96ea231 100644 --- a/pkgs/mockito/test/until_called_test.dart +++ b/pkgs/mockito/test/until_called_test.dart @@ -80,7 +80,7 @@ void main() { }); group('untilCalled', () { - var streamController = StreamController.broadcast(); + final streamController = StreamController.broadcast(); group('on methods already called', () { test('waits for method without args', () async { diff --git a/pkgs/mockito/test/verify_test.dart b/pkgs/mockito/test/verify_test.dart index e095611e3..373561d92 100644 --- a/pkgs/mockito/test/verify_test.dart +++ b/pkgs/mockito/test/verify_test.dart @@ -177,7 +177,7 @@ void main() { final expectedMessage = RegExp.escape('No matching calls. ' 'All calls: _MockedClass.setter==\'A\'\n$noMatchingCallsFooter'); // RegExp needed because of https://github.com/dart-lang/sdk/issues/33565 - var expectedPattern = RegExp(expectedMessage.replaceFirst('==', '=?=')); + final expectedPattern = RegExp(expectedMessage.replaceFirst('==', '=?=')); expectFail(expectedPattern, () => verify(mock.setter = 'B')); verify(mock.setter = 'A'); @@ -516,7 +516,7 @@ void main() { mock.methodWithNormalArgs(1); mock.methodWithoutArgs(); mock.methodWithNormalArgs(2); - var captured = verifyInOrder([ + final captured = verifyInOrder([ mock.methodWithNormalArgs(captureAny), mock.methodWithoutArgs(), mock.methodWithNormalArgs(captureAny) From b08192316d1f909daefc89bddf8bcf7b72b87e15 Mon Sep 17 00:00:00 2001 From: nbosch Date: Thu, 13 Apr 2023 18:45:41 +0000 Subject: [PATCH 505/607] Deprecate the mixingIn argument to MockSpec There are no internal uses, and the behavior goes against best practices for using mocks. PiperOrigin-RevId: 524060587 --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/lib/annotations.dart | 4 +++- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 53456eaf4..f79253f6f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.4.1-dev + +* Deprecate the `mixingIn` argument to `MockSpec`. Best practice is to avoid + any concrete implementation in classes which extend `Mock`. + ## 5.4.0 * Fix nice mocks generation in mixed mode (generated code is pre null-safety, diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 32c95c0cc..8fbeae00c 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -147,7 +147,9 @@ class MockSpec { /// as a legal return value. const MockSpec({ Symbol? as, - List mixingIn = const [], + @Deprecated('Avoid adding concrete implementation to mock classes. ' + 'Use a manual implementation of the class without `Mock`') + List mixingIn = const [], @Deprecated('Specify "missing stub" behavior with the ' '[onMissingStub] parameter.') this.returnNullOnMissingStub = false, diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 3dfd2c8ed..cac536c33 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.0'; +const packageVersion = '5.4.1-dev'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 060205fb2..0a0226ea7 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.0 +version: 5.4.1-dev description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From 8bdb5853ab41210bafe8022ab0272f3797a17080 Mon Sep 17 00:00:00 2001 From: oprypin Date: Tue, 25 Apr 2023 09:29:09 +0000 Subject: [PATCH 506/607] Keep generated mock files at language version 2.19 In the current state, Dart 3.0 will prevent generating mocks of some stdlib classes that now "can't be implemented" due to `base class`. Let's keep the old language version and everything will keep working for now. PiperOrigin-RevId: 526903976 --- pkgs/mockito/lib/src/builder.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 12782f3ba..313adfbcb 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -124,7 +124,8 @@ class MockBuilder implements Builder { // The source lib may be pre-null-safety because of an explicit opt-out // (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To // allow for this situation, we must also add an opt-out comment here. - final dartVersionComment = sourceLibIsNonNullable ? '' : '// @dart=2.9'; + final dartVersionComment = + sourceLibIsNonNullable ? '// @dart=2.19' : '// @dart=2.9'; final mockLibraryContent = DartFormatter().format(''' // Mocks generated by Mockito $packageVersion from annotations // in ${entryLib.definingCompilationUnit.source.uri.path}. From 7b3c396dd6025c7dd10421e01b1951695c940712 Mon Sep 17 00:00:00 2001 From: yanok Date: Wed, 26 Apr 2023 10:55:36 +0000 Subject: [PATCH 507/607] Fix the type variable capture problem Consider this example: ```dart class Foo { Iterable map(T Function(E) f) => /* ... */ } class Bar extends Foo {} @GenerateNiceMocks([MockSpec]) void main() {} ``` When generating an override for `map` we just use an `element.name` String to generate types, so we end up with this: ```dart Iterable map(T Function(T) f) ``` These must be two different `T`s, one bound at the class definition while another one at the method definition. But since we converted them to strings, we lost this information, so now they all refer to the `T` bound by the method declaration. Ideally I'd like code_builder to handle this for us, but there are no signs of any type variable support, so I thought it should be pretty straight forward to add it on Mockito level. We just need to check if we are shadowing any existing type variable while adding a new one and rename the new one if that's the case. PiperOrigin-RevId: 527221562 --- pkgs/mockito/CHANGELOG.md | 2 + pkgs/mockito/lib/src/builder.dart | 563 ++++++++++-------- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 112 ++++ 4 files changed, 415 insertions(+), 264 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f79253f6f..fcbea6f8c 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,8 @@ * Deprecate the `mixingIn` argument to `MockSpec`. Best practice is to avoid any concrete implementation in classes which extend `Mock`. +* Fixed generation for clashing type variable names. +* Require analyzer 5.11.0. ## 5.4.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 313adfbcb..dea9d76d8 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1132,86 +1132,76 @@ class _MockClassInfo { // For each type parameter on [classToMock], the Mock class needs a type // parameter with same type variables, and a mirrored type argument for // the "implements" clause. - final typeReferences = []; final typeParameters = aliasedElement?.typeParameters ?? classToMock.typeParameters; final typeArguments = typeAlias?.typeArguments ?? typeToMock.typeArguments; - if (mockTarget.hasExplicitTypeArguments) { - // [typeToMock] is a reference to a type with type arguments (for - // example: `Foo`). Generate a non-generic mock class which - // implements the mock target with said type arguments. For example: - // `class MockFoo extends Mock implements Foo {}` - for (final typeArgument in typeArguments) { - typeReferences.add(_typeReference(typeArgument)); + _withTypeParameters( + mockTarget.hasExplicitTypeArguments ? [] : typeParameters, + (typeParamsWithBounds, typeParams) { + cBuilder.types.addAll(typeParamsWithBounds); + for (final mixin in mockTarget.mixins) { + cBuilder.mixins.add(TypeReference((b) { + b + ..symbol = mixin.element.name + ..url = _typeImport(mixin.element) + ..types.addAll(mixin.typeArguments.map(_typeReference)); + })); } - } else { - // [typeToMock] is a simple reference to a generic type (for example: - // `Foo`, a reference to `class Foo {}`). Generate a generic mock - // class which perfectly mirrors the type parameters on [typeToMock], - // forwarding them to the "implements" clause. - for (final typeParameter in typeParameters) { - cBuilder.types.add(_typeParameterReference(typeParameter)); - typeReferences.add(refer(typeParameter.name)); - } - } - - for (final mixin in mockTarget.mixins) { - cBuilder.mixins.add(TypeReference((b) { + cBuilder.implements.add(TypeReference((b) { b - ..symbol = mixin.element.name - ..url = _typeImport(mixin.element) - ..types.addAll(mixin.typeArguments.map(_typeReference)); + ..symbol = className + ..url = _typeImport(aliasedElement ?? classToMock) + ..types.addAll(mockTarget.hasExplicitTypeArguments + ? typeArguments.map(_typeReference) + : typeParams); })); - } - cBuilder.implements.add(TypeReference((b) { - b - ..symbol = className - ..url = _typeImport(aliasedElement ?? classToMock) - ..types.addAll(typeReferences); - })); - if (mockTarget.onMissingStub == OnMissingStub.throwException) { - cBuilder.constructors.add(_constructorWithThrowOnMissingStub); - } - - final substitution = Substitution.fromPairs([ - ...classToMock.typeParameters, - ...?aliasedElement?.typeParameters, - ], [ - ...typeToMock.typeArguments, - ...?typeAlias?.typeArguments, - ]); - final members = - inheritanceManager.getInterface(classToMock).map.values.map((member) { - return ExecutableMember.from2(member, substitution); - }); + if (mockTarget.onMissingStub == OnMissingStub.throwException) { + cBuilder.constructors.add(_constructorWithThrowOnMissingStub); + } - // The test can be pre-null-safety but if the class - // we want to mock is defined in a null safe library, - // we still need to override methods to get nice mocks. - final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == - OnMissingStub.returnDefault && - typeToMock.element.enclosingElement.library.isNonNullableByDefault; - - if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { - cBuilder.methods.addAll( - fieldOverrides(members.whereType())); - cBuilder.methods - .addAll(methodOverrides(members.whereType())); - } else { - // For a pre-null safe library, we do not need to re-implement any - // members for the purpose of expanding their parameter types. However, - // we may need to include an implementation of `toString()`, if the - // class-to-mock has added optional parameters. - final toStringMethod = members - .whereType() - .firstWhereOrNull((m) => m.name == 'toString'); - if (toStringMethod != null) { - cBuilder.methods.addAll(methodOverrides([toStringMethod])); + final substitution = Substitution.fromPairs([ + ...classToMock.typeParameters, + ...?aliasedElement?.typeParameters, + ], [ + ...typeToMock.typeArguments, + ...?typeAlias?.typeArguments, + ]); + final members = inheritanceManager + .getInterface(classToMock) + .map + .values + .map((member) { + return ExecutableMember.from2(member, substitution); + }); + + // The test can be pre-null-safety but if the class + // we want to mock is defined in a null safe library, + // we still need to override methods to get nice mocks. + final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == + OnMissingStub.returnDefault && + typeToMock.element.enclosingElement.library.isNonNullableByDefault; + + if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { + cBuilder.methods.addAll( + fieldOverrides(members.whereType())); + cBuilder.methods + .addAll(methodOverrides(members.whereType())); + } else { + // For a pre-null safe library, we do not need to re-implement any + // members for the purpose of expanding their parameter types. However, + // we may need to include an implementation of `toString()`, if the + // class-to-mock has added optional parameters. + final toStringMethod = members + .whereType() + .firstWhereOrNull((m) => m.name == 'toString'); + if (toStringMethod != null) { + cBuilder.methods.addAll(methodOverrides([toStringMethod])); + } } - } + }); }); } @@ -1312,127 +1302,131 @@ class _MockClassInfo { var name = method.displayName; if (method.isOperator) name = 'operator$name'; final returnType = method.returnType; - builder - ..name = name - ..annotations.add(referImported('override', 'dart:core')) - ..types.addAll(method.typeParameters.map(_typeParameterReference)); - // We allow overriding a method with a private return type by omitting the - // return type (which is then inherited). - if (!returnType.containsPrivateName) { - builder.returns = _typeReference(returnType); - } - - // These two variables store the arguments that will be passed to the - // [Invocation] built for `noSuchMethod`. - final invocationPositionalArgs = []; - final invocationNamedArgs = {}; + _withTypeParameters(method.typeParameters, (typeParamsWithBounds, _) { + builder + ..name = name + ..annotations.add(referImported('override', 'dart:core')) + ..types.addAll(typeParamsWithBounds); + // We allow overriding a method with a private return type by omitting the + // return type (which is then inherited). + if (!returnType.containsPrivateName) { + builder.returns = _typeReference(returnType); + } + + // These two variables store the arguments that will be passed to the + // [Invocation] built for `noSuchMethod`. + final invocationPositionalArgs = []; + final invocationNamedArgs = {}; - var position = 0; - for (final parameter in method.parameters) { - if (parameter.isRequiredPositional || parameter.isOptionalPositional) { - final superParameterType = - _escapeCovariance(parameter, position: position); - final matchingParameter = _matchingParameter(parameter, - superParameterType: superParameterType, forceNullable: true); - if (parameter.isRequiredPositional) { - builder.requiredParameters.add(matchingParameter); - } else { + var position = 0; + for (final parameter in method.parameters) { + if (parameter.isRequiredPositional || parameter.isOptionalPositional) { + final superParameterType = + _escapeCovariance(parameter, position: position); + final matchingParameter = _matchingParameter(parameter, + superParameterType: superParameterType, forceNullable: true); + if (parameter.isRequiredPositional) { + builder.requiredParameters.add(matchingParameter); + } else { + builder.optionalParameters.add(matchingParameter); + } + invocationPositionalArgs.add(refer(parameter.displayName)); + position++; + } else if (parameter.isNamed) { + final superParameterType = + _escapeCovariance(parameter, isNamed: true); + final matchingParameter = _matchingParameter(parameter, + superParameterType: superParameterType, forceNullable: true); builder.optionalParameters.add(matchingParameter); + invocationNamedArgs[refer('#${parameter.displayName}')] = + refer(parameter.displayName); + } else { + throw StateError( + 'Parameter ${parameter.name} on method ${method.name} ' + 'is not required-positional, nor optional-positional, nor named'); } - invocationPositionalArgs.add(refer(parameter.displayName)); - position++; - } else if (parameter.isNamed) { - final superParameterType = _escapeCovariance(parameter, isNamed: true); - final matchingParameter = _matchingParameter(parameter, - superParameterType: superParameterType, forceNullable: true); - builder.optionalParameters.add(matchingParameter); - invocationNamedArgs[refer('#${parameter.displayName}')] = - refer(parameter.displayName); - } else { - throw StateError('Parameter ${parameter.name} on method ${method.name} ' - 'is not required-positional, nor optional-positional, nor named'); } - } - - if (name == 'toString') { - // We cannot call `super.noSuchMethod` here; we must use [Mock]'s - // implementation. - builder.body = refer('super').property('toString').call([]).code; - return; - } - final returnTypeIsTypeVariable = - typeSystem.isPotentiallyNonNullable(returnType) && - returnType is analyzer.TypeParameterType; - final fallbackGenerator = fallbackGenerators[method.name]; - final parametersContainPrivateName = - method.parameters.any((p) => p.type.containsPrivateName); - final throwsUnsupported = fallbackGenerator == null && - (returnTypeIsTypeVariable || - returnType.containsPrivateName || - parametersContainPrivateName); + if (name == 'toString') { + // We cannot call `super.noSuchMethod` here; we must use [Mock]'s + // implementation. + builder.body = refer('super').property('toString').call([]).code; + return; + } - if (throwsUnsupported) { - if (!mockTarget.unsupportedMembers.contains(name)) { - // We shouldn't get here as this is guarded against in - // [_MockTargetGatherer._checkFunction]. - throw InvalidMockitoAnnotationException( - "Mockito cannot generate a valid override for '$name', as it has a " - 'non-nullable unknown return type or a private type in its ' - 'signature.'); + final returnTypeIsTypeVariable = + typeSystem.isPotentiallyNonNullable(returnType) && + returnType is analyzer.TypeParameterType; + final fallbackGenerator = fallbackGenerators[method.name]; + final parametersContainPrivateName = + method.parameters.any((p) => p.type.containsPrivateName); + final throwsUnsupported = fallbackGenerator == null && + (returnTypeIsTypeVariable || + returnType.containsPrivateName || + parametersContainPrivateName); + + if (throwsUnsupported) { + if (!mockTarget.unsupportedMembers.contains(name)) { + // We shouldn't get here as this is guarded against in + // [_MockTargetGatherer._checkFunction]. + throw InvalidMockitoAnnotationException( + "Mockito cannot generate a valid override for '$name', as it has a " + 'non-nullable unknown return type or a private type in its ' + 'signature.'); + } + builder.body = refer('UnsupportedError') + .call([ + // Generate a raw string since name might contain a $. + literalString( + '"$name" cannot be used without a mockito fallback generator.', + raw: true) + ]) + .thrown + .code; + return; } - builder.body = refer('UnsupportedError') - .call([ - // Generate a raw string since name might contain a $. - literalString( - '"$name" cannot be used without a mockito fallback generator.', - raw: true) - ]) - .thrown - .code; - return; - } - final invocation = - referImported('Invocation', 'dart:core').property('method').call([ - refer('#${method.displayName}'), - literalList(invocationPositionalArgs), - if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs), - ]); + final invocation = + referImported('Invocation', 'dart:core').property('method').call([ + refer('#${method.displayName}'), + literalList(invocationPositionalArgs), + if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs), + ]); - Expression? returnValueForMissingStub; - if (returnType.isVoid) { - returnValueForMissingStub = refer('null'); - } else if (returnType.isFutureOfVoid) { - returnValueForMissingStub = - _futureReference(refer('void')).property('value').call([]); - } else if (mockTarget.onMissingStub == OnMissingStub.returnDefault) { - if (fallbackGenerator != null) { - // Re-use the fallback for missing stub. + Expression? returnValueForMissingStub; + if (returnType.isVoid) { + returnValueForMissingStub = refer('null'); + } else if (returnType.isFutureOfVoid) { returnValueForMissingStub = - _fallbackGeneratorCode(method, fallbackGenerator); - } else { - // Return a legal default value if no stub is found which matches a real - // call. - returnValueForMissingStub = _dummyValue(returnType, invocation); + _futureReference(refer('void')).property('value').call([]); + } else if (mockTarget.onMissingStub == OnMissingStub.returnDefault) { + if (fallbackGenerator != null) { + // Re-use the fallback for missing stub. + returnValueForMissingStub = + _fallbackGeneratorCode(method, fallbackGenerator); + } else { + // Return a legal default value if no stub is found which matches a real + // call. + returnValueForMissingStub = _dummyValue(returnType, invocation); + } } - } - final namedArgs = { - if (fallbackGenerator != null) - 'returnValue': _fallbackGeneratorCode(method, fallbackGenerator) - else if (typeSystem._returnTypeIsNonNullable(method)) - 'returnValue': _dummyValue(returnType, invocation), - if (returnValueForMissingStub != null) - 'returnValueForMissingStub': returnValueForMissingStub, - }; + final namedArgs = { + if (fallbackGenerator != null) + 'returnValue': _fallbackGeneratorCode(method, fallbackGenerator) + else if (typeSystem._returnTypeIsNonNullable(method)) + 'returnValue': _dummyValue(returnType, invocation), + if (returnValueForMissingStub != null) + 'returnValueForMissingStub': returnValueForMissingStub, + }; - var superNoSuchMethod = - refer('super').property('noSuchMethod').call([invocation], namedArgs); - if (!returnType.isVoid && !returnType.isDynamic) { - superNoSuchMethod = superNoSuchMethod.asA(_typeReference(returnType)); - } + var superNoSuchMethod = + refer('super').property('noSuchMethod').call([invocation], namedArgs); + if (!returnType.isVoid && !returnType.isDynamic) { + superNoSuchMethod = superNoSuchMethod.asA(_typeReference(returnType)); + } - builder.body = superNoSuchMethod.code; + builder.body = superNoSuchMethod.code; + }); } Expression _fallbackGeneratorCode( @@ -1448,8 +1442,10 @@ class _MockClassInfo { } final functionReference = referImported(function.name, _typeImport(function)); - return functionReference.call(positionalArguments, namedArguments, - [for (var t in method.typeParameters) refer(t.name)]); + return functionReference.call(positionalArguments, namedArguments, [ + for (var t in method.typeParameters) + _typeParameterReference(t, withBound: false) + ]); } Expression _dummyValue(analyzer.DartType type, Expression invocation) { @@ -1552,36 +1548,40 @@ class _MockClassInfo { Expression _dummyFunctionValue( analyzer.FunctionType type, Expression invocation) { return Method((b) { - // The positional parameters in a FunctionType have no names. This - // counter lets us create unique dummy names. - var position = 0; - b.types.addAll(type.typeFormals.map(_typeParameterReference)); - for (final parameter in type.parameters) { - if (parameter.isRequiredPositional) { - final matchingParameter = _matchingParameter(parameter, - superParameterType: parameter.type, defaultName: '__p$position'); - b.requiredParameters.add(matchingParameter); - position++; - } else if (parameter.isOptionalPositional) { - final matchingParameter = _matchingParameter(parameter, - superParameterType: parameter.type, defaultName: '__p$position'); - b.optionalParameters.add(matchingParameter); - position++; - } else if (parameter.isOptionalNamed) { - final matchingParameter = - _matchingParameter(parameter, superParameterType: parameter.type); - b.optionalParameters.add(matchingParameter); - } else if (parameter.isRequiredNamed) { - final matchingParameter = - _matchingParameter(parameter, superParameterType: parameter.type); - b.optionalParameters.add(matchingParameter); + _withTypeParameters(type.typeFormals, (typeParamsWithBounds, _) { + b.types.addAll(typeParamsWithBounds); + // The positional parameters in a FunctionType have no names. This + // counter lets us create unique dummy names. + var position = 0; + for (final parameter in type.parameters) { + if (parameter.isRequiredPositional) { + final matchingParameter = _matchingParameter(parameter, + superParameterType: parameter.type, + defaultName: '__p$position'); + b.requiredParameters.add(matchingParameter); + position++; + } else if (parameter.isOptionalPositional) { + final matchingParameter = _matchingParameter(parameter, + superParameterType: parameter.type, + defaultName: '__p$position'); + b.optionalParameters.add(matchingParameter); + position++; + } else if (parameter.isOptionalNamed) { + final matchingParameter = _matchingParameter(parameter, + superParameterType: parameter.type); + b.optionalParameters.add(matchingParameter); + } else if (parameter.isRequiredNamed) { + final matchingParameter = _matchingParameter(parameter, + superParameterType: parameter.type); + b.optionalParameters.add(matchingParameter); + } } - } - if (type.returnType.isVoid) { - b.body = Code(''); - } else { - b.body = _dummyValue(type.returnType, invocation).code; - } + if (type.returnType.isVoid) { + b.body = Code(''); + } else { + b.body = _dummyValue(type.returnType, invocation).code; + } + }); }).genericClosure; } @@ -1612,40 +1612,39 @@ class _MockClassInfo { // For each type parameter on [elementToFake], the Fake class needs a type // parameter with same type variables, and a mirrored type argument for // the "implements" clause. - final typeParameters = []; cBuilder ..name = fakeName ..extend = referImported('SmartFake', 'package:mockito/mockito.dart'); - for (final typeParameter in elementToFake.typeParameters) { - cBuilder.types.add(_typeParameterReference(typeParameter)); - typeParameters.add(refer(typeParameter.name)); - } - cBuilder.implements.add(TypeReference((b) { - b - ..symbol = elementToFake.name - ..url = _typeImport(elementToFake) - ..types.addAll(typeParameters); - })); - cBuilder.constructors.add(Constructor((constrBuilder) => constrBuilder - ..requiredParameters.addAll([ - Parameter((pBuilder) => pBuilder - ..name = 'parent' - ..type = referImported('Object', 'dart:core')), - Parameter((pBuilder) => pBuilder - ..name = 'parentInvocation' - ..type = referImported('Invocation', 'dart:core')) - ]) - ..initializers.add(refer('super') - .call([refer('parent'), refer('parentInvocation')]).code))); - - final toStringMethod = - elementToFake.lookUpMethod('toString', elementToFake.library); - if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { - // If [elementToFake] includes an overriding `toString` implementation, - // we need to include an implementation which matches the signature. - cBuilder.methods.add(Method( - (mBuilder) => _buildOverridingMethod(mBuilder, toStringMethod))); - } + _withTypeParameters(elementToFake.typeParameters, + (typeParamsWithBounds, typeParams) { + cBuilder.types.addAll(typeParamsWithBounds); + cBuilder.implements.add(TypeReference((b) { + b + ..symbol = elementToFake.name + ..url = _typeImport(elementToFake) + ..types.addAll(typeParams); + })); + cBuilder.constructors.add(Constructor((constrBuilder) => constrBuilder + ..requiredParameters.addAll([ + Parameter((pBuilder) => pBuilder + ..name = 'parent' + ..type = referImported('Object', 'dart:core')), + Parameter((pBuilder) => pBuilder + ..name = 'parentInvocation' + ..type = referImported('Invocation', 'dart:core')) + ]) + ..initializers.add(refer('super') + .call([refer('parent'), refer('parentInvocation')]).code))); + + final toStringMethod = + elementToFake.lookUpMethod('toString', elementToFake.library); + if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { + // If [elementToFake] includes an overriding `toString` implementation, + // we need to include an implementation which matches the signature. + cBuilder.methods.add(Method( + (mBuilder) => _buildOverridingMethod(mBuilder, toStringMethod))); + } + }); })); mockLibraryInfo.fakedInterfaceElements.add(elementToFake); } @@ -1967,12 +1966,48 @@ class _MockClassInfo { builder.body = returnNoSuchMethod.code; } + final List> _typeVariableScopes = []; + final Set _usedTypeVariables = {}; + + String _lookupTypeParameter(TypeParameterElement typeParameter) => + _typeVariableScopes.reversed.firstWhereOrNull( + (scope) => scope.containsKey(typeParameter))?[typeParameter] ?? + (throw StateError( + '$typeParameter not found, scopes: $_typeVariableScopes')); + + String _newTypeVar(TypeParameterElement typeParameter) { + var idx = 0; + while (true) { + final name = '${typeParameter.name}${idx == 0 ? '' : '$idx'}'; + if (!_usedTypeVariables.contains(name)) { + _usedTypeVariables.add(name); + return name; + } + idx++; + } + } + + T _withTypeParameters(Iterable typeParameters, + T Function(Iterable, Iterable) body) { + final typeVars = {for (final t in typeParameters) t: _newTypeVar(t)}; + _typeVariableScopes.add(typeVars); + final typeRefsWithBounds = typeParameters.map(_typeParameterReference); + final typeRefs = + typeParameters.map((t) => _typeParameterReference(t, withBound: false)); + + final result = body(typeRefsWithBounds, typeRefs); + _typeVariableScopes.removeLast(); + _usedTypeVariables.removeAll(typeVars.values); + return result; + } + /// Create a reference for [typeParameter], properly referencing all types /// in bounds. - TypeReference _typeParameterReference(TypeParameterElement typeParameter) { + TypeReference _typeParameterReference(TypeParameterElement typeParameter, + {bool withBound = true}) { return TypeReference((b) { - b.symbol = typeParameter.name; - if (typeParameter.bound != null) { + b.symbol = _lookupTypeParameter(typeParameter); + if (withBound && typeParameter.bound != null) { b.bound = _typeReference(typeParameter.bound!); } }); @@ -2011,26 +2046,28 @@ class _MockClassInfo { if (alias == null || alias.element.isPrivate) { // [type] does not refer to a type alias, or it refers to a private type // alias; we must instead write out its signature. - return FunctionType((b) { - b - ..isNullable = - forceNullable || typeSystem.isPotentiallyNullable(type) - ..returnType = _typeReference(type.returnType) - ..requiredParameters - .addAll(type.normalParameterTypes.map(_typeReference)) - ..optionalParameters - .addAll(type.optionalParameterTypes.map(_typeReference)); - for (final parameter - in type.parameters.where((p) => p.isOptionalNamed)) { - b.namedParameters[parameter.name] = _typeReference(parameter.type); - } - for (final parameter - in type.parameters.where((p) => p.isRequiredNamed)) { - b.namedRequiredParameters[parameter.name] = - _typeReference(parameter.type); - } - b.types.addAll(type.typeFormals.map(_typeParameterReference)); - }); + return FunctionType((b) => + _withTypeParameters(type.typeFormals, (typeParams, _) { + b.types.addAll(typeParams); + b + ..isNullable = + forceNullable || typeSystem.isPotentiallyNullable(type) + ..returnType = _typeReference(type.returnType) + ..requiredParameters + .addAll(type.normalParameterTypes.map(_typeReference)) + ..optionalParameters + .addAll(type.optionalParameterTypes.map(_typeReference)); + for (final parameter + in type.parameters.where((p) => p.isOptionalNamed)) { + b.namedParameters[parameter.name] = + _typeReference(parameter.type); + } + for (final parameter + in type.parameters.where((p) => p.isRequiredNamed)) { + b.namedRequiredParameters[parameter.name] = + _typeReference(parameter.type); + } + })); } return TypeReference((b) { b @@ -2044,7 +2081,7 @@ class _MockClassInfo { } else if (type is analyzer.TypeParameterType) { return TypeReference((b) { b - ..symbol = type.element.name + ..symbol = _lookupTypeParameter(type.element) ..isNullable = forceNullable || typeSystem.isNullable(type); }); } else { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 0a0226ea7..35fec159b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: '>=2.18.0 <3.0.0' dependencies: - analyzer: '>=5.2.0 <6.0.0' + analyzer: '>=5.11.0 <6.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.2.0 collection: ^1.15.0 diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 0674684bf..f3046132f 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1690,6 +1690,118 @@ void main() { }); expect(mocksContent, contains('void m(dynamic x)')); }); + test('We rename clashing type variables', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + Iterable map(T Function(E) f); + } + + abstract class Bar extends Foo {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + expect(mocksContent, contains('Iterable map(T1 Function(T)? f)')); + }); + test('We rename clashing type variables in type aliases', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + Iterable map(T Function(E) f); + } + + typedef Bar = Foo; + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + expect(mocksContent, contains('Iterable map(T1 Function(T)? f)')); + }); + test('We rename clashing type variables in function literals', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + typedef Fun = List Function(T); + abstract class Foo { + Fun m(); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + expect(mocksContent, contains('returnValue: (T1 __p0) => []')); + }); + // Here rename in not needed, but the code does it. + test('We rename obscure type variables', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + abstract class Foo { + Iterable m(T Function() f); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(), + ]) + void main() {} + ''' + }); + expect(mocksContent, contains('Iterable m(T1 Function()? f)')); + }); + test('We do not rename unrelated type variables', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + abstract class Bar { + Iterable m1(X Function(T) f); + Iterable m2(X Function(T) f); + } + abstract class FooBar extends Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateNiceMocks([ + MockSpec(), MockSpec(), MockSpec() + ]) + void main() {} + ''' + }); + expect(mocksContent, contains('class MockBar')); + expect(mocksContent, contains('class MockFooBar')); + expect(mocksContent, contains('Iterable m1(X1 Function(X)? f)')); + expect(mocksContent, contains('Iterable m2(X1 Function(X)? f)')); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From c356bda24829219f9ae859eef7c4e1562e122101 Mon Sep 17 00:00:00 2001 From: yanok Date: Wed, 26 Apr 2023 10:55:54 +0000 Subject: [PATCH 508/607] First part of Dart3 support in Mockito Detect `sealed`/`base`/`final` classes and refuse to mock them. These classes can't be implemented, so cannot be mocked. Suggest mocking one of the possibilities for a sealed class. PiperOrigin-RevId: 527221606 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/builder.dart | 26 +++++-- .../mockito/test/builder/auto_mocks_test.dart | 72 +++++++++++++++++- .../test/builder/custom_mocks_test.dart | 74 ++++++++++++++++++- 4 files changed, 161 insertions(+), 12 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index fcbea6f8c..da2729e7a 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -4,6 +4,7 @@ any concrete implementation in classes which extend `Mock`. * Fixed generation for clashing type variable names. * Require analyzer 5.11.0. +* Fail to generate mocks of `sealed`/`base`/`final` classes. ## 5.4.0 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index dea9d76d8..222894113 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -518,6 +518,7 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'Mockito cannot mock `dynamic`'); } + final typeToMockElement = typeToMock.element; var type = _determineDartType(typeToMock, entryLib.typeProvider); if (type.alias == null) { // For a generic class without an alias like `Foo` or @@ -725,20 +726,35 @@ class _MockTargetGatherer { analyzer.DartType typeToMock, TypeProvider typeProvider) { if (typeToMock is analyzer.InterfaceType) { final elementToMock = typeToMock.element; + final displayName = "'${elementToMock.displayName}'"; if (elementToMock is EnumElement) { throw InvalidMockitoAnnotationException( - 'Mockito cannot mock an enum: ${elementToMock.displayName}'); + 'Mockito cannot mock an enum: $displayName'); } if (typeProvider.isNonSubtypableClass(elementToMock)) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock a non-subtypable type: ' - '${elementToMock.displayName}. It is illegal to subtype this ' + '$displayName. It is illegal to subtype this ' 'type.'); } + if (elementToMock is ClassElement) { + if (elementToMock.isSealed) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock a sealed class $displayName, ' + 'try mocking one of the variants instead.'); + } + if (elementToMock.isBase) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock a base class $displayName.'); + } + if (elementToMock.isFinal) { + throw InvalidMockitoAnnotationException( + 'Mockito cannot mock a final class $displayName.'); + } + } if (elementToMock.isPrivate) { throw InvalidMockitoAnnotationException( - 'Mockito cannot mock a private type: ' - '${elementToMock.displayName}.'); + 'Mockito cannot mock a private type: $displayName.'); } final typeParameterErrors = _checkTypeParameters(elementToMock.typeParameters, elementToMock); @@ -747,7 +763,7 @@ class _MockTargetGatherer { typeParameterErrors.map((m) => ' $m').join('\n'); throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid mock class which implements ' - "'${elementToMock.displayName}' for the following reasons:\n" + '$displayName for the following reasons:\n' '$joinedMessages'); } if (typeToMock.alias != null && diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index c2e9211d0..12cccf12d 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3139,7 +3139,7 @@ void main() { class _Foo {} '''), }, - message: contains('Mockito cannot mock a private type: _Foo.'), + message: contains("Mockito cannot mock a private type: '_Foo'."), ); }); @@ -3266,7 +3266,7 @@ void main() { enum Foo {} '''), }, - message: 'Mockito cannot mock an enum: Foo', + message: "Mockito cannot mock an enum: 'Foo'", ); }); @@ -3293,7 +3293,73 @@ void main() { void main() {} '''), }, - message: contains('Mockito cannot mock a non-subtypable type: int'), + message: contains("Mockito cannot mock a non-subtypable type: 'int'"), + ); + }); + + test('throws when GenerateMocks references a sealed class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + sealed class Foo {} + @GenerateMocks([Foo]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a sealed class 'Foo'"), + ); + }); + + test('throws when GenerateMocks references sealed a class via typedef', + () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + sealed class Foo {} + typedef Bar = Foo; + @GenerateMocks([Bar]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a sealed class 'Foo'"), + ); + }); + + test('throws when GenerateMocks references a base class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + base class Foo {} + @GenerateMocks([Foo]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a base class 'Foo'"), + ); + }); + + test('throws when GenerateMocks references a final class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + final class Foo {} + @GenerateMocks([Foo]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a final class 'Foo'"), ); }); diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index f3046132f..42e735973 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1086,7 +1086,7 @@ void main() { class _Foo {} '''), }, - message: contains('Mockito cannot mock a private type: _Foo.'), + message: contains("Mockito cannot mock a private type: '_Foo'."), ); }); @@ -1182,7 +1182,7 @@ void main() { enum Foo {} '''), }, - message: 'Mockito cannot mock an enum: Foo', + message: "Mockito cannot mock an enum: 'Foo'", ); }); @@ -1196,7 +1196,73 @@ void main() { void main() {} '''), }, - message: contains('Mockito cannot mock a non-subtypable type: int'), + message: contains("Mockito cannot mock a non-subtypable type: 'int'"), + ); + }); + + test('throws when GenerateMocks references a sealed class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + sealed class Foo {} + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a sealed class 'Foo'"), + ); + }); + + test('throws when GenerateMocks references sealed a class via typedef', + () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + sealed class Foo {} + typedef Bar = Foo; + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a sealed class 'Foo'"), + ); + }); + + test('throws when GenerateMocks references a base class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + base class Foo {} + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a base class 'Foo'"), + ); + }); + + test('throws when GenerateMocks references a final class', () async { + _expectBuilderThrows( + assets: { + ...annotationsAsset, + 'foo|test/foo_test.dart': dedent(''' + // @dart=3.0 + import 'package:mockito/annotations.dart'; + final class Foo {} + @GenerateMocks([], customMocks: [MockSpec()]) + void main() {} + '''), + }, + message: contains("Mockito cannot mock a final class 'Foo'"), ); }); @@ -1234,7 +1300,7 @@ void main() { mixin _FooMixin implements Foo {} '''), }, - message: contains('Mockito cannot mock a private type: _FooMixin'), + message: contains("Mockito cannot mock a private type: '_FooMixin'"), ); }); From 4e18e867d7be1b743db9f291a75cecd99e573e7c Mon Sep 17 00:00:00 2001 From: yanok Date: Wed, 26 Apr 2023 14:17:37 +0000 Subject: [PATCH 509/607] Require Dart SDK >= 2.19 Otherwise the constraint for analyzer couldn't be solved PiperOrigin-RevId: 527258028 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index da2729e7a..a6efc1b40 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,7 @@ * Deprecate the `mixingIn` argument to `MockSpec`. Best practice is to avoid any concrete implementation in classes which extend `Mock`. * Fixed generation for clashing type variable names. +* Require Dart >= 2.19.0. * Require analyzer 5.11.0. * Fail to generate mocks of `sealed`/`base`/`final` classes. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 35fec159b..7fbabe6ef 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,7 +6,7 @@ description: >- repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.18.0 <3.0.0' + sdk: '>=2.19.0 <3.0.0' dependencies: analyzer: '>=5.11.0 <6.0.0' From 1de1bcf5a23d6ad208c73c51bfaaeb538dc233b2 Mon Sep 17 00:00:00 2001 From: yanok Date: Wed, 26 Apr 2023 16:13:22 +0000 Subject: [PATCH 510/607] Remove unused local variable PiperOrigin-RevId: 527284156 --- pkgs/mockito/lib/src/builder.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 222894113..45866fc83 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -518,7 +518,6 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'Mockito cannot mock `dynamic`'); } - final typeToMockElement = typeToMock.element; var type = _determineDartType(typeToMock, entryLib.typeProvider); if (type.alias == null) { // For a generic class without an alias like `Foo` or From 416b9fbf05c5c5bccec4c41c0574eff8949f4fe5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 22:48:01 +0000 Subject: [PATCH 511/607] Bump actions/checkout from 3.5.0 to 3.5.2 (dart-lang/mockito#626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2.
Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

v2.3.1

v2.3.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.0&new-version=3.5.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index a08661069..ab1e73f67 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: matrix: sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -47,7 +47,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -72,7 +72,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: dev From 3b93edaa3032ab02639aad2fd1f309b8da67087d Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 2 May 2023 06:38:58 -0700 Subject: [PATCH 512/607] Import `packge:matcher` directly instead of `test_api` The export of `matcher` apis will be removed in the next major version of `test_api`. Expand the pub constraint for `test_api` to allow the next major version. PiperOrigin-RevId: 528766649 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/mock.dart | 3 +-- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a6efc1b40..bbb99f67f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.1-dev +## 5.4.1-wip * Deprecate the `mixingIn` argument to `MockSpec`. Best practice is to avoid any concrete implementation in classes which extend `Mock`. diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 58a9cdab3..97bee48ae 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -19,13 +19,12 @@ import 'dart:async'; +import 'package:matcher/expect.dart'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; import 'package:mockito/src/invocation_matcher.dart'; // ignore: deprecated_member_use import 'package:test_api/fake.dart'; -// ignore: deprecated_member_use -import 'package:test_api/test_api.dart'; /// Whether a [when] call is "in progress." /// diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index cac536c33..f346f259a 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.1-dev'; +const packageVersion = '5.4.1-wip'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 7fbabe6ef..dbecddc46 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.1-dev +version: 5.4.1-wip description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -18,7 +18,7 @@ dependencies: meta: ^1.3.0 path: ^1.8.0 source_gen: '>=0.9.6 <2.0.0' - test_api: '>=0.2.1 <0.6.0' + test_api: '>=0.2.1 <0.7.0' dev_dependencies: build_runner: ^2.0.0 From d07932aa463ba755c7ee6aeb97a16c905c9beb53 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Rico Date: Sun, 15 Jan 2023 12:21:06 -0500 Subject: [PATCH 513/607] Add `returnInOrder` to `Mock` Add more validations Clean test Cleanup Cleanup Remove extra lines Fix linter Add docs Cleanup comments Format Fix changelog possition --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/README.md | 6 ++++ pkgs/mockito/lib/src/mock.dart | 46 ++++++++++++++++++++++++----- pkgs/mockito/test/mockito_test.dart | 23 +++++++++++++++ 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a6efc1b40..4a43bdc00 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,6 +6,7 @@ * Require Dart >= 2.19.0. * Require analyzer 5.11.0. * Fail to generate mocks of `sealed`/`base`/`final` classes. +* Add new `thenReturnInOrder` method to mock multiple calls to a single method in order. ## 5.4.0 diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a658dbb24..9ed48d4b4 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -103,6 +103,12 @@ var responses = ["Purr", "Meow"]; when(cat.sound()).thenAnswer((_) => responses.removeAt(0)); expect(cat.sound(), "Purr"); expect(cat.sound(), "Meow"); + +// We can stub a method with multiple calls that happened in a particular order. +when(cat.sound()).thenReturnInOrder(["Purr", "Meow"]); +expect(cat.sound(), "Purr"); +expect(cat.sound(), "Meow"); +expect(() => cat.sound(), throwsA(isA())); ``` The [`when`], [`thenReturn`], [`thenAnswer`], and [`thenThrow`] APIs provide a diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index 58a9cdab3..2661bd7d4 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -18,6 +18,7 @@ // ignore_for_file: prefer_void_to_null import 'dart:async'; +import 'dart:collection'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; @@ -501,17 +502,35 @@ class PostExpectation { /// Note: [expected] cannot be a Future or Stream, due to Zone considerations. /// To return a Future or Stream from a method stub, use [thenAnswer]. void thenReturn(T expected) { - if (expected is Future) { - throw ArgumentError('`thenReturn` should not be used to return a Future. ' - 'Instead, use `thenAnswer((_) => future)`.'); - } - if (expected is Stream) { - throw ArgumentError('`thenReturn` should not be used to return a Stream. ' - 'Instead, use `thenAnswer((_) => stream)`.'); - } + _throwIfInvalid(expected); return _completeWhen((_) => expected); } + /// Store a sequence of canned responses for this method stub. + /// + /// Note: [expects] cannot contain a Future or Stream, due to Zone considerations. + /// To return a Future or Stream from a method stub, use [thenAnswer]. + /// + /// Note: when the method stub is called more times than there are responses + /// in [expects], it will throw an StateError in the missing case. + void thenReturnInOrder(List expects) { + if (expects.isEmpty) { + throw ArgumentError('thenReturnInOrder expects should not be empty'); + } + + expects.forEach(_throwIfInvalid); + + final answers = Queue.of(expects); + + thenAnswer((_) { + if (answers.isEmpty) { + throw StateError('thenReturnInOrder does not have enough answers'); + } + + return answers.removeFirst(); + }); + } + /// Store an exception to throw when this method stub is called. void thenThrow(Object throwable) { return _completeWhen((Invocation _) { @@ -536,6 +555,17 @@ class PostExpectation { _whenCall = null; _whenInProgress = false; } + + void _throwIfInvalid(T expected) { + if (expected is Future) { + throw ArgumentError('`thenReturn` should not be used to return a Future. ' + 'Instead, use `thenAnswer((_) => future)`.'); + } + if (expected is Stream) { + throw ArgumentError('`thenReturn` should not be used to return a Stream. ' + 'Instead, use `thenAnswer((_) => stream)`.'); + } + } } class InvocationMatcher { diff --git a/pkgs/mockito/test/mockito_test.dart b/pkgs/mockito/test/mockito_test.dart index b9072c3f8..964b88243 100644 --- a/pkgs/mockito/test/mockito_test.dart +++ b/pkgs/mockito/test/mockito_test.dart @@ -168,6 +168,24 @@ void main() { expect(mock.getter, equals('A')); }); + test('throws an exception if not enough answers were provided', () { + when(mock.methodWithNormalArgs(any)).thenReturnInOrder(['One', 'Two']); + + expect(mock.methodWithNormalArgs(100), equals('One')); + expect(mock.methodWithNormalArgs(100), equals('Two')); + + expect( + () => mock.methodWithNormalArgs(100), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('thenReturnInOrder does not have enough answers'), + ), + ), + ); + }); + test('should have hashCode when it is not mocked', () { expect(mock.hashCode, isNotNull); }); @@ -239,6 +257,11 @@ void main() { throwsArgumentError); }); + test('thenReturn throws if provided expects are empty for inOrder', () { + expect(() => when(mock.methodReturningStream()).thenReturnInOrder([]), + throwsArgumentError); + }); + test('thenAnswer supports stubbing method returning a Future', () async { when(mock.methodReturningFuture()) .thenAnswer((_) => Future.value('stub')); From fb26ace9839eb430c4222868af92aaca2e65c883 Mon Sep 17 00:00:00 2001 From: Googler Date: Mon, 15 May 2023 03:57:21 -0700 Subject: [PATCH 514/607] Fix for InvalidType PiperOrigin-RevId: 532062787 --- pkgs/mockito/lib/src/builder.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 45866fc83..131d69738 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -2046,6 +2046,9 @@ class _MockClassInfo { if (overrideVoid && type.isVoid) { return TypeReference((b) => b..symbol = 'dynamic'); } + if (type is analyzer.InvalidType) { + return TypeReference((b) => b..symbol = 'dynamic'); + } if (type is analyzer.InterfaceType) { return TypeReference((b) { b @@ -2238,6 +2241,8 @@ extension on analyzer.DartType { final self = this; if (self is analyzer.DynamicType) { return false; + } else if (self is analyzer.InvalidType) { + return false; } else if (self is analyzer.InterfaceType) { return self.element.isPrivate || self.typeArguments.any((t) => t.containsPrivateName); From b696fd9eee58d45e289dcfe12ad1c46a14c213b2 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 17 May 2023 01:35:41 -0700 Subject: [PATCH 515/607] Let users provide dummy values for types Mockito needs to create dummy values both during stubbing and while returning default values from nice mocks. Most cases are covered by the code generation, but there are some tricky cases where we either don't know the type at generation time or the required type can't be implemented, so Mockito cannot easily fake it. In this cases users could help Mockito by supplying it with the dummy values. This change adds two new API functions `provideDummy` and `provideDummyBuilder` to do that. Based on work in https://github.com/dart-lang/mockito/pull/592. I haven't yet removed the checks that fail the generation if a generic return type is found, so this change doesn't yet allow people to mock classes that have methods returning type variables. I plan to lift that restriction in the next change. So the only actually affected case is the return type being a function returning a type variable, previouly that would result in `() => null` and now it's `() => dummyValue()` instead. I also didn't yet change the handling of `Future`: it seems we rely heavily on the fakability of `Future`: if codegen sees `Future` return type, it just creates a `FakeFuture` for it. It couldn't be `awaited`, but works perfectly fine while stubbing, so I'm not convienced we want to change it to `Future.value(dummyValue())` which could fail at run-time, even if not awaited. PiperOrigin-RevId: 532712274 --- pkgs/mockito/CHANGELOG.md | 2 + pkgs/mockito/lib/mockito.dart | 4 +- pkgs/mockito/lib/src/builder.dart | 9 +- pkgs/mockito/lib/src/dummies.dart | 155 ++++++++++++++++++++++++++++++ pkgs/mockito/lib/src/mock.dart | 27 ++++++ 5 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 pkgs/mockito/lib/src/dummies.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a01f3a3f7..0f3a7fcb7 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -7,6 +7,8 @@ * Require analyzer 5.11.0. * Fail to generate mocks of `sealed`/`base`/`final` classes. * Add new `thenReturnInOrder` method to mock multiple calls to a single method in order. +* Add `provideDummy`/`provideDummyBuilder` functions for users to supply dummy + values to Mockito, to cover cases where codegen can't create one. ## 5.4.0 diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 77ca3d4f7..f716d6253 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -51,4 +51,6 @@ export 'src/mock.dart' logInvocations, untilCalled, MissingStubError, - FakeUsedError; + FakeUsedError, + FakeFunctionUsedError; +export 'src/dummies.dart' show provideDummy, provideDummyBuilder; diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 131d69738..b94c6897e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1474,8 +1474,13 @@ class _MockClassInfo { } if (type is! analyzer.InterfaceType) { - // TODO(srawlins): This case is not known. - return literalNull; + if (type.isBottom || type is analyzer.InvalidType) { + // Not sure what could be done here... + return literalNull; + } + // As a last resort, try looking for the correct value at run-time. + return referImported('dummyValue', 'package:mockito/src/dummies.dart') + .call([refer('this'), invocation], {}, [_typeReference(type)]); } final typeArguments = type.typeArguments; diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart new file mode 100644 index 000000000..9bf2dd0a5 --- /dev/null +++ b/pkgs/mockito/lib/src/dummies.dart @@ -0,0 +1,155 @@ +// Copyright 2023 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:async'; +import 'dart:typed_data'; +import 'mock.dart' show FakeFunctionUsedError; + +// TODO(yanok): try to change these to _unreasonable_ values, for example, +// String could totally contain an explanation. +const int _dummyInt = 0; +const double _dummyDouble = 0.0; +const String _dummyString = ''; + +// This covers functions with up to 20 positional arguments, for more arguments, +// type arguments or named arguments, users would have to provide a dummy +// explicitly. + +Never Function([ + Object? arg1, + Object? arg2, + Object? arg3, + Object? arg4, + Object? arg5, + Object? arg6, + Object? arg7, + Object? arg8, + Object? arg9, + Object? arg10, + Object? arg11, + Object? arg12, + Object? arg13, + Object? arg14, + Object? arg15, + Object? arg16, + Object? arg17, + Object? arg18, + Object? arg19, + Object? arg20, +]) _dummyFunction(Object parent, Invocation invocation) { + final stackTrace = StackTrace.current; + return ([ + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9, + arg10, + arg11, + arg12, + arg13, + arg14, + arg15, + arg16, + arg17, + arg18, + arg19, + arg20, + ]) => + throw FakeFunctionUsedError(invocation, parent, stackTrace); +} + +class MissingDummyValueError { + final Type type; + MissingDummyValueError(this.type); + @override + String toString() => ''' +MissingDummyValueError: $type + +This means Mockito was not smart enough to generate a dummy value of type +'$type'. Please consider using either 'provideDummy' or 'provideDummyBuilder' +functions to give Mockito a proper dummy value. + +Please note that due to implementation details Mockito sometimes needs users +to provide dummy values for some types, even if they plan to explicitly stub +all the called methods. +'''; +} + +typedef DummyBuilder = T Function(Object parent, Invocation invocation); + +Map _dummyBuilders = {}; + +Map _defaultDummyBuilders = { + int: (_, _i) => _dummyInt, + num: (_, _i) => _dummyInt, + double: (_, _i) => _dummyDouble, + String: (_, _i) => _dummyString, + Int8List: (_, _i) => Int8List(0), + Int16List: (_, _i) => Int16List(0), + Int32List: (_, _i) => Int32List(0), + Int64List: (_, _i) => Int64List(0), + Uint8List: (_, _i) => Uint8List(0), + Uint16List: (_, _i) => Uint16List(0), + Uint32List: (_, _i) => Uint32List(0), + Uint64List: (_, _i) => Uint64List(0), + Float32List: (_, _i) => Float32List(0), + Float64List: (_, _i) => Float64List(0), + ByteData: (_, _i) => ByteData(0), +}; + +List _defaultDummies = [ + // This covers functions without named or type arguments, with up to 20 + // positional arguments. For others users need to provide a dummy. + _dummyFunction, + // Core containers. This works great due to Dart's "everything is covariant" + // rule. + [], + {}, + {}, + Stream.empty(), +]; + +T dummyValue(Object parent, Invocation invocation) { + if (null is T) return null as T; + final dummyBuilder = _dummyBuilders[T] ?? _defaultDummyBuilders[T]; + if (dummyBuilder != null) { + return dummyBuilder(parent, invocation) as T; + } + for (var value in _defaultDummies) { + if (value is DummyBuilder) value = value(parent, invocation); + if (value is T) return value; + } + throw MissingDummyValueError(T); +} + +/// Provide a builder for that could create a dummy value of type `T`. +/// This could be useful for nice mocks, such that information about the +/// specific invocation that caused the creation of a dummy value could be +/// preserved. +void provideDummyBuilder(DummyBuilder dummyBuilder) => + _dummyBuilders[T] = dummyBuilder; + +/// Provide a dummy value for `T` to be used both while adding expectations +/// and as a default value for unstubbed methods, if using a nice mock. +void provideDummy(T dummy) => + provideDummyBuilder((parent, invocation) => dummy); + +void resetDummyBuilders() { + _dummyBuilders.clear(); +} diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index ca5427914..f9a8e2195 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -23,6 +23,7 @@ import 'dart:collection'; import 'package:matcher/expect.dart'; import 'package:meta/meta.dart'; import 'package:mockito/src/call_pair.dart'; +import 'package:mockito/src/dummies.dart' show resetDummyBuilders; import 'package:mockito/src/invocation_matcher.dart'; // ignore: deprecated_member_use import 'package:test_api/fake.dart'; @@ -288,6 +289,29 @@ class FakeUsedError extends Error { "${receiver.runtimeType}.$_memberName using Mockito's 'when' API.\n"; } +class FakeFunctionUsedError extends Error { + final Invocation parentInvocation; + final Object receiver; + final StackTrace createdStackTrace; + final String _memberName; + + FakeFunctionUsedError( + this.parentInvocation, this.receiver, this.createdStackTrace) + : _memberName = _symbolToString(parentInvocation.memberName); + + @override + String toString() => "FakeFunctionUsedError: '$_memberName'\n" + 'No stub was found which matches the argument of this method call:\n' + '${parentInvocation.toPrettyString()}\n\n' + 'A fake function was created for this call, in the hope that it ' + "won't be ever called.\n" + "Here is the stack trace where '$_memberName' was called:\n\n" + '${createdStackTrace.toString()}\n\n' + 'However, the fake function was called.\n' + 'Add a stub for ' + "${receiver.runtimeType}.$_memberName using Mockito's 'when' API.\n"; +} + /// An error which is thrown when no stub is found which matches the arguments /// of a real method call on a mock object. class MissingStubError extends Error { @@ -1231,6 +1255,8 @@ void logInvocations(List mocks) { /// /// In these cases, [resetMockitoState] might be called at the end of `setUp`, /// or in `tearDown`. +/// +/// This also clears all user-provided dummy builders. void resetMockitoState() { _whenInProgress = false; _untilCalledInProgress = false; @@ -1241,6 +1267,7 @@ void resetMockitoState() { _capturedArgs.clear(); _storedArgs.clear(); _storedNamedArgs.clear(); + resetDummyBuilders(); } extension on Invocation { From 2dd8cf5ec81cc545c38fc0ed2752759b6b1bd6a1 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 17 May 2023 15:00:14 +0200 Subject: [PATCH 516/607] Only check formatting with the stable SDK The code can only be formatted in one way. If there is a formatter change and stable/dev don't agree, we will always have one check failing. --- .../.github/workflows/test-package.yml | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index ab1e73f67..77c848a30 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -15,7 +15,23 @@ env: permissions: read-all jobs: - # Check code formatting and static analysis against stable and dev SDKs. + # Check code formatting with the stable SDK. + format: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + with: + sdk: 2.19.0 + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + + # Check static analysis against stable and dev SDKs. analyze: runs-on: ubuntu-latest strategy: @@ -30,8 +46,6 @@ jobs: - id: install name: Install dependencies run: dart pub get - - name: Check formatting - run: dart format --output=none --set-exit-if-changed . - name: Build generated artifacts run: dart pub run build_runner build - name: Analyze code From 9e20c401ad0a64a421dae161ad3c9d7a8d853720 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 19 May 2023 01:24:55 -0700 Subject: [PATCH 517/607] Extend using run-time dummy values to Futures If we have to create a `Future` of an unknown type, we have two alternatives: 1. Create a fake class implementing `Future`. This is guaranteed to fail if ever `await`ed, but works nice when setting expectations. 2. Try to find a value for `T` at run-time and create a real future. That can always fail if there is no dummy for `T`. This change tries to unite these approaches: - It adds `dummyValueOrNull` function that returns `null` instead of throwing. - If we need a value of type `Future`, first try if we have a dummy value for `T`. If it works, go with the real future, otherwise fall back to faking. PiperOrigin-RevId: 533386034 --- pkgs/mockito/lib/src/builder.dart | 17 +++++++++-- pkgs/mockito/lib/src/dummies.dart | 12 +++++++- .../mockito/test/builder/auto_mocks_test.dart | 2 +- pkgs/mockito/test/end2end/foo.dart | 1 + .../test/end2end/generated_mocks_test.dart | 29 ++++++++++++++++++- 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index b94c6897e..7ac65625c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1497,10 +1497,23 @@ class _MockClassInfo { if (typeArgument is analyzer.TypeParameterType && typeArgumentIsPotentiallyNonNullable) { // We cannot create a valid Future for this unknown, potentially - // non-nullable type, so we'll use a `_FakeFuture`, which will throw + // non-nullable type, so try creating a value at run-time and if + // that fails, we'll use a `_FakeFuture`, which will throw // if awaited. final futureType = typeProvider.futureType(typeArguments.first); - return _dummyValueImplementing(futureType, invocation); + return referImported('ifNotNull', 'package:mockito/src/dummies.dart') + .call([ + referImported('dummyValueOrNull', 'package:mockito/src/dummies.dart') + .call([refer('this'), invocation], {}, + [_typeReference(typeArgument)]), + Method((b) => b + ..requiredParameters.add(Parameter((p) => p + ..type = _typeReference(typeArgument) + ..name = 'v')) + ..body = _futureReference(_typeReference(typeArgument)) + .property('value') + .call([refer('v')]).code).closure + ]).ifNullThen(_dummyValueImplementing(futureType, invocation)); } else { // Create a real Future with a legal value, via [Future.value]. final futureValueArguments = typeArgumentIsPotentiallyNonNullable diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index 9bf2dd0a5..9c6d8ca31 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -125,7 +125,7 @@ List _defaultDummies = [ Stream.empty(), ]; -T dummyValue(Object parent, Invocation invocation) { +T? dummyValueOrNull(Object parent, Invocation invocation) { if (null is T) return null as T; final dummyBuilder = _dummyBuilders[T] ?? _defaultDummyBuilders[T]; if (dummyBuilder != null) { @@ -135,6 +135,12 @@ T dummyValue(Object parent, Invocation invocation) { if (value is DummyBuilder) value = value(parent, invocation); if (value is T) return value; } + return null; +} + +T dummyValue(Object parent, Invocation invocation) { + final value = dummyValueOrNull(parent, invocation); + if (value is T) return value; throw MissingDummyValueError(T); } @@ -153,3 +159,7 @@ void provideDummy(T dummy) => void resetDummyBuilders() { _dummyBuilders.clear(); } + +// Helper function. +R? ifNotNull(T? value, R Function(T) action) => + value != null ? action(value) : null; diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 12cccf12d..ff6e7130a 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2310,7 +2310,7 @@ void main() { Future m() async => false; } '''), - _containsAllOf('returnValue: _FakeFuture_0('), + _containsAllOf('dummyValueOrNull(', '_FakeFuture_0('), ); }); diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index e57b92531..6e8770a99 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -20,6 +20,7 @@ class Foo { void returnsVoid() {} Future returnsFutureVoid() => Future.value(); Future? returnsNullableFutureVoid() => Future.value(); + Future returnsFuture(T x) => Future.value(x); Bar returnsBar(int arg) => Bar(); } diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 62c9c7a92..623ddccd8 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -184,6 +184,11 @@ void main() { .having((e) => e.toString(), 'toString()', contains('getter'))), ); }); + + test('a method returning Future can be stubbed', () async { + when(foo.returnsFuture(any)).thenAnswer((_) async => 1); + expect(await foo.returnsFuture(0), 1); + }); }); group('for a generated mock using unsupportedMembers', () { @@ -278,7 +283,7 @@ void main() { }); group('for a generated nice mock', () { - late Foo foo; + late Foo foo; setUp(() { foo = MockFooNice(); @@ -315,6 +320,28 @@ void main() { throwsA(isA().having( (e) => e.toString(), 'toString()', contains('returnsBar(43)')))); }); + group('a method returning Future', () { + final bar = Bar(); + tearDown(() { + resetMockitoState(); + }); + test('returns a fake future if unstubbed', () { + expect(foo.returnsFuture(bar), isA()); + }); + test('returned fake future cannot be awaited', () async { + try { + await foo.returnsFuture(bar); + // Expect it to throw. + expect('This code should not be reached', false); + } catch (e) { + expect(e, isA()); + } + }); + test('with provideDummy returned value can be awaited', () async { + provideDummy(bar); + expect(await foo.returnsFuture(MockBar()), bar); + }); + }); }); test('a generated mock can be used as a stub argument', () { From dddd9ba2ac9988526293f40081fbdf58d11d913a Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Thu, 18 May 2023 12:21:28 -0700 Subject: [PATCH 518/607] blast_repo fixes dependabot --- pkgs/mockito/.github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/mockito/.github/dependabot.yml b/pkgs/mockito/.github/dependabot.yml index cf8e11086..90dffc509 100644 --- a/pkgs/mockito/.github/dependabot.yml +++ b/pkgs/mockito/.github/dependabot.yml @@ -7,3 +7,5 @@ updates: directory: / schedule: interval: monthly + labels: + - autosubmit From f007ae9df8c6810664f15dfc16071dfa95aa9c6a Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Mon, 22 May 2023 09:25:23 -0700 Subject: [PATCH 519/607] Don't abort codegen on return type being a type variable Now we can continue in this case, hoping that the needed value will be found at run time. And if not, user will get a message, saying for which type they have to provide a dummy value. Tests are extended to cover the new behavior, where possible. I had to delete tests for fallback generators, as there is nothing much to test. Fallback generators are now not required for return type being a type variable. And for private return type they can't be used due to a bug in codegen: we are trying to cast to a private type, that is not visible in the generated library. PiperOrigin-RevId: 534088653 --- pkgs/mockito/CHANGELOG.md | 2 + pkgs/mockito/lib/mockito.dart | 3 +- pkgs/mockito/lib/src/builder.dart | 34 ++------- .../mockito/test/builder/auto_mocks_test.dart | 60 +++------------ .../test/builder/custom_mocks_test.dart | 29 ------- pkgs/mockito/test/end2end/foo.dart | 9 ++- .../test/end2end/generated_mocks_test.dart | 75 ++++++++----------- 7 files changed, 62 insertions(+), 150 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 0f3a7fcb7..a16e59624 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -9,6 +9,8 @@ * Add new `thenReturnInOrder` method to mock multiple calls to a single method in order. * Add `provideDummy`/`provideDummyBuilder` functions for users to supply dummy values to Mockito, to cover cases where codegen can't create one. +* Allow generating mocks for classes with methods/getters returning non-nullable + unknown types. ## 5.4.0 diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index f716d6253..6febba3e6 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -53,4 +53,5 @@ export 'src/mock.dart' MissingStubError, FakeUsedError, FakeFunctionUsedError; -export 'src/dummies.dart' show provideDummy, provideDummyBuilder; +export 'src/dummies.dart' + show provideDummy, provideDummyBuilder, MissingDummyValueError; diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7ac65625c..feb847afb 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -832,9 +832,6 @@ class _MockTargetGatherer { /// A method is not valid for stubbing if: /// - It has a private type anywhere in its signature; Mockito cannot override /// such a method. - /// - It has a non-nullable type variable return type, for example `T m()`, - /// and no corresponding dummy generator. Mockito cannot generate its own - /// dummy return values for unknown types. void _checkMethodsToStubAreValid(_MockTarget mockTarget) { final interfaceElement = mockTarget.interfaceElement; final className = interfaceElement.name; @@ -921,16 +918,6 @@ class _MockTargetGatherer { errorMessages.addAll(_checkFunction(returnType, enclosingElement, allowUnsupportedMember: allowUnsupportedMember, hasDummyGenerator: hasDummyGenerator)); - } else if (returnType is analyzer.TypeParameterType) { - if (!isParameter && - !allowUnsupportedMember && - !hasDummyGenerator && - _entryLib.typeSystem.isPotentiallyNonNullable(returnType)) { - errorMessages - .add('${enclosingElement.fullName} features a non-nullable unknown ' - 'return type, and cannot be stubbed. ' - '$_tryUnsupportedMembersMessage'); - } } for (final parameter in function.parameters) { @@ -1369,25 +1356,19 @@ class _MockClassInfo { return; } - final returnTypeIsTypeVariable = - typeSystem.isPotentiallyNonNullable(returnType) && - returnType is analyzer.TypeParameterType; final fallbackGenerator = fallbackGenerators[method.name]; final parametersContainPrivateName = method.parameters.any((p) => p.type.containsPrivateName); final throwsUnsupported = fallbackGenerator == null && - (returnTypeIsTypeVariable || - returnType.containsPrivateName || - parametersContainPrivateName); + (returnType.containsPrivateName || parametersContainPrivateName); if (throwsUnsupported) { if (!mockTarget.unsupportedMembers.contains(name)) { // We shouldn't get here as this is guarded against in // [_MockTargetGatherer._checkFunction]. throw InvalidMockitoAnnotationException( - "Mockito cannot generate a valid override for '$name', as it has a " - 'non-nullable unknown return type or a private type in its ' - 'signature.'); + "Mockito cannot generate a valid override for '$name', as it has " + 'a private type in its signature.'); } builder.body = refer('UnsupportedError') .call([ @@ -1894,18 +1875,15 @@ class _MockClassInfo { final returnType = getter.returnType; final fallbackGenerator = fallbackGenerators[getter.name]; - final returnTypeIsTypeVariable = - typeSystem.isPotentiallyNonNullable(returnType) && - returnType is analyzer.TypeParameterType; - final throwsUnsupported = fallbackGenerator == null && - (returnTypeIsTypeVariable || getter.returnType.containsPrivateName); + final throwsUnsupported = + fallbackGenerator == null && (getter.returnType.containsPrivateName); if (throwsUnsupported) { if (!mockTarget.unsupportedMembers.contains(getter.name)) { // We shouldn't get here as this is guarded against in // [_MockTargetGatherer._checkFunction]. throw InvalidMockitoAnnotationException( "Mockito cannot generate a valid override for '${getter.name}', as " - 'it has a non-nullable unknown type or a private type.'); + 'it has a private type.'); } builder.body = refer('UnsupportedError') .call([ diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index ff6e7130a..01fea665a 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3037,80 +3037,44 @@ void main() { }); test( - 'throws when GenerateMocks is given a class with a getter with a ' + "calls 'dummyValue' for a getter with a " 'non-nullable class-declared type variable type', () async { - _expectBuilderThrows( - assets: { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(''' + await expectSingleNonNullableOutput(dedent(''' abstract class Foo { T get f; } - '''), - }, - message: contains( - "The property accessor 'Foo.f' features a non-nullable unknown " - 'return type, and cannot be stubbed'), - ); + '''), _containsAllOf('dummyValue(')); }); test( - 'throws when GenerateMocks is given a class with a method with a ' + "calls 'dummyValue' for a method with a " 'non-nullable class-declared type variable return type', () async { - _expectBuilderThrows( - assets: { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(''' + await expectSingleNonNullableOutput(dedent(''' abstract class Foo { T m(int a); } - '''), - }, - message: contains( - "The method 'Foo.m' features a non-nullable unknown return type, and " - 'cannot be stubbed'), - ); + '''), _containsAllOf('dummyValue(')); }); test( - 'throws when GenerateMocks is given a class with a method with a ' + "calls 'dummyValue' for a method with a " 'non-nullable method-declared type variable return type', () async { - _expectBuilderThrows( - assets: { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(''' + await expectSingleNonNullableOutput(dedent(''' abstract class Foo { T m(int a); } - '''), - }, - message: contains( - "The method 'Foo.m' features a non-nullable unknown return type, and " - 'cannot be stubbed'), - ); + '''), _containsAllOf('dummyValue(')); }); test( - 'throws when GenerateMocks is given a class with a method with a ' + "calls 'dummyValue' for a method with a " 'non-nullable method-declared bounded type variable return type', () async { - _expectBuilderThrows( - assets: { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(''' + await expectSingleNonNullableOutput(dedent(''' abstract class Foo { T m(int a); } - '''), - }, - message: contains( - "The method 'Foo.m' features a non-nullable unknown return type, and " - 'cannot be stubbed'), - ); + '''), _containsAllOf('dummyValue(')); }); test('throws when GenerateMocks is missing an argument', () async { diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 42e735973..1e69c4135 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -456,35 +456,6 @@ void main() { expect(mocksContent, isNot(contains('throwOnMissingStub'))); }); - test( - 'generates mock methods with non-nullable unknown types, given ' - 'unsupportedMembers', () async { - final mocksContent = await buildWithNonNullable({ - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - T m(T a); - } - '''), - 'foo|test/foo_test.dart': ''' - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - - @GenerateMocks( - [], - customMocks: [ - MockSpec(unsupportedMembers: {#m}), - ], - ) - void main() {} - ''' - }); - expect( - mocksContent, - contains(' T m(T? a) => throw UnsupportedError(\n' - ' r\'"m" cannot be used without a mockito fallback generator.\');')); - }); - test( 'generates mock methods with private return types, given ' 'unsupportedMembers', () async { diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 6e8770a99..d4e26caf6 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -29,13 +29,20 @@ class Bar { int f() => 0; } +class _Private {} + +final private = _Private(); + abstract class Baz { T returnsTypeVariable(); T returnsBoundedTypeVariable(); T returnsTypeVariableFromTwo(); S Function(S) returnsGenericFunction(); S get typeVariableField; - T $hasDollarInName(); + _Private returnsPrivate(); + _Private get privateTypeField; + void privateArg(_Private arg); + _Private $hasDollarInName(); } class HasPrivate { diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 623ddccd8..64573f436 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -6,18 +6,6 @@ import 'foo.dart'; import 'foo_sub.dart'; import 'generated_mocks_test.mocks.dart'; -T returnsTypeVariableShim() => [1, 1.5].whereType().first!; - -T returnsBoundedTypeVariableShim() => - [1, 1.5].whereType().first!; - -T returnsTypeVariableFromTwoShim() => [1, 1.5].whereType().first!; - -T typeVariableFieldShim() => - throw UnsupportedError('typeVariableField cannot be used'); - -T Function(T) returnsGenericFunctionShim() => (T _) => null as T; - @GenerateMocks([ Foo, FooSub, @@ -32,25 +20,12 @@ T Function(T) returnsGenericFunctionShim() => (T _) => null as T; MockSpec( as: #MockBazWithUnsupportedMembers, unsupportedMembers: { - #returnsTypeVariable, - #returnsBoundedTypeVariable, - #returnsTypeVariableFromTwo, - #returnsGenericFunction, - #typeVariableField, + #returnsPrivate, + #privateArg, + #privateTypeField, #$hasDollarInName, }, ), - MockSpec( - as: #MockBazWithFallbackGenerators, - fallbackGenerators: { - #returnsTypeVariable: returnsTypeVariableShim, - #returnsBoundedTypeVariable: returnsBoundedTypeVariableShim, - #returnsTypeVariableFromTwo: returnsTypeVariableFromTwoShim, - #returnsGenericFunction: returnsGenericFunctionShim, - #typeVariableField: typeVariableFieldShim, - #$hasDollarInName: returnsTypeVariableShim, - }, - ), MockSpec(mixingIn: [HasPrivateMixin]), ]) @GenerateNiceMocks([MockSpec(as: #MockFooNice)]) @@ -192,31 +167,29 @@ void main() { }); group('for a generated mock using unsupportedMembers', () { - late Baz baz; + late MockBazWithUnsupportedMembers baz; setUp(() { - baz = MockBazWithUnsupportedMembers(); + baz = MockBazWithUnsupportedMembers(); + }); + + tearDown(() => resetMockitoState()); + + test('a real method call that returns private type throws', () { + expect(() => baz.returnsPrivate(), throwsUnsupportedError); }); - test('a real method call throws', () { - expect(() => baz.returnsTypeVariable(), throwsUnsupportedError); + test('a real method call that accepts private type throws', () { + expect(() => baz.privateArg(private), throwsUnsupportedError); }); test('a real getter call (or field access) throws', () { - expect(() => baz.typeVariableField, throwsUnsupportedError); + expect(() => baz.privateTypeField, throwsUnsupportedError); }); test('a real call to a method whose name has a \$ in it throws', () { expect(() => baz.$hasDollarInName(), throwsUnsupportedError); }); - }); - - group('for a generated mock using fallbackGenerators,', () { - late Baz baz; - - setUp(() { - baz = MockBazWithFallbackGenerators(); - }); test('a method with a type variable return type can be called', () { when(baz.returnsTypeVariable()).thenReturn(3); @@ -231,8 +204,24 @@ void main() { test( 'a method with multiple type parameters and a type variable return ' 'type can be called', () { - when(baz.returnsTypeVariable()).thenReturn(3); - baz.returnsTypeVariable(); + when(baz.returnsTypeVariableFromTwo()).thenReturn(3); + baz.returnsTypeVariableFromTwo(); + }); + + test( + 'a getter with a type variable return type throws if there is no ' + 'dummy value', () { + expect(() => when(baz.typeVariableField).thenReturn(Bar()), + throwsA(isA())); + }); + + test( + 'a getter with a type variable return type can be called if dummy ' + 'value was provided', () { + provideDummy(Bar()); + final bar = Bar(); + when(baz.typeVariableField).thenReturn(bar); + expect(baz.typeVariableField, bar); }); }); From 65263566af30a5dcd4996ff02d4f1cf75f3f59d7 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 22 May 2023 16:31:43 -0700 Subject: [PATCH 520/607] Prepare to publish Drop `-wip` version suffix. Reflow changelog entry over 80 columns. Constrain `matcher` to version which has `expect` library. PiperOrigin-RevId: 534221348 --- pkgs/mockito/CHANGELOG.md | 5 +++-- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index a16e59624..4ccec2c1a 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.1-wip +## 5.4.1 * Deprecate the `mixingIn` argument to `MockSpec`. Best practice is to avoid any concrete implementation in classes which extend `Mock`. @@ -6,7 +6,8 @@ * Require Dart >= 2.19.0. * Require analyzer 5.11.0. * Fail to generate mocks of `sealed`/`base`/`final` classes. -* Add new `thenReturnInOrder` method to mock multiple calls to a single method in order. +* Add new `thenReturnInOrder` method to mock multiple calls to a single method + in order. * Add `provideDummy`/`provideDummyBuilder` functions for users to supply dummy values to Mockito, to cover cases where codegen can't create one. * Allow generating mocks for classes with methods/getters returning non-nullable diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index f346f259a..03bb748c0 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.1-wip'; +const packageVersion = '5.4.1'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index dbecddc46..00932ec61 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.1-wip +version: 5.4.1 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -14,7 +14,7 @@ dependencies: code_builder: ^4.2.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' - matcher: ^0.12.10 + matcher: ^0.12.15 meta: ^1.3.0 path: ^1.8.0 source_gen: '>=0.9.6 <2.0.0' From 73baf66e49cac72994286082eb16a3136b45571f Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Tue, 23 May 2023 01:25:17 -0700 Subject: [PATCH 521/607] Don't try to fake unfakable classes We have to rely on runtime dummy value lookup instead. Also adds a dummy value for SDK's `ProcessResult`, `SplayTreeMap`, `SplayTreeSet` and `Isolate` classes, which are `final`. I guess we want to cover all final SDK classes eventually. Also bump the version used in the generator tests to 3.0. #dart3 PiperOrigin-RevId: 534338463 --- pkgs/mockito/lib/src/builder.dart | 56 ++++++++++++++----- pkgs/mockito/lib/src/dummies.dart | 6 ++ pkgs/mockito/lib/src/platform_dummies_js.dart | 17 ++++++ pkgs/mockito/lib/src/platform_dummies_vm.dart | 29 ++++++++++ .../mockito/test/builder/auto_mocks_test.dart | 47 +++++++++++----- .../test/builder/custom_mocks_test.dart | 2 +- 6 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 pkgs/mockito/lib/src/platform_dummies_js.dart create mode 100644 pkgs/mockito/lib/src/platform_dummies_vm.dart diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index feb847afb..f903b4cbc 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -124,8 +124,7 @@ class MockBuilder implements Builder { // The source lib may be pre-null-safety because of an explicit opt-out // (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To // allow for this situation, we must also add an opt-out comment here. - final dartVersionComment = - sourceLibIsNonNullable ? '// @dart=2.19' : '// @dart=2.9'; + final dartVersionComment = sourceLibIsNonNullable ? '' : '// @dart=2.9'; final mockLibraryContent = DartFormatter().format(''' // Mocks generated by Mockito $packageVersion from annotations // in ${entryLib.definingCompilationUnit.source.uri.path}. @@ -1444,6 +1443,11 @@ class _MockClassInfo { ]); } + Expression _dummyValueFallbackToRuntime( + analyzer.DartType type, Expression invocation) => + referImported('dummyValue', 'package:mockito/src/dummies.dart') + .call([refer('this'), invocation], {}, [_typeReference(type)]); + Expression _dummyValue(analyzer.DartType type, Expression invocation) { // The type is nullable, just take a shortcut and return `null`. if (typeSystem.isNullable(type)) { @@ -1460,8 +1464,7 @@ class _MockClassInfo { return literalNull; } // As a last resort, try looking for the correct value at run-time. - return referImported('dummyValue', 'package:mockito/src/dummies.dart') - .call([refer('this'), invocation], {}, [_typeReference(type)]); + return _dummyValueFallbackToRuntime(type, invocation); } final typeArguments = type.typeArguments; @@ -1599,24 +1602,47 @@ class _MockClassInfo { }).genericClosure; } + Expression _dummyFakedValue( + analyzer.InterfaceType dartType, Expression invocation) { + final elementToFake = dartType.element; + final fakeName = mockLibraryInfo._fakeNameFor(elementToFake); + // Only make one fake class for each class that needs to be faked. + if (!mockLibraryInfo.fakedInterfaceElements.contains(elementToFake)) { + _addFakeClass(fakeName, elementToFake); + } + final typeArguments = dartType.typeArguments; + return TypeReference((b) { + b + ..symbol = fakeName + ..types.addAll(typeArguments.map(_typeReference)); + }).newInstance([refer('this'), invocation]); + } + Expression _dummyValueImplementing( analyzer.InterfaceType dartType, Expression invocation) { final elementToFake = dartType.element; if (elementToFake is EnumElement) { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); + } else if (elementToFake is ClassElement) { + if (elementToFake.isBase || + elementToFake.isFinal || + elementToFake.isSealed) { + // This class can't be faked, so try to call `dummyValue` to get + // a dummy value at run time. + // TODO(yanok): Consider checking subtypes, maybe some of them are + // implementable. + return _dummyValueFallbackToRuntime(dartType, invocation); + } + return _dummyFakedValue(dartType, invocation); + } else if (elementToFake is MixinElement) { + // This is a mixin and not a class. This should not happen in Dart 3, + // since it is not possible to have a value of mixin type. But we + // have to support this for reverse comptatibility. + return _dummyFakedValue(dartType, invocation); } else { - final fakeName = mockLibraryInfo._fakeNameFor(elementToFake); - // Only make one fake class for each class that needs to be faked. - if (!mockLibraryInfo.fakedInterfaceElements.contains(elementToFake)) { - _addFakeClass(fakeName, elementToFake); - } - final typeArguments = dartType.typeArguments; - return TypeReference((b) { - b - ..symbol = fakeName - ..types.addAll(typeArguments.map(_typeReference)); - }).newInstance([refer('this'), invocation]); + throw StateError("Interface type '$dartType' which is nether an enum, " + 'nor a class, nor a mixin. This case is unknown, please report a bug.'); } } diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index 9c6d8ca31..8bd6b85fb 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -13,8 +13,11 @@ // limitations under the License. import 'dart:async'; +import 'dart:collection'; import 'dart:typed_data'; import 'mock.dart' show FakeFunctionUsedError; +import 'platform_dummies_js.dart' + if (dart.library.io) 'platform_dummies_vm.dart'; // TODO(yanok): try to change these to _unreasonable_ values, for example, // String could totally contain an explanation. @@ -111,6 +114,7 @@ Map _defaultDummyBuilders = { Float32List: (_, _i) => Float32List(0), Float64List: (_, _i) => Float64List(0), ByteData: (_, _i) => ByteData(0), + ...platformDummies, }; List _defaultDummies = [ @@ -123,6 +127,8 @@ List _defaultDummies = [ {}, {}, Stream.empty(), + SplayTreeSet(), + SplayTreeMap(), ]; T? dummyValueOrNull(Object parent, Invocation invocation) { diff --git a/pkgs/mockito/lib/src/platform_dummies_js.dart b/pkgs/mockito/lib/src/platform_dummies_js.dart new file mode 100644 index 000000000..ebe8c38fc --- /dev/null +++ b/pkgs/mockito/lib/src/platform_dummies_js.dart @@ -0,0 +1,17 @@ +// Copyright 2023 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dummies.dart' show DummyBuilder; + +Map platformDummies = {}; diff --git a/pkgs/mockito/lib/src/platform_dummies_vm.dart b/pkgs/mockito/lib/src/platform_dummies_vm.dart new file mode 100644 index 000000000..d8eb14cac --- /dev/null +++ b/pkgs/mockito/lib/src/platform_dummies_vm.dart @@ -0,0 +1,29 @@ +// Copyright 2023 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:io'; +import 'dart:isolate'; +import 'dummies.dart' show DummyBuilder; +import 'mock.dart' show SmartFake; + +class FakeSendPort extends SmartFake implements SendPort { + FakeSendPort(super.parent, super.invocation); +} + +Map platformDummies = { + ProcessResult: (parent, invocation) => ProcessResult(0, 0, '', ''' +dummy ProcessResult created for a call to $parent.${invocation.memberName}'''), + // We can't fake `Isolate`, but we can fake `SendPort`, so use it. + Isolate: (parent, invocation) => Isolate(FakeSendPort(parent, invocation)), +}; diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 01fea665a..0ba321485 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -102,14 +102,10 @@ void main() { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 13)) + languageVersion: LanguageVersion(3, 0)) ]); - await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), sourceAssets, - writer: writer, outputs: outputs, packageConfig: packageConfig), - ['nonfunction-type-aliases'], - ); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, outputs: outputs, packageConfig: packageConfig); } /// Builds with [MockBuilder] in a package which has opted into null safety, @@ -118,15 +114,11 @@ void main() { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 13)) + languageVersion: LanguageVersion(3, 0)) ]); - await withEnabledExperiments( - () async => await testBuilder( - buildMocks(BuilderOptions({})), sourceAssets, - writer: writer, packageConfig: packageConfig), - ['nonfunction-type-aliases'], - ); + await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + writer: writer, packageConfig: packageConfig); final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart'); return utf8.decode(writer.assets[mocksAsset]!); } @@ -2730,6 +2722,33 @@ void main() { hasLength(1)); }); + test('does not try to generate a fake for a final class', () async { + await expectSingleNonNullableOutput(dedent(r''' + class Foo { + Bar m1() => Bar(); + } + final class Bar {} + '''), _containsAllOf('dummyValue<_i2.Bar>(')); + }); + + test('does not try to generate a fake for a base class', () async { + await expectSingleNonNullableOutput(dedent(r''' + class Foo { + Bar m1() => Bar(); + } + base class Bar {} + '''), _containsAllOf('dummyValue<_i2.Bar>(')); + }); + + test('does not try to generate a fake for a sealed class', () async { + await expectSingleNonNullableOutput(dedent(r''' + class Foo { + Bar m1() => Bar(); + } + sealed class Bar {} + '''), _containsAllOf('dummyValue<_i2.Bar>(')); + }); + test('throws when GenerateMocks is given a class multiple times', () async { _expectBuilderThrows( assets: { diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 1e69c4135..eb890d5f3 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -110,7 +110,7 @@ void main() { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 15)) + languageVersion: LanguageVersion(3, 0)) ]); await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); From cdc3dfdb37210e0e918c1868e85ca9dbcc48d4ef Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Wed, 24 May 2023 04:41:05 -0700 Subject: [PATCH 522/607] Fix violations of `prefer_final_locals`, `prefer_final_in_for_each` lints PiperOrigin-RevId: 534783619 --- pkgs/mockito/lib/src/builder.dart | 12 ++++++------ pkgs/mockito/lib/src/mock.dart | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index f903b4cbc..7dceb7387 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1438,7 +1438,7 @@ class _MockClassInfo { final functionReference = referImported(function.name, _typeImport(function)); return functionReference.call(positionalArguments, namedArguments, [ - for (var t in method.typeParameters) + for (final t in method.typeParameters) _typeParameterReference(t, withBound: false) ]); } @@ -1815,18 +1815,18 @@ class _MockClassInfo { return literalString(constant.stringValue, raw: true); } else if (constant.isList) { return literalConstList([ - for (var element in constant.listValue) + for (final element in constant.listValue) _expressionFromDartObject(element) ]); } else if (constant.isMap) { return literalConstMap({ - for (var pair in constant.mapValue.entries) + for (final pair in constant.mapValue.entries) _expressionFromDartObject(pair.key!): _expressionFromDartObject(pair.value!) }); } else if (constant.isSet) { return literalConstSet({ - for (var element in constant.setValue) + for (final element in constant.setValue) _expressionFromDartObject(element) }); } else if (constant.isType) { @@ -1860,11 +1860,11 @@ class _MockClassInfo { final name = revivable.source.fragment; final positionalArgs = [ - for (var argument in revivable.positionalArguments) + for (final argument in revivable.positionalArguments) _expressionFromDartObject(argument) ]; final namedArgs = { - for (var pair in revivable.namedArguments.entries) + for (final pair in revivable.namedArguments.entries) pair.key: _expressionFromDartObject(pair.value) }; final element = parameter != null && name != object.type!.element!.name diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index f9a8e2195..f988cf10a 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -769,7 +769,7 @@ class _VerifyCall { } final matchingCapturedArgs = [ - for (var invocation in matchingInvocations) ...invocation.capturedArgs, + for (final invocation in matchingInvocations) ...invocation.capturedArgs, ]; return _VerifyCall._( From b7b51d5fb6ba61649f1d2be82cf56ad50ad1cfa8 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 25 May 2023 16:01:22 -0700 Subject: [PATCH 523/607] Expand constraint on package:http Allow the 1.0.0 version which does not have any breaking changes. PiperOrigin-RevId: 535418668 --- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 03bb748c0..3a8fd6575 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.1'; +const packageVersion = '5.4.2-wip'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 00932ec61..06e2fbd0a 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.1 +version: 5.4.2-wip description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -24,7 +24,7 @@ dev_dependencies: build_runner: ^2.0.0 build_test: ^2.0.0 build_web_compilers: '>=3.0.0 <5.0.0' - http: ^0.13.0 + http: '>=0.13.0 <2.0.0' lints: ^2.0.0 package_config: '>=1.9.3 <3.0.0' test: ^1.16.0 From 60bb31ce47107b178337557380f89f18351899d0 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 1 Jun 2023 01:37:10 -0700 Subject: [PATCH 524/607] Add support for records This adds support for both emitting record types correctly (fixes https://github.com/dart-lang/mockito/issues/636) and for creating dummy record values. PiperOrigin-RevId: 536951169 --- pkgs/mockito/CHANGELOG.md | 5 +++ pkgs/mockito/lib/src/builder.dart | 33 +++++++++++++++++++ pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 22 +++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 4ccec2c1a..f38a34133 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.4.2-wip + +* Require code_builder 4.5.0. +* Add support for records. + ## 5.4.1 * Deprecate the `mixingIn` argument to `MockSpec`. Best practice is to avoid diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7dceb7387..288e7de06 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -350,6 +350,13 @@ class _TypeVisitor extends RecursiveElementVisitor { if (aliasElement != null) { _elements.add(aliasElement); } + } else if (type is analyzer.RecordType) { + for (final f in type.positionalFields) { + _addType(f.type); + } + for (final f in type.namedFields) { + _addType(f.type); + } } } @@ -1458,6 +1465,10 @@ class _MockClassInfo { return _dummyFunctionValue(type, invocation); } + if (type is analyzer.RecordType) { + return _dummyRecordValue(type, invocation); + } + if (type is! analyzer.InterfaceType) { if (type.isBottom || type is analyzer.InvalidType) { // Not sure what could be done here... @@ -1602,6 +1613,18 @@ class _MockClassInfo { }).genericClosure; } + Expression _dummyRecordValue( + analyzer.RecordType type, Expression invocation) => + literalRecord( + [ + for (final f in type.positionalFields) _dummyValue(f.type, invocation) + ], + { + for (final f in type.namedFields) + f.name: _dummyValue(f.type, invocation) + }, + ); + Expression _dummyFakedValue( analyzer.InterfaceType dartType, Expression invocation) { final elementToFake = dartType.element; @@ -2124,6 +2147,13 @@ class _MockClassInfo { ..symbol = _lookupTypeParameter(type.element) ..isNullable = forceNullable || typeSystem.isNullable(type); }); + } else if (type is analyzer.RecordType) { + return RecordType((b) => b + ..positionalFieldTypes.addAll( + [for (final f in type.positionalFields) _typeReference(f.type)]) + ..namedFieldTypes.addAll( + {for (final f in type.namedFields) f.name: _typeReference(f.type)}) + ..isNullable = forceNullable || typeSystem.isNullable(type)); } else { return referImported( type.getDisplayString(withNullability: false), @@ -2277,6 +2307,9 @@ extension on analyzer.DartType { return false; } else if (self is analyzer.VoidType) { return false; + } else if (self is analyzer.RecordType) { + return self.positionalFields.any((f) => f.type.containsPrivateName) || + self.namedFields.any((f) => f.type.containsPrivateName); } else { assert(false, 'Unexpected subtype of DartType: ${self.runtimeType}'); return false; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 06e2fbd0a..1bd2fbd4e 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: analyzer: '>=5.11.0 <6.0.0' build: '>=1.3.0 <3.0.0' - code_builder: ^4.2.0 + code_builder: ^4.5.0 collection: ^1.15.0 dart_style: '>=1.3.6 <3.0.0' matcher: ^0.12.15 diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 0ba321485..18f4f0fad 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3609,6 +3609,28 @@ void main() { }); expect(mocksContent, contains('void m(dynamic x)')); }); + group('Record types', () { + test('are supported as arguments', () async { + await expectSingleNonNullableOutput(dedent(''' + abstract class Foo { + int m((int, {Foo foo}) a); + } + '''), _containsAllOf('int m((int, {_i2.Foo foo})? a)')); + }); + test('are supported as return types', () async { + await expectSingleNonNullableOutput( + dedent(''' + class Bar {} + abstract class Foo { + Future<(int, {Bar bar})> get v; + } + '''), + decodedMatches(allOf( + contains('Future<(int, {_i2.Bar bar})> get v'), + contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('), + contains('bar: _FakeBar_0(')))); + }); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From 673880f3e36e5daf922bd347254b899d5778340f Mon Sep 17 00:00:00 2001 From: Googler Date: Thu, 1 Jun 2023 10:57:58 -0700 Subject: [PATCH 525/607] Prepare for NamedType breaking change in the analyzer. https://dart-review.googlesource.com/c/sdk/+/303280 PiperOrigin-RevId: 537071619 --- pkgs/mockito/lib/src/builder.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 288e7de06..48cea6b60 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -564,7 +564,7 @@ class _MockTargetGatherer { final mockType = _mockType(mockSpecAsts[index]); final typeToMock = mockSpecType.typeArguments.single; if (typeToMock.isDynamic) { - final mockTypeName = mockType?.name.name; + final mockTypeName = mockType?.qualifiedName; if (mockTypeName == null) { throw InvalidMockitoAnnotationException( 'MockSpec requires a type argument to determine the class to mock. ' @@ -596,7 +596,7 @@ class _MockTargetGatherer { '${(typeArgIdx + 1).ordinal} type argument for mocked ' '$typeName.'); } - if (typeArgAst.name.name == 'dynamic') return; + if (typeArgAst.qualifiedName == 'dynamic') return; throw InvalidMockitoAnnotationException( 'Undefined type $typeArgAst passed as the ' '${(typeArgIdx + 1).ordinal} type argument for mocked $typeName. Are ' @@ -2387,3 +2387,13 @@ extension on ElementAnnotation { ast.Annotation get annotationAst => (this as ElementAnnotationImpl).annotationAst; } + +extension NamedTypeExtension on ast.NamedType { + String get qualifiedName { + final importPrefix = this.importPrefix; + if (importPrefix != null) { + return '${importPrefix.name.lexeme}.${name2.lexeme}'; + } + return name2.lexeme; + } +} From 7b0ddd8b8b2370d5872d69f1e05470e398d8cf6c Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 2 Jun 2023 03:31:02 -0700 Subject: [PATCH 526/607] Fix the unresolved types if used as ignored type-alias arguments Mockito tries to keep function type aliases unrolled if possible, so for example, for the code ```dart typedef Ignore = String Function(); abstract class C { void m(Ignore c); } ``` the generated mock will have this override: ```dart void m(_i1.Ignore? c) => /* ... */; ``` But the way `TypeVisitor` collects potentially used types, excludes ignored type alias arguments (non-ignored ones are visited while visiting the right hand side of the `typedef`). Make sure we visit all type alias arguments unconditionally. Fixes https://github.com/dart-lang/mockito/issues/642 PiperOrigin-RevId: 537266416 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/lib/src/builder.dart | 2 ++ pkgs/mockito/test/builder/auto_mocks_test.dart | 16 ++++++++++++++++ pkgs/mockito/test/builder/custom_mocks_test.dart | 6 +++++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index f38a34133..9b33d352f 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,7 @@ * Require code_builder 4.5.0. * Add support for records. +* Fixed the bug with missing imports for unused type alias arguments. ## 5.4.1 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 48cea6b60..56361c84c 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -307,6 +307,8 @@ class _TypeVisitor extends RecursiveElementVisitor { void _addType(analyzer.DartType? type) { if (type == null) return; + type.alias?.typeArguments.forEach(_addType); + if (type is analyzer.InterfaceType) { final alreadyVisitedElement = _elements.contains(type.element); _elements.add(type.element); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 18f4f0fad..836a699e9 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1245,6 +1245,22 @@ void main() { expect(mocksContent, contains('_i2.Callback3<_i2.Foo>? c')); }); + test('imports libraries for type aliases with external types 2', () async { + final mocksContent = await buildWithSingleNonNullableSource(dedent(r''' + import 'dart:async'; + typedef Ignore = String Function(int); + class Foo { + dynamic f(Ignore> c) {} + dynamic g(Ignore c) {} + } + ''')); + expect(mocksContent, contains("import 'package:foo/foo.dart' as _i2;")); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('implements _i2.Foo')); + expect(mocksContent, contains('_i2.Ignore<_i3.Future>? c')); + expect(mocksContent, contains('_i2.Ignore<_i3.Future>? c')); + }); + test('imports libraries for types declared in private SDK libraries', () async { final mocksContent = await buildWithSingleNonNullableSource(dedent(''' diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index eb890d5f3..4a673e9a2 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1353,8 +1353,10 @@ void main() { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' - class Foo { + class Bar {} + abstract class Foo { int m(); + Bar get f; } '''), 'foo|test/foo_test.dart': ''' @@ -1367,6 +1369,8 @@ void main() { expect(mocksContent, isNot(contains('throwOnMissingStub'))); expect(mocksContent, contains('returnValue: 0')); expect(mocksContent, contains('returnValueForMissingStub: 0')); + expect(mocksContent, contains('returnValue: _FakeBar_0(')); + expect(mocksContent, contains('returnValueForMissingStub: _FakeBar_0(')); }); test( From 5d809f8d78808817a75b8057f543fabbdf7b3512 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 9 Jun 2023 17:54:17 -0700 Subject: [PATCH 527/607] Prepare to publish Drop `-wip` suffix. Bump upper SDK bound to silence publish warning. PiperOrigin-RevId: 539219416 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9b33d352f..da207c6d3 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.2-wip +## 5.4.2 * Require code_builder 4.5.0. * Add support for records. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 3a8fd6575..d7b0fe544 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.2-wip'; +const packageVersion = '5.4.2'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 1bd2fbd4e..578ccf212 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,12 +1,12 @@ name: mockito -version: 5.4.2-wip +version: 5.4.2 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.19.0 <3.0.0' + sdk: '>=2.19.0 <4.0.0' dependencies: analyzer: '>=5.11.0 <6.0.0' From 2baccced0863fc8511044ff8a6d5d0f168beb02f Mon Sep 17 00:00:00 2001 From: Googler Date: Tue, 27 Jun 2023 06:14:10 -0700 Subject: [PATCH 528/607] Internal change PiperOrigin-RevId: 543722790 --- pkgs/mockito/lib/src/builder.dart | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 56361c84c..10afbf0ee 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -522,7 +522,8 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'The "classes" argument includes a non-type: $objectToMock'); } - if (typeToMock.isDynamic) { + if (typeToMock is analyzer.DynamicType || + typeToMock is analyzer.InvalidType) { throw InvalidMockitoAnnotationException( 'Mockito cannot mock `dynamic`'); } @@ -565,7 +566,8 @@ class _MockTargetGatherer { assert(mockSpecType.typeArguments.length == 1); final mockType = _mockType(mockSpecAsts[index]); final typeToMock = mockSpecType.typeArguments.single; - if (typeToMock.isDynamic) { + if (typeToMock is analyzer.DynamicType || + typeToMock is analyzer.InvalidType) { final mockTypeName = mockType?.qualifiedName; if (mockTypeName == null) { throw InvalidMockitoAnnotationException( @@ -588,7 +590,8 @@ class _MockTargetGatherer { // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. typeArguments.forEachIndexed((typeArgIdx, typeArgument) { - if (!typeArgument.isDynamic) return; + if (!(typeArgument is analyzer.DynamicType || + typeArgument is analyzer.InvalidType)) return; if (typeArgIdx >= mockTypeArguments.arguments.length) return; final typeArgAst = mockTypeArguments.arguments[typeArgIdx]; if (typeArgAst is! ast.NamedType) { @@ -621,7 +624,8 @@ class _MockTargetGatherer { throw InvalidMockitoAnnotationException( 'The "mixingIn" argument includes a non-type: $m'); } - if (typeToMixin.isDynamic) { + if (typeToMixin is analyzer.DynamicType || + typeToMixin is analyzer.InvalidType) { throw InvalidMockitoAnnotationException( 'Mockito cannot mix `dynamic` into a mock class'); } @@ -1398,7 +1402,7 @@ class _MockClassInfo { ]); Expression? returnValueForMissingStub; - if (returnType.isVoid) { + if (returnType is analyzer.VoidType) { returnValueForMissingStub = refer('null'); } else if (returnType.isFutureOfVoid) { returnValueForMissingStub = @@ -1425,7 +1429,9 @@ class _MockClassInfo { var superNoSuchMethod = refer('super').property('noSuchMethod').call([invocation], namedArgs); - if (!returnType.isVoid && !returnType.isDynamic) { + if (returnType is! analyzer.VoidType && + returnType is! analyzer.DynamicType && + returnType is! analyzer.InvalidType) { superNoSuchMethod = superNoSuchMethod.asA(_typeReference(returnType)); } @@ -1606,7 +1612,7 @@ class _MockClassInfo { b.optionalParameters.add(matchingParameter); } } - if (type.returnType.isVoid) { + if (type.returnType is analyzer.VoidType) { b.body = Code(''); } else { b.body = _dummyValue(type.returnType, invocation).code; @@ -1965,7 +1971,9 @@ class _MockClassInfo { }; var superNoSuchMethod = refer('super').property('noSuchMethod').call([invocation], namedArgs); - if (!returnType.isVoid && !returnType.isDynamic) { + if (returnType is! analyzer.VoidType && + returnType is! analyzer.DynamicType && + returnType is! analyzer.InvalidType) { superNoSuchMethod = superNoSuchMethod.asA(_typeReference(returnType)); } @@ -2090,7 +2098,7 @@ class _MockClassInfo { // package:source_gen? Reference _typeReference(analyzer.DartType type, {bool forceNullable = false, bool overrideVoid = false}) { - if (overrideVoid && type.isVoid) { + if (overrideVoid && type is analyzer.VoidType) { return TypeReference((b) => b..symbol = 'dynamic'); } if (type is analyzer.InvalidType) { @@ -2321,7 +2329,7 @@ extension on analyzer.DartType { /// Returns whether this type is `Future` or `Future?`. bool get isFutureOfVoid => isDartAsyncFuture && - (this as analyzer.InterfaceType).typeArguments.first.isVoid; + (this as analyzer.InterfaceType).typeArguments.first is analyzer.VoidType; /// Returns whether this type is a sealed type from the dart:typed_data /// library. @@ -2380,7 +2388,7 @@ extension on int { } bool _needsOverrideForVoidStub(ExecutableElement method) => - method.returnType.isVoid || method.returnType.isFutureOfVoid; + method.returnType is analyzer.VoidType || method.returnType.isFutureOfVoid; /// This casts `ElementAnnotation` to the internal `ElementAnnotationImpl` /// class, since analyzer doesn't provide public interface to access From 93c23efbc3afd82abe8fcbc66026886caa57aaea Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 30 Jun 2023 03:04:59 -0700 Subject: [PATCH 529/607] Require analyzer 5.12.0 There was a recent change that added usage of the new `InvalidType` to Mockito codegen, but it only appears in analyzer 5.12. Fixes https://github.com/dart-lang/mockito/issues/656 PiperOrigin-RevId: 544605763 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index da207c6d3..20aeb1538 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.4.3-wip + +* Require analyzer 5.12.0. + ## 5.4.2 * Require code_builder 4.5.0. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index d7b0fe544..e0b6f302a 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.2'; +const packageVersion = '5.4.3-wip'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 578ccf212..6d27e7848 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.2 +version: 5.4.3-wip description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -9,7 +9,7 @@ environment: sdk: '>=2.19.0 <4.0.0' dependencies: - analyzer: '>=5.11.0 <6.0.0' + analyzer: '>=5.12.0 <6.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.5.0 collection: ^1.15.0 From e50a434f5e6cb2fd8b1500024d0e143d1f567a14 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 30 Jun 2023 09:04:55 -0700 Subject: [PATCH 530/607] Add a note on only running codegen on files under `test/` by default Fixes https://github.com/dart-lang/mockito/issues/631 Fixes https://github.com/dart-lang/mockito/issues/550 PiperOrigin-RevId: 544670948 --- pkgs/mockito/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 9ed48d4b4..4e7bed004 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -58,6 +58,10 @@ dart run build_runner build `@GenerateNiceMocks` annotation. In the above `cat.dart` example, we import the generated library as `cat.mocks.dart`. +**NOTE**: by default only annotations in files under `test/` are processed, if +you want to add Mockito annotations in other places, you will need to add a +`build.yaml` file to your project, see [this SO answer](https://stackoverflow.com/questions/68275811/is-there-a-way-to-let-mockito-generate-mocks-for-integration-tests-in-a-flutter). + The generated mock class, `MockCat`, extends Mockito's Mock class and implements the Cat class, giving us a class which supports stubbing and verifying. From 447055ad22d7b801c3cdd7331cb3763519c6bf6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jul 2023 23:46:15 +0000 Subject: [PATCH 531/607] Bump actions/checkout from 3.5.2 to 3.5.3 (dart-lang/mockito#669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
Release notes

Sourced from actions/checkout's releases.

v3.5.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v3.5.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

v2.3.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.2&new-version=3.5.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/mockito/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 77c848a30..3c93ca6dd 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: 2.19.0 @@ -39,7 +39,7 @@ jobs: matrix: sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -61,7 +61,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -86,7 +86,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: dev From 50f5ead14108c2fb3050b804c9c538761af9f177 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 5 Jul 2023 08:28:22 -0700 Subject: [PATCH 532/607] Use `FunctionTypedElement.type` while generating method overrides Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes https://github.com/dart-lang/mockito/issues/658 PiperOrigin-RevId: 545681214 --- pkgs/mockito/lib/src/builder.dart | 12 +++++----- .../test/builder/custom_mocks_test.dart | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 10afbf0ee..8298149ad 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1315,8 +1315,8 @@ class _MockClassInfo { void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { var name = method.displayName; if (method.isOperator) name = 'operator$name'; - final returnType = method.returnType; - _withTypeParameters(method.typeParameters, (typeParamsWithBounds, _) { + final returnType = method.type.returnType; + _withTypeParameters(method.type.typeFormals, (typeParamsWithBounds, _) { builder ..name = name ..annotations.add(referImported('override', 'dart:core')) @@ -1333,7 +1333,7 @@ class _MockClassInfo { final invocationNamedArgs = {}; var position = 0; - for (final parameter in method.parameters) { + for (final parameter in method.type.parameters) { if (parameter.isRequiredPositional || parameter.isOptionalPositional) { final superParameterType = _escapeCovariance(parameter, position: position); @@ -1370,7 +1370,7 @@ class _MockClassInfo { final fallbackGenerator = fallbackGenerators[method.name]; final parametersContainPrivateName = - method.parameters.any((p) => p.type.containsPrivateName); + method.type.parameters.any((p) => p.type.containsPrivateName); final throwsUnsupported = fallbackGenerator == null && (returnType.containsPrivateName || parametersContainPrivateName); @@ -1443,7 +1443,7 @@ class _MockClassInfo { ExecutableElement method, ExecutableElement function) { final positionalArguments = []; final namedArguments = {}; - for (final parameter in method.parameters) { + for (final parameter in method.type.parameters) { if (parameter.isPositional) { positionalArguments.add(refer(parameter.name)); } else if (parameter.isNamed) { @@ -1453,7 +1453,7 @@ class _MockClassInfo { final functionReference = referImported(function.name, _typeImport(function)); return functionReference.call(positionalArguments, namedArguments, [ - for (final t in method.typeParameters) + for (final t in method.type.typeFormals) _typeParameterReference(t, withBound: false) ]); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 4a673e9a2..d0de4b80c 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1843,6 +1843,29 @@ void main() { expect(mocksContent, contains('Iterable m1(X1 Function(X)? f)')); expect(mocksContent, contains('Iterable m2(X1 Function(X)? f)')); }); + test('We preserve nested generic bounded type arguments', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + abstract class Bar { + X m1, X>>(X Function(T)? f); + } + abstract class FooBar extends Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([FooBar]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'X1 m1, X1>>(X1 Function(X)? f)')); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From bbe5c194846d9f2249adff82840fdaef22bdbdba Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 5 Jul 2023 10:18:39 -0700 Subject: [PATCH 533/607] Rollback of "Use `FunctionTypedElement.type` while generating method overrides" PiperOrigin-RevId: 545713598 --- pkgs/mockito/lib/src/builder.dart | 12 +++++----- .../test/builder/custom_mocks_test.dart | 23 ------------------- 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 8298149ad..10afbf0ee 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1315,8 +1315,8 @@ class _MockClassInfo { void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { var name = method.displayName; if (method.isOperator) name = 'operator$name'; - final returnType = method.type.returnType; - _withTypeParameters(method.type.typeFormals, (typeParamsWithBounds, _) { + final returnType = method.returnType; + _withTypeParameters(method.typeParameters, (typeParamsWithBounds, _) { builder ..name = name ..annotations.add(referImported('override', 'dart:core')) @@ -1333,7 +1333,7 @@ class _MockClassInfo { final invocationNamedArgs = {}; var position = 0; - for (final parameter in method.type.parameters) { + for (final parameter in method.parameters) { if (parameter.isRequiredPositional || parameter.isOptionalPositional) { final superParameterType = _escapeCovariance(parameter, position: position); @@ -1370,7 +1370,7 @@ class _MockClassInfo { final fallbackGenerator = fallbackGenerators[method.name]; final parametersContainPrivateName = - method.type.parameters.any((p) => p.type.containsPrivateName); + method.parameters.any((p) => p.type.containsPrivateName); final throwsUnsupported = fallbackGenerator == null && (returnType.containsPrivateName || parametersContainPrivateName); @@ -1443,7 +1443,7 @@ class _MockClassInfo { ExecutableElement method, ExecutableElement function) { final positionalArguments = []; final namedArguments = {}; - for (final parameter in method.type.parameters) { + for (final parameter in method.parameters) { if (parameter.isPositional) { positionalArguments.add(refer(parameter.name)); } else if (parameter.isNamed) { @@ -1453,7 +1453,7 @@ class _MockClassInfo { final functionReference = referImported(function.name, _typeImport(function)); return functionReference.call(positionalArguments, namedArguments, [ - for (final t in method.type.typeFormals) + for (final t in method.typeParameters) _typeParameterReference(t, withBound: false) ]); } diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index d0de4b80c..4a673e9a2 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1843,29 +1843,6 @@ void main() { expect(mocksContent, contains('Iterable m1(X1 Function(X)? f)')); expect(mocksContent, contains('Iterable m2(X1 Function(X)? f)')); }); - test('We preserve nested generic bounded type arguments', () async { - final mocksContent = await buildWithNonNullable({ - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo {} - abstract class Bar { - X m1, X>>(X Function(T)? f); - } - abstract class FooBar extends Bar {} - '''), - 'foo|test/foo_test.dart': ''' - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - - @GenerateMocks([FooBar]) - void main() {} - ''' - }); - expect( - mocksContent, - contains( - 'X1 m1, X1>>(X1 Function(X)? f)')); - }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From 177af7f9100c85820f9e9e0505065d13ee0b797d Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 10 Jul 2023 03:19:29 -0700 Subject: [PATCH 534/607] Add example of mocking callbacks Towards dart-lang/mockito#62 Update the README and example usage with an example of writing a class to hold the callback signatures and mocking it with codegen. Demonstrate that the callbacks can be stubbed after being torn off. PiperOrigin-RevId: 546817751 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/README.md | 21 +++++++++++++++++++++ pkgs/mockito/example/example.dart | 20 +++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 20aeb1538..5657515fe 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,7 @@ ## 5.4.3-wip * Require analyzer 5.12.0. +* Add example of writing a class to mock function objects. ## 5.4.2 diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 4e7bed004..74fee1846 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -350,6 +350,27 @@ behavior? non-`null` value for a non-nullable return type). The value should not be used in any way; it is returned solely to avoid a runtime type exception. +## Mocking a Function type + +To create mocks for Function objects, write an `abstract class` with a method +for each function type signature that needs to be mocked. The methods can be +torn off and individually stubbed and verified. + +```dart +@GenerateMocks([Cat, Callbacks]) +import 'cat_test.mocks.dart' + +abstract class Callbacks { + Cat findCat(String name); +} + +void main() { + var mockCat = MockCat(); + var findCatCallback = MockCallbacks().findCat; + when(findCatCallback('Pete')).thenReturn(mockCat); +} +``` + ## Writing a fake You can also write a simple fake class that implements a real class, by diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 77cd00bcf..62480f336 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -26,8 +26,14 @@ class FakeCat extends Fake implements Cat { } } +abstract class Callbacks { + Cat findCat(String name); + String? makeSound(); +} + @GenerateMocks([ - Cat + Cat, + Callbacks, ], customMocks: [ MockSpec(as: #MockCatRelaxed, returnNullOnMissingStub: true), ]) @@ -206,6 +212,18 @@ void main() { await untilCalled(cat.eatFood(any)); // This completes immediately. }); + test('Mocked callbacks', () { + final makeSoundCallback = MockCallbacks().makeSound; + when(makeSoundCallback()).thenReturn('woof'); + expect(makeSoundCallback(), 'woof'); + + final findCatCallback = MockCallbacks().findCat; + final mockCat = MockCat(); + when(findCatCallback('Pete')).thenReturn(mockCat); + when(mockCat.sound()).thenReturn('meow'); + expect(findCatCallback('Pete').sound(), 'meow'); + }); + test('Fake class', () { // Create a new fake Cat at runtime. final cat = FakeCat(); From 7ff4f587c083044e76cece6a6f0938495641cba7 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 12 Jul 2023 01:41:40 -0700 Subject: [PATCH 535/607] Second attempt to fix "not found" error for type vars in bounds First attempt was https://github.com/dart-lang/mockito/pull/671 and I had to roll it back, since it caused breakages in two ways: 1. Sometimes `ParameterElement` from `type.parameters` had `enclosingElement` set to `null` and we use that in one helper function. That was easy to fix, we could just pass `methodElement` to that function directly. It's probably correct that `ParameterElement` of a `FunctionType` doesn't link back to a `MethodElement`, but it's weird that sometimes it does, so it wasn't caught in the tests. I had to get rid of using `type.parameters` anyway because of the second problem. 2. `type.parameters` don't contain parameters' default values (totally correct, since default values belong to methods, not to types), but that means we can't use them, since we do need default values. So I ended up with a more hacky solution, that uses `type.typeFormals` just to get correct references for method's type parameters, and then uses `typeParameters`, `returnType` and `parameters` for the rest as before. Original commit description: Use `FunctionTypedElement.type` while generating method overrides Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes dart-lang/mockito#658 PiperOrigin-RevId: 547427000 --- pkgs/mockito/lib/src/builder.dart | 37 ++++++++++++++++--- .../mockito/test/builder/auto_mocks_test.dart | 15 ++++++++ .../test/builder/custom_mocks_test.dart | 23 ++++++++++++ 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 10afbf0ee..3fc4c28ab 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1316,7 +1316,8 @@ class _MockClassInfo { var name = method.displayName; if (method.isOperator) name = 'operator$name'; final returnType = method.returnType; - _withTypeParameters(method.typeParameters, (typeParamsWithBounds, _) { + _withTypeParameters(method.typeParameters, + typeFormalsHack: method.type.typeFormals, (typeParamsWithBounds, _) { builder ..name = name ..annotations.add(referImported('override', 'dart:core')) @@ -2057,17 +2058,43 @@ class _MockClassInfo { } } + /// Creates fresh type parameter names for [typeParameters] and runs [body] + /// in the extended type parameter scope, passing type references for + /// [typeParameters] (both with and without bound) as arguments. + /// If [typeFormalsHack] is not `null`, it will be used to build the + /// type references instead of [typeParameters]. This is needed while + /// building method overrides, since sometimes + /// [ExecutableMember.typeParameters] can contain inconsistency if a type + /// parameter refers to itself in its bound. See + /// https://github.com/dart-lang/mockito/issues/658. So we have to + /// pass `ExecutableMember.type.typeFormals` instead, that seem to be + /// always correct. Unfortunately we can't just use the latter everywhere, + /// since `type.typeFormals` don't contain default arguments' values + /// and we need that for code generation. T _withTypeParameters(Iterable typeParameters, - T Function(Iterable, Iterable) body) { - final typeVars = {for (final t in typeParameters) t: _newTypeVar(t)}; - _typeVariableScopes.add(typeVars); + T Function(Iterable, Iterable) body, + {Iterable? typeFormalsHack}) { + final typeVars = [for (final t in typeParameters) _newTypeVar(t)]; + final scope = Map.fromIterables(typeParameters, typeVars); + _typeVariableScopes.add(scope); + if (typeFormalsHack != null) { + // add an additional scope based on [type.typeFormals] just to make + // type parameters references. + _typeVariableScopes.add(Map.fromIterables(typeFormalsHack, typeVars)); + // use typeFormals instead of typeParameters to create refs. + typeParameters = typeFormalsHack; + } final typeRefsWithBounds = typeParameters.map(_typeParameterReference); final typeRefs = typeParameters.map((t) => _typeParameterReference(t, withBound: false)); final result = body(typeRefsWithBounds, typeRefs); _typeVariableScopes.removeLast(); - _usedTypeVariables.removeAll(typeVars.values); + if (typeFormalsHack != null) { + // remove the additional scope too. + _typeVariableScopes.removeLast(); + } + _usedTypeVariables.removeAll(typeVars); return result; } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 836a699e9..dfdf23251 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1540,6 +1540,21 @@ void main() { ); }); + test('widens the type of covariant generic parameters to be nullable', + () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBase { + void m(Object a); + } + abstract class Foo extends FooBase { + void m(covariant T a); + } + '''), + _containsAllOf('void m(Object? a) => super.noSuchMethod('), + ); + }); + test('matches nullability of type arguments of a parameter', () async { await expectSingleNonNullableOutput( dedent(r''' diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 4a673e9a2..d0de4b80c 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -1843,6 +1843,29 @@ void main() { expect(mocksContent, contains('Iterable m1(X1 Function(X)? f)')); expect(mocksContent, contains('Iterable m2(X1 Function(X)? f)')); }); + test('We preserve nested generic bounded type arguments', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + abstract class Bar { + X m1, X>>(X Function(T)? f); + } + abstract class FooBar extends Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([FooBar]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'X1 m1, X1>>(X1 Function(X)? f)')); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From c7d7bfccdffe9096e5a4b1f93d16f3821dbf8c26 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 14 Jul 2023 07:19:05 -0700 Subject: [PATCH 536/607] Disable deprecated member findings in generated code People might want to mock classes that use some deprecated libraries or classes. Mocked class itself could also be deprecated. Deprecated member findings for the generated code are not very useful: 1. They are dubbing the finding in the actual code. 2. They are hard to disable (have to edit generated code). So disable `deprecated_member_use` and `deprecated_member_use_from_same_package` findings in the generated code. Fixes https://github.com/dart-lang/mockito/issues/565 PiperOrigin-RevId: 548118939 --- pkgs/mockito/lib/src/builder.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 3fc4c28ab..9a6828386 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -97,6 +97,10 @@ class MockBuilder implements Builder { b.body.add(Code('// ignore_for_file: avoid_setters_without_getters\n')); // We don't properly prefix imported class names in doc comments. b.body.add(Code('// ignore_for_file: comment_references\n')); + // We might import a deprecated library, or implement a deprecated class. + b.body.add(Code('// ignore_for_file: deprecated_member_use\n')); + b.body.add(Code( + '// ignore_for_file: deprecated_member_use_from_same_package\n')); // We might import a package's 'src' directory. b.body.add(Code('// ignore_for_file: implementation_imports\n')); // `Mock.noSuchMethod` is `@visibleForTesting`, but the generated code is From b12c555671180f1929d74724e938eeecfba2e05b Mon Sep 17 00:00:00 2001 From: Googler Date: Sat, 22 Jul 2023 07:35:33 -0700 Subject: [PATCH 537/607] Internal change PiperOrigin-RevId: 550196851 --- pkgs/mockito/lib/src/builder.dart | 8 ++++---- pkgs/mockito/test/builder/auto_mocks_test.dart | 6 +++--- pkgs/mockito/test/builder/custom_mocks_test.dart | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 9a6828386..19381ddd5 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -512,9 +512,9 @@ class _MockTargetGatherer { static Iterable<_MockTarget> _mockTargetsFromGenerateMocks( ElementAnnotation annotation, LibraryElement entryLib) { - final generateMocksValue = annotation.computeConstantValue()!; - final classesField = generateMocksValue.getField('classes')!; - if (classesField.isNull) { + final generateMocksValue = annotation.computeConstantValue(); + final classesField = generateMocksValue?.getField('classes'); + if (classesField == null || classesField.isNull) { throw InvalidMockitoAnnotationException( 'The GenerateMocks "classes" argument is missing, includes an ' 'unknown type, or includes an extension'); @@ -548,7 +548,7 @@ class _MockTargetGatherer { fallbackGenerators: {}, )); } - final customMocksField = generateMocksValue.getField('customMocks'); + final customMocksField = generateMocksValue?.getField('customMocks'); if (customMocksField != null && !customMocksField.isNull) { final customMocksAsts = _customMocksAst(annotation.annotationAst)?.elements ?? diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index dfdf23251..1a7b0e001 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -30,11 +30,11 @@ class GenerateMocks { final List classes; final List customMocks; - const GenerateMocks(this.classes, {this.customMocks = []}); + const GenerateMocks(this.classes, {this.customMocks = const []}); } class MockSpec { - final Symbol mockName; + final Symbol? mockName; final bool returnNullOnMissingStub; @@ -43,7 +43,7 @@ class MockSpec { final Map fallbackGenerators; const MockSpec({ - Symbol as, + Symbol? as, this.returnNullOnMissingStub = false, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index d0de4b80c..9fac876b7 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -39,7 +39,7 @@ class GenerateNiceMocks { } class MockSpec { - final Symbol mockName; + final Symbol? mockName; final List mixins; From b8180a383b0612b79bb57cdc779baab7d5af558a Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 4 Aug 2023 01:54:54 -0700 Subject: [PATCH 538/607] Allow the latest package:analyzer Closes dart-lang/mockito#681 PiperOrigin-RevId: 553733481 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 5657515fe..7da3cb86a 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,6 +1,6 @@ ## 5.4.3-wip -* Require analyzer 5.12.0. +* Require analyzer 5.12.0, allow analyzer version 6.x; * Add example of writing a class to mock function objects. ## 5.4.2 diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 6d27e7848..6d9c55618 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: '>=2.19.0 <4.0.0' dependencies: - analyzer: '>=5.12.0 <6.0.0' + analyzer: '>=5.12.0 <7.0.0' build: '>=1.3.0 <3.0.0' code_builder: ^4.5.0 collection: ^1.15.0 From 0f7f66ee2b75cbc19dc65b5f8ebd38fb7227f228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Duarte?= Date: Thu, 10 Aug 2023 17:53:05 +0100 Subject: [PATCH 539/607] Make MockBuilder support build_extensions option. This is useful to change the destination of the generated files. i.e: instead of having them on the same folder, you can specify a diferent folder for the mocks. Closes https://github.com/dart-lang/mockito/issues/545 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/FAQ.md | 34 +++++ pkgs/mockito/build.yaml | 7 + .../example/build_extensions/example.dart | 40 ++++++ pkgs/mockito/lib/src/builder.dart | 57 +++++++-- .../mockito/test/builder/auto_mocks_test.dart | 120 +++++++++++++++++- 6 files changed, 244 insertions(+), 15 deletions(-) create mode 100644 pkgs/mockito/example/build_extensions/example.dart diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 7da3cb86a..4d1dba83a 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -2,6 +2,7 @@ * Require analyzer 5.12.0, allow analyzer version 6.x; * Add example of writing a class to mock function objects. +* Add support for the `build_extensions` build.yaml option ## 5.4.2 diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index 53ca02195..f20336041 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -167,3 +167,37 @@ it's done. It's very straightforward. [`verify`]: https://pub.dev/documentation/mockito/latest/mockito/verify.html [`verifyInOrder`]: https://pub.dev/documentation/mockito/latest/mockito/verifyInOrder.html + + +### How can I customize where Mockito outputs its mocks? + +Mockito supports configuration of outputs by the configuration provided by the `build` +package by creating (if it doesn't exist already) the `build.yaml` at the root folder +of the project. + +It uses the `build_extensions` option, which can be used to alter not only the output directory but you +can also do other filename manipulation, eg.: append/prepend strings to the filename or add another extension +to the filename. + +To use `build_extensions` you can use `^` on the input string to match on the project root, and `{{}}` to capture the remaining path/filename. + +You can also have multiple build_extensions options, but they can't conflict with each other. +For consistency, the output pattern must always end with `.mocks.dart` and the input pattern must always end with `.dart` + +```yaml +targets: + $default: + builders: + mockito|mockBuilder: + generate_for: + options: + # build_extensions takes a source pattern and if it matches it will transform the output + # to your desired path. The default behaviour is to the .mocks.dart file to be in the same + # directory as the source .dart file. As seen below this is customizable, but the generated + # file must always end in `.mocks.dart`. + build_extensions: + '^tests/{{}}.dart' : 'tests/mocks/{{}}.mocks.dart' + '^integration-tests/{{}}.dart' : 'integration-tests/{{}}.mocks.dart' +``` + +Also, you can also check out the example configuration in the Mockito repository. diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index 0b4fb40d9..1c44479f3 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -5,6 +5,13 @@ targets: generate_for: - example/**.dart - test/end2end/*.dart + options: + # build_extensions takes a source pattern and if it matches it will transform the output + # to your desired path. The default behaviour is to the .mocks.dart file to be in the same + # directory as the source .dart file. As seen below this is customizable, but the generated + # file must always end in `.mocks.dart`. + build_extensions: + '^example/build_extensions/{{}}.dart' : 'example/build_extensions/mocks/{{}}.mocks.dart' builders: mockBuilder: diff --git a/pkgs/mockito/example/build_extensions/example.dart b/pkgs/mockito/example/build_extensions/example.dart new file mode 100644 index 000000000..fde5ca406 --- /dev/null +++ b/pkgs/mockito/example/build_extensions/example.dart @@ -0,0 +1,40 @@ +// Copyright 2023 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test_api/scaffolding.dart'; + +// Because we customized the `build_extensions` option, we can output +// the generated mocks in a diferent directory +import 'mocks/example.mocks.dart'; + +class Dog { + String sound() => "bark"; + bool? eatFood(String? food) => true; + Future chew() async => print('Chewing...'); + int? walk(List? places) => 1; +} + +@GenerateNiceMocks([MockSpec()]) +void main() { + test("Verify some dog behaviour", () async { + MockDog mockDog = MockDog(); + when(mockDog.eatFood(any)); + + mockDog.eatFood("biscuits"); + + verify(mockDog.eatFood(any)).called(1); + }); +} diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 19381ddd5..a9d53f78b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -62,19 +62,41 @@ import 'package:source_gen/source_gen.dart'; /// will produce a "'.mocks.dart' file with such mocks. In this example, /// 'foo.mocks.dart' will be created. class MockBuilder implements Builder { + @override + final Map> buildExtensions = { + '.dart': ['.mocks.dart'] + }; + + MockBuilder({Map>? buildExtensions}) { + this.buildExtensions.addAll(buildExtensions ?? {}); + } + @override Future build(BuildStep buildStep) async { if (!await buildStep.resolver.isLibrary(buildStep.inputId)) return; final entryLib = await buildStep.inputLibrary; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; - final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart'); + + // While it can be acceptable that we get more than 2 allowedOutputs, + // because it's the general one and the user defined one. Having + // more, means that user has conflicting patterns so we should throw. + if (buildStep.allowedOutputs.length > 2) { + throw ArgumentError('Build_extensions has conflicting outputs on file ' + '`${buildStep.inputId.path}`, it usually caused by missconfiguration ' + 'on your `build.yaml` file'); + } + // if not single, we always choose the user defined one. + final mockLibraryAsset = buildStep.allowedOutputs.singleOrNull ?? + buildStep.allowedOutputs + .where((element) => + element != buildStep.inputId.changeExtension('.mocks.dart')) + .single; final inheritanceManager = InheritanceManager3(); final mockTargetGatherer = _MockTargetGatherer(entryLib, inheritanceManager); - final entryAssetId = await buildStep.resolver.assetIdForElement(entryLib); final assetUris = await _resolveAssetUris(buildStep.resolver, - mockTargetGatherer._mockTargets, entryAssetId.path, entryLib); + mockTargetGatherer._mockTargets, mockLibraryAsset.path, entryLib); final mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets, assetUris: assetUris, @@ -240,11 +262,6 @@ $rawOutput } return element.library!; } - - @override - final buildExtensions = const { - '.dart': ['.mocks.dart'] - }; } /// An [Element] visitor which collects the elements of all of the @@ -2304,7 +2321,29 @@ class _AvoidConflictsAllocator implements Allocator { } /// A [MockBuilder] instance for use by `build.yaml`. -Builder buildMocks(BuilderOptions options) => MockBuilder(); +Builder buildMocks(BuilderOptions options) { + final buildExtensions = options.config['build_extensions']; + if (buildExtensions == null) return MockBuilder(); + if (buildExtensions is! Map) { + throw ArgumentError( + 'build_extensions should be a map from inputs to outputs'); + } + final result = >{}; + for (final entry in buildExtensions.entries) { + final input = entry.key; + final output = entry.value; + if (input is! String || !input.endsWith('.dart')) { + throw ArgumentError('Invalid key in build_extensions `$input`, it ' + 'should be a string ending with `.dart`'); + } + if (output is! String || !output.endsWith('.mocks.dart')) { + throw ArgumentError('Invalid key in build_extensions `$output`, it ' + 'should be a string ending with `mocks.dart`'); + } + result[input] = [output]; + } + return MockBuilder(buildExtensions: result); +} extension on Element { /// Returns the "full name" of a class or method element. diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 1a7b0e001..f3237cc22 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -22,8 +22,6 @@ import 'package:mockito/src/builder.dart'; import 'package:package_config/package_config.dart'; import 'package:test/test.dart'; -Builder buildMocks(BuilderOptions options) => MockBuilder(); - const annotationsAsset = { 'mockito|lib/annotations.dart': ''' class GenerateMocks { @@ -86,25 +84,27 @@ void main() { /// Test [MockBuilder] in a package which has not opted into null safety. Future testPreNonNullable(Map sourceAssets, - {Map*/ Object>? outputs}) async { + {Map*/ Object>? outputs, + Map config = const {}}) async { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(2, 7)) ]); - await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + await testBuilder(buildMocks(BuilderOptions(config)), sourceAssets, writer: writer, outputs: outputs, packageConfig: packageConfig); } /// Test [MockBuilder] in a package which has opted into null safety. Future testWithNonNullable(Map sourceAssets, - {Map>*/ Object>? outputs}) async { + {Map>*/ Object>? outputs, + Map config = const {}}) async { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), languageVersion: LanguageVersion(3, 0)) ]); - await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, + await testBuilder(buildMocks(BuilderOptions(config)), sourceAssets, writer: writer, outputs: outputs, packageConfig: packageConfig); } @@ -3662,6 +3662,114 @@ void main() { contains('bar: _FakeBar_0(')))); }); }); + + group('build_extensions support', () { + test('should export mocks to different directory', () async { + await testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'bar.dart'; + class Foo extends Bar {} + ''', + 'foo|lib/bar.dart': ''' + import 'dart:async'; + class Bar { + m(Future a) {} + } + ''', + }, config: { + "build_extensions": {"^test/{{}}.dart": "test/mocks/{{}}.mocks.dart"} + }); + final mocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); + final mocksContent = utf8.decode(writer.assets[mocksAsset]!); + expect(mocksContent, contains("import 'dart:async' as _i3;")); + expect(mocksContent, contains('m(_i3.Future? a)')); + }); + + test('should throw if it has confilicting outputs', () async { + await expectLater( + testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'bar.dart'; + class Foo extends Bar {} + ''', + 'foo|lib/bar.dart': ''' + import 'dart:async'; + class Bar { + m(Future a) {} + } + ''', + }, config: { + "build_extensions": { + "^test/{{}}.dart": "test/mocks/{{}}.mocks.dart", + "test/{{}}.dart": "test/{{}}.something.mocks.dart" + } + }), + throwsArgumentError); + final mocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); + final otherMocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); + final somethingMocksAsset = + AssetId('foo', 'test/mocks/foo_test.something.mocks.dart'); + + expect(writer.assets.containsKey(mocksAsset), false); + expect(writer.assets.containsKey(otherMocksAsset), false); + expect(writer.assets.containsKey(somethingMocksAsset), false); + }); + + test('should throw if input is in incorrect format', () async { + await expectLater( + testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'bar.dart'; + class Foo extends Bar {} + ''', + 'foo|lib/bar.dart': ''' + import 'dart:async'; + class Bar { + m(Future a) {} + } + ''', + }, config: { + "build_extensions": {"^test/{{}}": "test/mocks/{{}}.mocks.dart"} + }), + throwsArgumentError); + final mocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); + final mocksAssetOriginal = AssetId('foo', 'test/foo_test.mocks.dart'); + + expect(writer.assets.containsKey(mocksAsset), false); + expect(writer.assets.containsKey(mocksAssetOriginal), false); + }); + + test('should throw if output is in incorrect format', () async { + await expectLater( + testWithNonNullable({ + ...annotationsAsset, + ...simpleTestAsset, + 'foo|lib/foo.dart': ''' + import 'bar.dart'; + class Foo extends Bar {} + ''', + 'foo|lib/bar.dart': ''' + import 'dart:async'; + class Bar { + m(Future a) {} + } + ''', + }, config: { + "build_extensions": {"^test/{{}}.dart": "test/mocks/{{}}.g.dart"} + }), + throwsArgumentError); + final mocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); + final mocksAssetOriginal = AssetId('foo', 'test/foo_test.mocks.dart'); + expect(writer.assets.containsKey(mocksAsset), false); + expect(writer.assets.containsKey(mocksAssetOriginal), false); + }); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( From ea8b6d04ea583ad3ca6709838369b4b2962bf2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Duarte?= Date: Wed, 16 Aug 2023 11:17:05 +0100 Subject: [PATCH 540/607] Update lib/src/builder.dart Co-authored-by: Nate Bosch --- pkgs/mockito/lib/src/builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index a9d53f78b..bacc01a8f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -2337,7 +2337,7 @@ Builder buildMocks(BuilderOptions options) { 'should be a string ending with `.dart`'); } if (output is! String || !output.endsWith('.mocks.dart')) { - throw ArgumentError('Invalid key in build_extensions `$output`, it ' + throw ArgumentError('Invalid value in build_extensions `$output`, it ' 'should be a string ending with `mocks.dart`'); } result[input] = [output]; From e39d456248b0d68ef70225dc2e6e4564f6567b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Duarte?= Date: Wed, 16 Aug 2023 15:08:31 +0100 Subject: [PATCH 541/607] Replace double-quotes with single quotes --- pkgs/mockito/example/build_extensions/example.dart | 6 +++--- pkgs/mockito/test/builder/auto_mocks_test.dart | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/example/build_extensions/example.dart b/pkgs/mockito/example/build_extensions/example.dart index fde5ca406..ddda06541 100644 --- a/pkgs/mockito/example/build_extensions/example.dart +++ b/pkgs/mockito/example/build_extensions/example.dart @@ -21,7 +21,7 @@ import 'package:test_api/scaffolding.dart'; import 'mocks/example.mocks.dart'; class Dog { - String sound() => "bark"; + String sound() => 'bark'; bool? eatFood(String? food) => true; Future chew() async => print('Chewing...'); int? walk(List? places) => 1; @@ -29,11 +29,11 @@ class Dog { @GenerateNiceMocks([MockSpec()]) void main() { - test("Verify some dog behaviour", () async { + test('Verify some dog behaviour', () async { MockDog mockDog = MockDog(); when(mockDog.eatFood(any)); - mockDog.eatFood("biscuits"); + mockDog.eatFood('biscuits'); verify(mockDog.eatFood(any)).called(1); }); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index f3237cc22..31a62447b 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3679,7 +3679,7 @@ void main() { } ''', }, config: { - "build_extensions": {"^test/{{}}.dart": "test/mocks/{{}}.mocks.dart"} + 'build_extensions': {'^test/{{}}.dart': 'test/mocks/{{}}.mocks.dart'} }); final mocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); final mocksContent = utf8.decode(writer.assets[mocksAsset]!); @@ -3703,9 +3703,9 @@ void main() { } ''', }, config: { - "build_extensions": { - "^test/{{}}.dart": "test/mocks/{{}}.mocks.dart", - "test/{{}}.dart": "test/{{}}.something.mocks.dart" + 'build_extensions': { + '^test/{{}}.dart': 'test/mocks/{{}}.mocks.dart', + 'test/{{}}.dart': 'test/{{}}.something.mocks.dart' } }), throwsArgumentError); @@ -3735,7 +3735,7 @@ void main() { } ''', }, config: { - "build_extensions": {"^test/{{}}": "test/mocks/{{}}.mocks.dart"} + 'build_extensions': {'^test/{{}}': 'test/mocks/{{}}.mocks.dart'} }), throwsArgumentError); final mocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); @@ -3761,7 +3761,7 @@ void main() { } ''', }, config: { - "build_extensions": {"^test/{{}}.dart": "test/mocks/{{}}.g.dart"} + 'build_extensions': {'^test/{{}}.dart': 'test/mocks/{{}}.g.dart'} }), throwsArgumentError); final mocksAsset = AssetId('foo', 'test/mocks/foo_test.mocks.dart'); From 771708f15533576610ffbee1c18998aaca1ad7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Duarte?= Date: Thu, 17 Aug 2023 14:49:06 +0100 Subject: [PATCH 542/607] Make builder not merge generic extension. The previous behaviour was that if the user adds a build_extension, the build would inject the generic build extension to cover all files. This was useful to simplify the build.yaml file at the cost of unexpected behaviour. To avoid potencial unexpected behaviour, if the user provides custom build_extensions we assume that the patterns cover all files and therefore we do not merge the generic build_extension. --- pkgs/mockito/FAQ.md | 4 +++- pkgs/mockito/build.yaml | 5 +++++ pkgs/mockito/lib/src/builder.dart | 24 ++++++++---------------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index f20336041..bc3e09eb8 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -182,7 +182,9 @@ to the filename. To use `build_extensions` you can use `^` on the input string to match on the project root, and `{{}}` to capture the remaining path/filename. You can also have multiple build_extensions options, but they can't conflict with each other. -For consistency, the output pattern must always end with `.mocks.dart` and the input pattern must always end with `.dart` +For consistency, the output pattern must always end with `.mocks.dart` and the input pattern must always end with `.dart`. + +If you specify a build extension, you **MUST** ensure that your patterns cover all input files that you want generate mocks from. Failing to do so will lead to the unmatched file from not being generated at all. ```yaml targets: diff --git a/pkgs/mockito/build.yaml b/pkgs/mockito/build.yaml index 1c44479f3..25487f354 100644 --- a/pkgs/mockito/build.yaml +++ b/pkgs/mockito/build.yaml @@ -10,8 +10,13 @@ targets: # to your desired path. The default behaviour is to the .mocks.dart file to be in the same # directory as the source .dart file. As seen below this is customizable, but the generated # file must always end in `.mocks.dart`. + # + # If you specify custom build_extensions you MUST ensure that they cover all input files build_extensions: '^example/build_extensions/{{}}.dart' : 'example/build_extensions/mocks/{{}}.mocks.dart' + '^example/example.dart' : 'example/example.mocks.dart' + '^example/iss/{{}}.dart' : 'example/iss/{{}}.mocks.dart' + '^test/end2end/{{}}.dart' : 'test/end2end/{{}}.mocks.dart' builders: mockBuilder: diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index bacc01a8f..616236891 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -63,13 +63,12 @@ import 'package:source_gen/source_gen.dart'; /// 'foo.mocks.dart' will be created. class MockBuilder implements Builder { @override - final Map> buildExtensions = { - '.dart': ['.mocks.dart'] - }; + final Map> buildExtensions; - MockBuilder({Map>? buildExtensions}) { - this.buildExtensions.addAll(buildExtensions ?? {}); - } + const MockBuilder( + {this.buildExtensions = const { + '.dart': ['.mocks.dart'] + }}); @override Future build(BuildStep buildStep) async { @@ -77,20 +76,13 @@ class MockBuilder implements Builder { final entryLib = await buildStep.inputLibrary; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; - // While it can be acceptable that we get more than 2 allowedOutputs, - // because it's the general one and the user defined one. Having - // more, means that user has conflicting patterns so we should throw. - if (buildStep.allowedOutputs.length > 2) { + if (buildStep.allowedOutputs.length > 1) { throw ArgumentError('Build_extensions has conflicting outputs on file ' '`${buildStep.inputId.path}`, it usually caused by missconfiguration ' 'on your `build.yaml` file'); } - // if not single, we always choose the user defined one. - final mockLibraryAsset = buildStep.allowedOutputs.singleOrNull ?? - buildStep.allowedOutputs - .where((element) => - element != buildStep.inputId.changeExtension('.mocks.dart')) - .single; + final mockLibraryAsset = buildStep.allowedOutputs.single; + final inheritanceManager = InheritanceManager3(); final mockTargetGatherer = _MockTargetGatherer(entryLib, inheritanceManager); From 511b1a017e3293f14355dbe0c6e14e8aae204d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Duarte?= Date: Thu, 17 Aug 2023 21:26:51 +0100 Subject: [PATCH 543/607] Update lib/src/builder.dart Co-authored-by: Nate Bosch --- pkgs/mockito/lib/src/builder.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 616236891..325d86bea 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -76,12 +76,12 @@ class MockBuilder implements Builder { final entryLib = await buildStep.inputLibrary; final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; - if (buildStep.allowedOutputs.length > 1) { - throw ArgumentError('Build_extensions has conflicting outputs on file ' - '`${buildStep.inputId.path}`, it usually caused by missconfiguration ' - 'on your `build.yaml` file'); + final mockLibraryAsset = buildStep.allowedOutputs.singleOrNull; + if (mockLibraryAsset == null) { + throw ArgumentError('Build_extensions has missing or conflicting outputs for ' + '`${buildStep.inputId.path}`, this is usually caused by a misconfigured ' + 'build extension override in `build.yaml`'); } - final mockLibraryAsset = buildStep.allowedOutputs.single; final inheritanceManager = InheritanceManager3(); final mockTargetGatherer = From 5d7876b08e4aa60e978d541c1ea65b4a13f6ee36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Duarte?= Date: Fri, 18 Aug 2023 11:40:54 +0000 Subject: [PATCH 544/607] Format files --- pkgs/mockito/lib/src/builder.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 325d86bea..48be22054 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -78,7 +78,8 @@ class MockBuilder implements Builder { final mockLibraryAsset = buildStep.allowedOutputs.singleOrNull; if (mockLibraryAsset == null) { - throw ArgumentError('Build_extensions has missing or conflicting outputs for ' + throw ArgumentError( + 'Build_extensions has missing or conflicting outputs for ' '`${buildStep.inputId.path}`, this is usually caused by a misconfigured ' 'build extension override in `build.yaml`'); } From f4541eb5f7b03e1877299c328c8b467ba24e3e81 Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Tue, 15 Aug 2023 18:35:29 -0500 Subject: [PATCH 545/607] Fix analysis errors in example directory --- pkgs/mockito/example/example.dart | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 62480f336..1439ed2e5 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -1,5 +1,3 @@ -// ignore_for_file: sdk_version_async_exported_from_core -// ignore_for_file: unawaited_futures import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; @@ -35,7 +33,10 @@ abstract class Callbacks { Cat, Callbacks, ], customMocks: [ - MockSpec(as: #MockCatRelaxed, returnNullOnMissingStub: true), + MockSpec( + as: #MockCatRelaxed, + onMissingStub: OnMissingStub.returnDefault, + ), ]) void main() { late Cat cat; @@ -57,14 +58,8 @@ void main() { }); test('How about some stubbing?', () { - try { - cat.sound(); - } on MissingStubError { - // Unstubbed methods throw MissingStubError. - } - - // Unstubbed methods return null. - expect(cat.sound(), null); + // Unstubbed methods throw MissingStubError. + expect(() => cat.sound(), throwsA(isA())); // Stub a method before interacting with it. when(cat.sound()).thenReturn('Purr'); @@ -102,7 +97,7 @@ void main() { // ... or matchers when(cat.eatFood(argThat(startsWith('dry')))).thenReturn(false); - // ... or mix aguments with matchers + // ... or mix arguments with matchers when(cat.eatFood(argThat(startsWith('dry')), hungry: true)) .thenReturn(true); expect(cat.eatFood('fish'), isTrue); @@ -239,7 +234,12 @@ void main() { // You can call it without stubbing. cat.sleep(); - // Returns null unless you stub it. + // Non-null properties and function return values + // default to reasonable defaults. + expect(cat.lives, 0); + + // Nullable properties and function return values + // default to null unless you stub them. expect(cat.sound(), null); expect(cat.eatFood('Milk'), null); From 53d891a0674775ac263ebca6bc895ff4a5a738f5 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 28 Aug 2023 01:09:16 -0700 Subject: [PATCH 546/607] Add a best practice sentence about data models It should always be possible to use real instances for data only classes. Specifically mention data models in the best practices section as not needing mocks. PiperOrigin-RevId: 560634157 --- pkgs/mockito/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 74fee1846..0189d40c9 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -433,13 +433,14 @@ throwOnMissingStub(cat); Testing with real objects is preferred over testing with mocks - if you can construct a real instance for your tests, you should! If there are no calls to -[`verify`] in your test, it is a strong signal that you may not need mocks at all, -though it's also OK to use a `Mock` like a stub. When it's not possible to use -the real object, a tested implementation of a fake is the next best thing - it's -more likely to behave similarly to the real class than responses stubbed out in -tests. Finally an object which `extends Fake` using manually overridden methods -is preferred over an object which `extends Mock` used as either a stub or a -mock. +[`verify`] in your test, it is a strong signal that you may not need mocks at +all, though it's also OK to use a `Mock` like a stub. Data models never need to +be mocked if they can be constructed with stubbed data. When it's not possible +to use the real object, a tested implementation of a fake is the next best +thing; it's more likely to behave similarly to the real class than responses +stubbed out in tests. Finally an object which `extends Fake` using manually +overridden methods is preferred over an object which `extends Mock` used as +either a stub or a mock. A class which `extends Mock` should _never_ stub out its own responses with `when` in its constructor or anywhere else. Stubbed responses should be defined From e6ec8dbc5d24ccbb4b46b9b39f7085baab7c9e7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 20:55:51 +0000 Subject: [PATCH 547/607] Bump actions/checkout from 3.5.3 to 3.6.0 (dart-lang/mockito#692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
Release notes

Sourced from actions/checkout's releases.

v3.6.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3.5.3...v3.6.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.3&new-version=3.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/mockito/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 3c93ca6dd..ce1f619d3 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: 2.19.0 @@ -39,7 +39,7 @@ jobs: matrix: sdk: [2.19.0, dev] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -61,7 +61,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -86,7 +86,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: dev From 335e0ce3fca174cb01ae8cd7bf7cce42f78433af Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Tue, 19 Sep 2023 10:23:52 -0700 Subject: [PATCH 548/607] Add a missing dummy `bool` value Seems like I forgot to add it. PiperOrigin-RevId: 566671572 --- pkgs/mockito/lib/src/dummies.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index 8bd6b85fb..f5630c13a 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -15,6 +15,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:typed_data'; + import 'mock.dart' show FakeFunctionUsedError; import 'platform_dummies_js.dart' if (dart.library.io) 'platform_dummies_vm.dart'; @@ -99,6 +100,7 @@ typedef DummyBuilder = T Function(Object parent, Invocation invocation); Map _dummyBuilders = {}; Map _defaultDummyBuilders = { + bool: (_, _i) => false, int: (_, _i) => _dummyInt, num: (_, _i) => _dummyInt, double: (_, _i) => _dummyDouble, From 8474c878f5e0c69e4f880935b31135196145532d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 20:08:04 +0000 Subject: [PATCH 549/607] Bump actions/checkout from 3.6.0 to 4.1.0 (dart-lang/mockito#698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.1.0.
Release notes

Sourced from actions/checkout's releases.

v4.1.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.0.0...v4.1.0

v4.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v4.0.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.6.0&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/mockito/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index ce1f619d3..b732542b7 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: 2.19.0 @@ -39,7 +39,7 @@ jobs: matrix: sdk: [2.19.0, dev] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -61,7 +61,7 @@ jobs: os: [ubuntu-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -86,7 +86,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: dev From 2e621921de3dd334585b69e716c96d578f8af9f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 20:14:04 +0000 Subject: [PATCH 550/607] Bump dart-lang/setup-dart from 1.5.0 to 1.5.1 (dart-lang/mockito#699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.0 to 1.5.1.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/mockito#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/mockito#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

  • Added a flavor option setup.sh to allow downloading unpublished builds.

v1.0.0

  • Promoted to 1.0 stable.

v0.5

  • Fixed a Windows pub global activate path issue.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.5.0&new-version=1.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/mockito/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index b732542b7..9c38218fd 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: 2.19.0 - id: install @@ -40,7 +40,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install @@ -62,7 +62,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install @@ -87,7 +87,7 @@ jobs: os: [ubuntu-latest] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: dev - id: install From 801ef4dd5b477ad7598b64550233fed8a03b81dd Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 5 Oct 2023 06:44:04 -0700 Subject: [PATCH 551/607] Fix Mockito formatting for recent Dart versions There were changes to the formatter that made `lib/annotation.dart` badly formatted. PiperOrigin-RevId: 571001081 --- pkgs/mockito/lib/annotations.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 8fbeae00c..362db0287 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -149,10 +149,10 @@ class MockSpec { Symbol? as, @Deprecated('Avoid adding concrete implementation to mock classes. ' 'Use a manual implementation of the class without `Mock`') - List mixingIn = const [], + List mixingIn = const [], @Deprecated('Specify "missing stub" behavior with the ' '[onMissingStub] parameter.') - this.returnNullOnMissingStub = false, + this.returnNullOnMissingStub = false, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, this.onMissingStub, From b8d595102ee052eca49046ab8ac8bef4b67ca64f Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 4 Oct 2023 22:40:21 +0200 Subject: [PATCH 552/607] Use 3.1.3 as stable SDK --- pkgs/mockito/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 9c38218fd..30c65ea9e 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: - sdk: 2.19.0 + sdk: 3.1.3 - id: install name: Install dependencies run: dart pub get @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [2.19.0, dev] + sdk: [3.1.3, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 From 39273e7468bbbcfc129fdf7a1fa304a8468d5d23 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 5 Oct 2023 22:44:37 +0200 Subject: [PATCH 553/607] Use SDK 3.0.0 for stable tests --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 30c65ea9e..eac09cdd0 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: - sdk: 3.1.3 + sdk: 3.0.0 - id: install name: Install dependencies run: dart pub get @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [3.1.3, dev] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 @@ -59,7 +59,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [2.19.0, dev] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 From af7b3a1151d6f81d5d5c8b4cf24d125b19c3a0a8 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 5 Oct 2023 23:38:39 -0700 Subject: [PATCH 554/607] Change default dummy value for `String` to contain some info PiperOrigin-RevId: 571241586 --- pkgs/mockito/CHANGELOG.md | 4 ++++ pkgs/mockito/lib/src/builder.dart | 2 -- pkgs/mockito/lib/src/dummies.dart | 10 +++++--- pkgs/mockito/lib/src/mock.dart | 4 ++-- pkgs/mockito/pubspec.yaml | 2 +- .../mockito/test/builder/auto_mocks_test.dart | 5 ++-- .../test/end2end/generated_mocks_test.dart | 24 +++++++++++++++---- 7 files changed, 37 insertions(+), 14 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 4d1dba83a..b512d3a67 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,10 @@ * Require analyzer 5.12.0, allow analyzer version 6.x; * Add example of writing a class to mock function objects. * Add support for the `build_extensions` build.yaml option +* Require Dart >= 3.0.0. +* **Potentially breaking** Changed default `String` value returned by nice + mocks' unstubbed method to include some useful info. This could break the + tests that relied on getting an empty `String` from unstubbed methods. ## 5.4.2 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 48be22054..e354ed212 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1567,8 +1567,6 @@ class _MockClassInfo { ..url = 'dart:async' ..types.add(elementType); }).property('empty').call([]); - } else if (type.isDartCoreString) { - return literalString(''); } else if (type.isDartTypedDataSealed) { // These types (XXXList + ByteData) from dart:typed_data are // sealed, e.g. "non-subtypeable", but they diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index f5630c13a..71c77b62c 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -16,7 +16,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:typed_data'; -import 'mock.dart' show FakeFunctionUsedError; +import 'mock.dart' show FakeFunctionUsedError, PrettyString; import 'platform_dummies_js.dart' if (dart.library.io) 'platform_dummies_vm.dart'; @@ -24,7 +24,11 @@ import 'platform_dummies_js.dart' // String could totally contain an explanation. const int _dummyInt = 0; const double _dummyDouble = 0.0; -const String _dummyString = ''; + +// Create a dummy String with info on why it was created. +String _dummyString(Object o, Invocation i) => Uri.encodeComponent( + 'Dummy String created while calling ${i.toPrettyString()} on $o.' + .replaceAll(' ', '_')); // This covers functions with up to 20 positional arguments, for more arguments, // type arguments or named arguments, users would have to provide a dummy @@ -104,7 +108,7 @@ Map _defaultDummyBuilders = { int: (_, _i) => _dummyInt, num: (_, _i) => _dummyInt, double: (_, _i) => _dummyDouble, - String: (_, _i) => _dummyString, + String: _dummyString, Int8List: (_, _i) => Int8List(0), Int16List: (_, _i) => Int16List(0), Int32List: (_, _i) => Int32List(0), diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index f988cf10a..d53b017cf 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -123,7 +123,7 @@ void throwOnMissingStub( /// be generated. As such, [Mock] 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). -class Mock { +mixin class Mock { static Null _answerNull(_) => null; static const _nullResponse = CallPair.allInvocations(_answerNull); @@ -1270,7 +1270,7 @@ void resetMockitoState() { resetDummyBuilders(); } -extension on Invocation { +extension PrettyString on Invocation { /// Returns a pretty String representing a method (or getter or setter) call /// including its arguments, separating elements with newlines when it should /// improve readability. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 6d9c55618..12c2051a9 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,7 +6,7 @@ description: >- repository: https://github.com/dart-lang/mockito environment: - sdk: '>=2.19.0 <4.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: analyzer: '>=5.12.0 <7.0.0' diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 31a62447b..b275cc53d 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2259,14 +2259,15 @@ void main() { ); }); - test('creates dummy non-null String return value', () async { + test('calls dummyValue to get a dummy non-null String return value', + () async { await expectSingleNonNullableOutput( dedent(r''' class Foo { String m() => "Hello"; } '''), - _containsAllOf("returnValue: '',"), + _containsAllOf('returnValue: _i3.dummyValue('), ); }); diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 64573f436..d081ee266 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -263,11 +263,19 @@ void main() { test('an unstubbed method returns a value', () { when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); - expect(foo.namedParameter(x: 43), equals('')); + expect( + foo.namedParameter(x: 43), + contains(Uri.encodeComponent( + 'Dummy String created while calling namedParameter({x: 43})' + .replaceAll(' ', '_')))); }); test('an unstubbed getter returns a value', () { - expect(foo.getter, equals('')); + expect( + foo.getter, + contains(Uri.encodeComponent( + 'Dummy String created while calling getter' + .replaceAll(' ', '_')))); }); }); @@ -280,11 +288,19 @@ void main() { test('an unstubbed method returns a value', () { when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); - expect(foo.namedParameter(x: 43), equals('')); + expect( + foo.namedParameter(x: 43), + contains(Uri.encodeComponent( + 'Dummy String created while calling namedParameter({x: 43})' + .replaceAll(' ', '_')))); }); test('an unstubbed getter returns a value', () { - expect(foo.getter, equals('')); + expect( + foo.getter, + contains(Uri.encodeComponent( + 'Dummy String created while calling getter' + .replaceAll(' ', '_')))); }); test('an unstubbed method returning non-core type returns a fake', () { From 214ed7f0dcd2d2976c3bd7e3bc62be78a125151f Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 6 Oct 2023 07:35:36 -0700 Subject: [PATCH 555/607] Undo the formatting fix We ended up only bumping CI to Dart 3.0.0, and in 3.0.0 the old formatting is the correct one. PiperOrigin-RevId: 571333640 --- pkgs/mockito/lib/annotations.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 362db0287..8fbeae00c 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -149,10 +149,10 @@ class MockSpec { Symbol? as, @Deprecated('Avoid adding concrete implementation to mock classes. ' 'Use a manual implementation of the class without `Mock`') - List mixingIn = const [], + List mixingIn = const [], @Deprecated('Specify "missing stub" behavior with the ' '[onMissingStub] parameter.') - this.returnNullOnMissingStub = false, + this.returnNullOnMissingStub = false, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, this.onMissingStub, From 455ef2514260a6a15ccf85b31c6e61439a9153c6 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Fri, 6 Oct 2023 07:36:19 -0700 Subject: [PATCH 556/607] Don't try to compare fakes to real objects Real object comparison sometimes tries to access private members, which are obvously missing on fakes. This results in confusing errors. To prevent those, just fail the comparison immediately if one thing is a fake while the other one is not. PiperOrigin-RevId: 571333761 --- pkgs/mockito/lib/src/invocation_matcher.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkgs/mockito/lib/src/invocation_matcher.dart b/pkgs/mockito/lib/src/invocation_matcher.dart index 005eb0500..d809c19a2 100644 --- a/pkgs/mockito/lib/src/invocation_matcher.dart +++ b/pkgs/mockito/lib/src/invocation_matcher.dart @@ -171,6 +171,11 @@ class _MatcherEquality extends DeepCollectionEquality /* */ { if (e2 is Matcher && e1 is! Matcher) { return e2.matches(e1, {}); } + // If one thing is a `SmartFake` but not the other, always fail. + // Otherwise the real thing might try calling private members on + // fake, and that fails at runtime with confusing errors. + if ((e1 is SmartFake) ^ (e2 is SmartFake)) return false; + return super.equals(e1, e2); } From c99057c0e284418f92c25dd765022fb7fed134c2 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 18 Oct 2023 04:42:38 -0700 Subject: [PATCH 557/607] Change `!= null` to `is T` to handle "double nullable" case We use `dummyValueOrNull`/`ifNotNull` combination to return a proper `Future` when `T` is not known at compile time but a dummy value for `T` exists at run time, instead of falling back to creating a `FakeFuture` (that can't be `await`ed for example). This works fine unless `T` itself allows `null` (is nullable or `void`). In this case `dummyValueOrNull` returns `null`, but it's a proper `T`, not a failure. This CL changes `ifNotNull` to check `is T` instead of `!= null` to handle this properly. PiperOrigin-RevId: 574437205 --- pkgs/mockito/lib/src/dummies.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index 71c77b62c..7d85a6288 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -174,4 +174,4 @@ void resetDummyBuilders() { // Helper function. R? ifNotNull(T? value, R Function(T) action) => - value != null ? action(value) : null; + value is T ? action(value) : null; From 15fb53c152347a429ea30a75ceff2678ca115a13 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 26 Oct 2023 02:37:43 -0700 Subject: [PATCH 558/607] Remove deprecated `returnNullOnMissingStub` and `OnMissingStub.returnNull` This option results in runtime type errors and was superseeded by `OnMissingStub.returnDefault`. PiperOrigin-RevId: 576796424 --- pkgs/mockito/CHANGELOG.md | 2 + pkgs/mockito/NULL_SAFETY_README.md | 13 ------ pkgs/mockito/lib/annotations.dart | 26 +---------- pkgs/mockito/lib/src/builder.dart | 44 +++---------------- .../mockito/test/builder/auto_mocks_test.dart | 3 -- .../test/builder/custom_mocks_test.dart | 23 +--------- .../test/end2end/generated_mocks_test.dart | 42 +++--------------- 7 files changed, 15 insertions(+), 138 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index b512d3a67..46e0b5350 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -7,6 +7,8 @@ * **Potentially breaking** Changed default `String` value returned by nice mocks' unstubbed method to include some useful info. This could break the tests that relied on getting an empty `String` from unstubbed methods. +* Remove deprecated `returnNullOnMissingStub` and `OnMissingStub.returnNull` + `MockSpec` options. ## 5.4.2 diff --git a/pkgs/mockito/NULL_SAFETY_README.md b/pkgs/mockito/NULL_SAFETY_README.md index 616fbd2fa..ebf243423 100644 --- a/pkgs/mockito/NULL_SAFETY_README.md +++ b/pkgs/mockito/NULL_SAFETY_README.md @@ -159,19 +159,6 @@ behavior, use `@GenerateMocks`: @GenerateMocks([Foo]) ``` -#### Old "return null" missing stub behavior - - To use the old default behavior of returning null (which doesn't make a lot of -sense in the Null safety type system), for legacy code, use -`returnNullOnMissingStub`: - -```dart -@GenerateMocks([], customMocks: [MockSpec(returnNullOnMissingStub: true)]) -``` - -This option is soft-deprecated (no deprecation warning yet); it will be marked -`@deprecated` in a future release, and removed in a later release. - #### Mocking members with non-nullable type variable return types If a class has a member with a type variable as a return type (for example, diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index 8fbeae00c..aebfba228 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -78,9 +78,6 @@ class GenerateNiceMocks { /// The name of the mock class is either specified with the `as` named argument, /// or is the name of the class being mocked, prefixed with 'Mock'. /// -/// To use the legacy behavior of returning null for unstubbed methods, use -/// `returnNullOnMissingStub: true`. -/// /// For example, given the generic class, `class Foo`, then this /// annotation: /// @@ -99,10 +96,6 @@ class MockSpec { final List mixins; - @Deprecated('Specify "missing stub" behavior with the [onMissingStub] ' - 'parameter.') - final bool returnNullOnMissingStub; - final OnMissingStub? onMissingStub; final Set unsupportedMembers; @@ -117,12 +110,6 @@ class MockSpec { /// /// Specify a custom name with the [as] parameter. /// - /// If [onMissingStub] is specified as [OnMissingStub.returnNull], - /// (or if the deprecated parameter [returnNullOnMissingStub] is `true`), then - /// a real call to a mock method (or getter) will return `null` when no stub - /// is found. This may result in a runtime error, if the return type of the - /// method (or the getter) is non-nullable. - /// /// If [onMissingStub] is specified as /// [OnMissingStub.returnDefault], a real call to a mock method (or /// getter) will return a legal value when no stub is found. @@ -149,10 +136,7 @@ class MockSpec { Symbol? as, @Deprecated('Avoid adding concrete implementation to mock classes. ' 'Use a manual implementation of the class without `Mock`') - List mixingIn = const [], - @Deprecated('Specify "missing stub" behavior with the ' - '[onMissingStub] parameter.') - this.returnNullOnMissingStub = false, + List mixingIn = const [], this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, this.onMissingStub, @@ -166,14 +150,6 @@ enum OnMissingStub { /// An exception should be thrown. throwException, - /// A `null` value should be returned. - /// - /// This is considered legacy behavior, as it may result in a runtime error, - /// if the return type of the method (or the getter) is non-nullable. - @Deprecated( - 'This is legacy behavior, it may result in runtime errors. Consider using returnDefault instead') - returnNull, - /// A legal default value should be returned. /// /// For basic known types, like `int` and `Future`, a simple value is diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index e354ed212..4e817871e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -648,47 +648,15 @@ class _MockTargetGatherer { mixins.add(mixinInterfaceType); } - final returnNullOnMissingStub = - mockSpec.getField('returnNullOnMissingStub')!.toBoolValue()!; final onMissingStubValue = mockSpec.getField('onMissingStub')!; final OnMissingStub onMissingStub; - if (nice) { - // The new @GenerateNiceMocks API. Don't allow `returnNullOnMissingStub`. - if (returnNullOnMissingStub) { - throw ArgumentError("'returnNullOnMissingStub' is not supported with " - '@GenerateNiceMocks'); - } - if (onMissingStubValue.isNull) { - onMissingStub = OnMissingStub.returnDefault; - } else { - final onMissingStubIndex = - onMissingStubValue.getField('index')!.toIntValue()!; - onMissingStub = OnMissingStub.values[onMissingStubIndex]; - // ignore: deprecated_member_use_from_same_package - if (onMissingStub == OnMissingStub.returnNull) { - throw ArgumentError( - "'OnMissingStub.returnNull' is not supported with " - '@GenerateNiceMocks'); - } - } + if (onMissingStubValue.isNull) { + onMissingStub = + nice ? OnMissingStub.returnDefault : OnMissingStub.throwException; } else { - if (onMissingStubValue.isNull) { - // No value was given for `onMissingStub`. But the behavior may - // be specified by `returnNullOnMissingStub`. - onMissingStub = returnNullOnMissingStub - // ignore: deprecated_member_use_from_same_package - ? OnMissingStub.returnNull - : OnMissingStub.throwException; - } else { - // A value was given for `onMissingStub`. - if (returnNullOnMissingStub) { - throw ArgumentError("Cannot specify 'returnNullOnMissingStub' and a " - "'onMissingStub' value at the same time."); - } - final onMissingStubIndex = - onMissingStubValue.getField('index')!.toIntValue()!; - onMissingStub = OnMissingStub.values[onMissingStubIndex]; - } + final onMissingStubIndex = + onMissingStubValue.getField('index')!.toIntValue()!; + onMissingStub = OnMissingStub.values[onMissingStubIndex]; } final unsupportedMembers = { for (final m in mockSpec.getField('unsupportedMembers')!.toSetValue()!) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index b275cc53d..34e218bbd 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -34,15 +34,12 @@ class GenerateMocks { class MockSpec { final Symbol? mockName; - final bool returnNullOnMissingStub; - final Set unsupportedMembers; final Map fallbackGenerators; const MockSpec({ Symbol? as, - this.returnNullOnMissingStub = false, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, }) diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 9fac876b7..e725f4802 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -43,8 +43,6 @@ class MockSpec { final List mixins; - final bool returnNullOnMissingStub; - final OnMissingStub? onMissingStub; final Set unsupportedMembers; @@ -54,7 +52,6 @@ class MockSpec { const MockSpec({ Symbol? as, List mixingIn = const [], - this.returnNullOnMissingStub = false, this.onMissingStub, this.unsupportedMembers = const {}, this.fallbackGenerators = const {}, @@ -62,7 +59,7 @@ class MockSpec { mixins = mixingIn; } -enum OnMissingStub { throwException, returnNull, returnDefault } +enum OnMissingStub { throwException, returnDefault } ''' }; @@ -438,24 +435,6 @@ void main() { ); }); - test( - 'generates a mock class which uses the old behavior of returning null on ' - 'missing stubs', () async { - final mocksContent = await buildWithNonNullable({ - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - class Foo {} - '''), - 'foo|test/foo_test.dart': ''' - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - @GenerateMocks([], customMocks: [MockSpec(as: #MockFoo, returnNullOnMissingStub: true)]) - void main() {} - ''' - }); - expect(mocksContent, isNot(contains('throwOnMissingStub'))); - }); - test( 'generates mock methods with private return types, given ' 'unsupportedMembers', () async { diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index d081ee266..ea1fdddc2 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -11,12 +11,10 @@ import 'generated_mocks_test.mocks.dart'; FooSub, Bar ], customMocks: [ - MockSpec(as: #MockFooRelaxed, returnNullOnMissingStub: true), MockSpec( as: #MockFooWithDefaults, onMissingStub: OnMissingStub.returnDefault, ), - MockSpec(as: #MockBarRelaxed, returnNullOnMissingStub: true), MockSpec( as: #MockBazWithUnsupportedMembers, unsupportedMembers: { @@ -28,7 +26,8 @@ import 'generated_mocks_test.mocks.dart'; ), MockSpec(mixingIn: [HasPrivateMixin]), ]) -@GenerateNiceMocks([MockSpec(as: #MockFooNice)]) +@GenerateNiceMocks( + [MockSpec(as: #MockFooNice), MockSpec(as: #MockBarNice)]) void main() { group('for a generated mock,', () { late MockFoo foo; @@ -225,35 +224,6 @@ void main() { }); }); - group('for a generated mock using returnNullOnMissingStub,', () { - late Foo foo; - - setUp(() { - foo = MockFooRelaxed(); - }); - - test('an unstubbed method returning a non-nullable type throws a TypeError', - () { - when(foo.namedParameter(x: 42)).thenReturn('Stubbed'); - expect( - () => foo.namedParameter(x: 43), throwsA(TypeMatcher())); - }); - - test('an unstubbed getter returning a non-nullable type throws a TypeError', - () { - expect(() => foo.getter, throwsA(TypeMatcher())); - }); - - test('an unstubbed method returning a nullable type returns null', () { - when(foo.nullableMethod(42)).thenReturn('Stubbed'); - expect(foo.nullableMethod(43), isNull); - }); - - test('an unstubbed getter returning a nullable type returns null', () { - expect(foo.nullableGetter, isNull); - }); - }); - group('for a generated mock using OnMissingStub.returnDefault,', () { late Foo foo; @@ -356,11 +326,9 @@ void main() { expect(foo.methodWithBarArg(bar), equals('mocked result')); }); - test( - 'a generated mock which returns null on missing stubs can be used as a ' - 'stub argument', () { - final foo = MockFooRelaxed(); - final bar = MockBarRelaxed(); + test('a generated nice mock can be used as a stub argument', () { + final foo = MockFoo(); + final bar = MockBarNice(); when(foo.methodWithBarArg(bar)).thenReturn('mocked result'); expect(foo.methodWithBarArg(bar), equals('mocked result')); }); From 7e4cf5868f0e273672ce8d635415580abf34b8b7 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 26 Oct 2023 02:49:35 -0700 Subject: [PATCH 559/607] Remove unneeded deprecation warning disable comment `returnNullOnMissingStub` was removed, so we can drop this comment as well. PiperOrigin-RevId: 576799717 --- pkgs/mockito/lib/annotations.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkgs/mockito/lib/annotations.dart b/pkgs/mockito/lib/annotations.dart index aebfba228..740a8b0ce 100644 --- a/pkgs/mockito/lib/annotations.dart +++ b/pkgs/mockito/lib/annotations.dart @@ -102,10 +102,6 @@ class MockSpec { final Map fallbackGenerators; - // This is here for the doc comment references to `returnNullOnMissingStub`. - // Remove when those are gone. - // ignore_for_file: deprecated_member_use_from_same_package - /// Constructs a custom mock specification. /// /// Specify a custom name with the [as] parameter. From d8f2dbbdeabc65826e6a363f8da52fc29abf92c5 Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 26 Oct 2023 12:04:08 +0200 Subject: [PATCH 560/607] Use Dart SDK 3.1.0 for format check To make the check happy. There was a change to formatter and since we format with a HEAD version of dart format internally, now the format check is unhappy @ github. --- pkgs/mockito/.github/workflows/test-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index eac09cdd0..e67ab4263 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: - sdk: 3.0.0 + sdk: 3.1.0 - id: install name: Install dependencies run: dart pub get From 6038d6e6741e511517d4dbafd90eedd05e1e2aaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 20:22:07 +0000 Subject: [PATCH 561/607] Bump dart-lang/setup-dart from 1.5.1 to 1.6.0 (dart-lang/mockito#712) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.1 to 1.6.0.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/mockito#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/mockito#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

  • Added a flavor option setup.sh to allow downloading unpublished builds.

v1.0.0

  • Promoted to 1.0 stable.

v0.5

  • Fixed a Windows pub global activate path issue.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.5.1&new-version=1.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/mockito/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index e67ab4263..9efcf59ed 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: 3.1.0 - id: install @@ -40,7 +40,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install @@ -62,7 +62,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install @@ -87,7 +87,7 @@ jobs: os: [ubuntu-latest] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: dev - id: install From a8a6d0116182e1450df0c3c5f1a7c905d16388bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 20:29:32 +0000 Subject: [PATCH 562/607] Bump actions/checkout from 4.1.0 to 4.1.1 (dart-lang/mockito#713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1.
Release notes

Sourced from actions/checkout's releases.

v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.0...v4.1.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.0&new-version=4.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/mockito/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 9efcf59ed..c9206940c 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: 3.1.0 @@ -39,7 +39,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} @@ -61,7 +61,7 @@ jobs: os: [ubuntu-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} @@ -86,7 +86,7 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: dev From a0a57f524e332227512eb510955b662a34d11a1c Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Wed, 15 Nov 2023 07:33:38 -0800 Subject: [PATCH 563/607] Generate valid functions with optional non-nullable arguments If a method of a mocked class returns a function with optional yet non-nullable argument, Mockito currently produces something like ```dart (int x, {int y}) => /* doesn't matter */ ``` which is not a valid Dart, since `y` must either be `required` or have a default value. `required` won't fit into the desired type and coming up with a default value is not easy, since it has to be a constant, but `dummyValue` doesn't always produce one. Fortunately, we can just widen the type here and make it nullable. PiperOrigin-RevId: 582669927 --- pkgs/mockito/lib/src/builder.dart | 5 +++-- pkgs/mockito/test/builder/auto_mocks_test.dart | 4 ++-- pkgs/mockito/test/end2end/foo.dart | 3 +++ .../test/end2end/generated_mocks_test.dart | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 4e817871e..f12e673d8 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1580,12 +1580,13 @@ class _MockClassInfo { } else if (parameter.isOptionalPositional) { final matchingParameter = _matchingParameter(parameter, superParameterType: parameter.type, - defaultName: '__p$position'); + defaultName: '__p$position', + forceNullable: true); b.optionalParameters.add(matchingParameter); position++; } else if (parameter.isOptionalNamed) { final matchingParameter = _matchingParameter(parameter, - superParameterType: parameter.type); + superParameterType: parameter.type, forceNullable: true); b.optionalParameters.add(matchingParameter); } else if (parameter.isRequiredNamed) { final matchingParameter = _matchingParameter(parameter, diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 34e218bbd..5840c9a1d 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2414,7 +2414,7 @@ void main() { _containsAllOf(''' returnValue: ( int __p0, [ - String __p1, + String? __p1, ]) {},'''), ); }); @@ -2431,7 +2431,7 @@ void main() { _containsAllOf(''' returnValue: ( _i2.Foo __p0, { - bool b, + bool? b, }) {},'''), ); }); diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index d4e26caf6..46bd60f28 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -22,6 +22,9 @@ class Foo { Future? returnsNullableFutureVoid() => Future.value(); Future returnsFuture(T x) => Future.value(x); Bar returnsBar(int arg) => Bar(); + String Function(int x, [String s]) returnsFunction() => (x, [s = '']) => ''; + String Function(int x, {String s}) returnsFunctionNamed() => + (x, {s = ''}) => ''; } class Bar { diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index ea1fdddc2..51d196716 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -163,6 +163,22 @@ void main() { when(foo.returnsFuture(any)).thenAnswer((_) async => 1); expect(await foo.returnsFuture(0), 1); }); + + test( + 'a method returning a function with optional non-nullable argument ' + 'can be stubbed', () { + when(foo.returnsFunction()).thenReturn((x, [s = 'x']) => s + s); + expect(foo.returnsFunction()(1), equals('xx')); + expect(foo.returnsFunction()(1, 'y'), equals('yy')); + }); + + test( + 'a method returning a function with named optional non-nullable ' + 'argument can be stubbed', () { + when(foo.returnsFunctionNamed()).thenReturn((x, {s = 'x'}) => s + s); + expect(foo.returnsFunctionNamed()(1), equals('xx')); + expect(foo.returnsFunctionNamed()(1, s: 'y'), equals('yy')); + }); }); group('for a generated mock using unsupportedMembers', () { From f3e3fc1e0c7685898bc8fc68ae039783cecb68a1 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 16 Nov 2023 09:48:41 -0800 Subject: [PATCH 564/607] Update to the latest lints, require Dart 3.1, prepare release --- .../.github/workflows/test-package.yml | 4 +-- pkgs/mockito/CHANGELOG.md | 4 +-- pkgs/mockito/lib/src/builder.dart | 18 ++++------- pkgs/mockito/lib/src/dummies.dart | 30 +++++++++---------- pkgs/mockito/lib/src/mock.dart | 2 +- pkgs/mockito/pubspec.yaml | 16 +++++----- pkgs/mockito/test/end2end/foo.dart | 2 ++ .../test/end2end/generated_mocks_test.dart | 1 + 8 files changed, 37 insertions(+), 40 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index c9206940c..7f607b5cd 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [3.0.0, dev] + sdk: [3.1.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d @@ -59,7 +59,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [3.0.0, dev] + sdk: [3.1.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 46e0b5350..4a761a6ce 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,9 +1,9 @@ -## 5.4.3-wip +## 5.4.3 * Require analyzer 5.12.0, allow analyzer version 6.x; * Add example of writing a class to mock function objects. * Add support for the `build_extensions` build.yaml option -* Require Dart >= 3.0.0. +* Require Dart >=3.1.0. * **Potentially breaking** Changed default `String` value returned by nice mocks' unstubbed method to include some useful info. This could break the tests that relied on getting an empty `String` from unstubbed methods. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index f12e673d8..85ab38a31 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -488,11 +488,9 @@ class _MockTargetGatherer { case 'GenerateMocks': mockTargets .addAll(_mockTargetsFromGenerateMocks(annotation, entryLib)); - break; case 'GenerateNiceMocks': mockTargets.addAll( _mockTargetsFromGenerateNiceMocks(annotation, entryLib)); - break; } } } @@ -2404,16 +2402,12 @@ extension on TypeSystem { extension on int { String get ordinal { final remainder = this % 10; - switch (remainder) { - case 1: - return '${this}st'; - case 2: - return '${this}nd'; - case 3: - return '${this}rd'; - default: - return '${this}th'; - } + return switch (remainder) { + 1 => '${this}st', + 2 => '${this}nd', + 3 => '${this}rd', + _ => '${this}th' + }; } } diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index 7d85a6288..f5b48c164 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -104,22 +104,22 @@ typedef DummyBuilder = T Function(Object parent, Invocation invocation); Map _dummyBuilders = {}; Map _defaultDummyBuilders = { - bool: (_, _i) => false, - int: (_, _i) => _dummyInt, - num: (_, _i) => _dummyInt, - double: (_, _i) => _dummyDouble, + bool: (_, __) => false, + int: (_, __) => _dummyInt, + num: (_, __) => _dummyInt, + double: (_, __) => _dummyDouble, String: _dummyString, - Int8List: (_, _i) => Int8List(0), - Int16List: (_, _i) => Int16List(0), - Int32List: (_, _i) => Int32List(0), - Int64List: (_, _i) => Int64List(0), - Uint8List: (_, _i) => Uint8List(0), - Uint16List: (_, _i) => Uint16List(0), - Uint32List: (_, _i) => Uint32List(0), - Uint64List: (_, _i) => Uint64List(0), - Float32List: (_, _i) => Float32List(0), - Float64List: (_, _i) => Float64List(0), - ByteData: (_, _i) => ByteData(0), + Int8List: (_, __) => Int8List(0), + Int16List: (_, __) => Int16List(0), + Int32List: (_, __) => Int32List(0), + Int64List: (_, __) => Int64List(0), + Uint8List: (_, __) => Uint8List(0), + Uint16List: (_, __) => Uint16List(0), + Uint32List: (_, __) => Uint32List(0), + Uint64List: (_, __) => Uint64List(0), + Float32List: (_, __) => Float32List(0), + Float64List: (_, __) => Float64List(0), + ByteData: (_, __) => ByteData(0), ...platformDummies, }; diff --git a/pkgs/mockito/lib/src/mock.dart b/pkgs/mockito/lib/src/mock.dart index d53b017cf..d99b39db1 100644 --- a/pkgs/mockito/lib/src/mock.dart +++ b/pkgs/mockito/lib/src/mock.dart @@ -235,7 +235,7 @@ class SmartFake { // make Analyzer happy, if we fake classes that override `==` to // accept `Object?` or `dynamic` (most notably [Interceptor]). @override - // ignore: non_nullable_equals_parameter + // ignore: non_nullable_equals_parameter, hash_and_equals bool operator ==(Object? other) => identical(this, other); @override diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 12c2051a9..912b2313f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,30 +1,30 @@ name: mockito -version: 5.4.3-wip +version: 5.4.3 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. repository: https://github.com/dart-lang/mockito environment: - sdk: '>=3.0.0 <4.0.0' + sdk: ^3.1.0 dependencies: analyzer: '>=5.12.0 <7.0.0' - build: '>=1.3.0 <3.0.0' + build: ^2.0.0 code_builder: ^4.5.0 collection: ^1.15.0 - dart_style: '>=1.3.6 <3.0.0' + dart_style: ^2.0.0 matcher: ^0.12.15 meta: ^1.3.0 path: ^1.8.0 - source_gen: '>=0.9.6 <2.0.0' + source_gen: ^1.0.0 test_api: '>=0.2.1 <0.7.0' dev_dependencies: build_runner: ^2.0.0 build_test: ^2.0.0 build_web_compilers: '>=3.0.0 <5.0.0' - http: '>=0.13.0 <2.0.0' - lints: ^2.0.0 - package_config: '>=1.9.3 <3.0.0' + http: ^1.0.0 + lints: ^3.0.0 + package_config: ^2.0.0 test: ^1.16.0 diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 46bd60f28..857321da7 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -1,3 +1,5 @@ +// ignore_for_file: library_private_types_in_public_api + import 'foo_sub.dart'; class Foo { diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index 51d196716..a68e0e1c0 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -24,6 +24,7 @@ import 'generated_mocks_test.mocks.dart'; #$hasDollarInName, }, ), + // ignore: deprecated_member_use_from_same_package MockSpec(mixingIn: [HasPrivateMixin]), ]) @GenerateNiceMocks( From 8b67b2277230ca75234ecc28a5eaad759f595792 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 16 Nov 2023 10:27:04 -0800 Subject: [PATCH 565/607] release later --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 4a761a6ce..9ec1aab65 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.3 +## 5.4.3-wip * Require analyzer 5.12.0, allow analyzer version 6.x; * Add example of writing a class to mock function objects. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 912b2313f..3f5659024 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.3 +version: 5.4.3-wip description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From fdf4107bcf0d74008eeb480b45c7618f65e4e086 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 16 Nov 2023 13:46:57 -0800 Subject: [PATCH 566/607] No public description PiperOrigin-RevId: 583151331 --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 9ec1aab65..4a761a6ce 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.3-wip +## 5.4.3 * Require analyzer 5.12.0, allow analyzer version 6.x; * Add example of writing a class to mock function objects. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index e0b6f302a..48d6ef445 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.3-wip'; +const packageVersion = '5.4.3'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 3f5659024..912b2313f 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.3-wip +version: 5.4.3 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From 2f2619590d5e8c10205fafd65664f1e1a8dae051 Mon Sep 17 00:00:00 2001 From: Sebastian Schneider Date: Tue, 21 Nov 2023 12:49:25 +0100 Subject: [PATCH 567/607] Use `posix` style for local imports --- pkgs/mockito/lib/src/builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 85ab38a31..1cf152741 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -220,8 +220,8 @@ $rawOutput if (typeAssetId.path.startsWith('lib/')) { typeUris[element] = typeAssetId.uri.toString(); } else { - typeUris[element] = - p.relative(typeAssetId.path, from: p.dirname(entryAssetPath)); + typeUris[element] = p.Context(style: p.Style.posix) + .relative(typeAssetId.path, from: p.dirname(entryAssetPath)); } } on UnresolvableAssetException { // Asset may be in a summary. From 1b2ccd1cc9eccab8bb59a0cd358bf4044d5b0677 Mon Sep 17 00:00:00 2001 From: Sese Schneider Date: Wed, 6 Dec 2023 11:42:29 +0100 Subject: [PATCH 568/607] Apply suggestions from code review --- pkgs/mockito/lib/src/builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 1cf152741..381b3a848 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -220,8 +220,8 @@ $rawOutput if (typeAssetId.path.startsWith('lib/')) { typeUris[element] = typeAssetId.uri.toString(); } else { - typeUris[element] = p.Context(style: p.Style.posix) - .relative(typeAssetId.path, from: p.dirname(entryAssetPath)); + typeUris[element] = p.posix.relative(typeAssetId.path, + from: p.posix.dirname(entryAssetPath)); } } on UnresolvableAssetException { // Asset may be in a summary. From 899338105a56dea2181d0457bbc40f5773e40b63 Mon Sep 17 00:00:00 2001 From: Paul Berry Date: Tue, 12 Dec 2023 14:18:12 -0800 Subject: [PATCH 569/607] Remove mockito pre-null-safety tests. Now that all source code in google3 has been migrated to use Dart language version 2.12 or greater, this test case isn't needed anymore. Removing it helps unblock the removal of legacy (pre-null-safety) support from the Dart analyzer. PiperOrigin-RevId: 590334278 --- .../mockito/test/builder/auto_mocks_test.dart | 72 --------------- .../test/builder/custom_mocks_test.dart | 92 ------------------- 2 files changed, 164 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 5840c9a1d..2b7f6bb12 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -79,19 +79,6 @@ void main() {} void main() { late InMemoryAssetWriter writer; - /// Test [MockBuilder] in a package which has not opted into null safety. - Future testPreNonNullable(Map sourceAssets, - {Map*/ Object>? outputs, - Map config = const {}}) async { - final packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 7)) - ]); - await testBuilder(buildMocks(BuilderOptions(config)), sourceAssets, - writer: writer, outputs: outputs, packageConfig: packageConfig); - } - /// Test [MockBuilder] in a package which has opted into null safety. Future testWithNonNullable(Map sourceAssets, {Map>*/ Object>? outputs, @@ -3375,65 +3362,6 @@ void main() { ); }); - test('given a pre-non-nullable library, includes an opt-out comment', - () async { - await testPreNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - int f(int a); - } - '''), - }, - outputs: {'foo|test/foo_test.mocks.dart': _containsAllOf('// @dart=2.9')}, - ); - }); - - test('given a pre-non-nullable library, does not override any members', - () async { - await testPreNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - int f(int a); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - MockFoo() { - _i1.throwOnMissingStub(this); - } - } - ''')) - }, - ); - }); - - test('given a pre-non-nullable library, overrides toString if necessary', - () async { - await testPreNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - String toString({bool a = false}); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf( - 'String toString({bool a = false}) => super.toString();') - }, - ); - }); - test( 'adds ignore: must_be_immutable analyzer comment if mocked class is ' 'immutable', () async { diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index e725f4802..b03ce4ca5 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -89,18 +89,6 @@ MockFoo() { void main() { late InMemoryAssetWriter writer; - /// Test [MockBuilder] in a package which has not opted into null safety. - Future testPreNonNullable(Map sourceAssets, - {Map*/ Object>? outputs}) async { - final packageConfig = PackageConfig([ - Package('foo', Uri.file('/foo/'), - packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(2, 7)) - ]); - await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, - outputs: outputs, packageConfig: packageConfig); - } - /// Builds with [MockBuilder] in a package which has opted into null safety, /// returning the content of the generated mocks library. Future buildWithNonNullable(Map sourceAssets) async { @@ -622,34 +610,6 @@ void main() { expect(mocksContent, contains('returnValueForMissingStub: 0,')); }); - test( - 'generates mock methods with non-nullable return types, specifying ' - 'legal default values for basic known types, in mixed mode', () async { - final mocksContent = await buildWithNonNullable({ - ...annotationsAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - int m({required int x, double? y}); - } - '''), - 'foo|test/foo_test.dart': ''' - // @dart=2.9 - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - - @GenerateMocks( - [], - customMocks: [ - MockSpec(onMissingStub: OnMissingStub.returnDefault), - ], - ) - void main() {} - ''' - }); - expect(mocksContent, contains('returnValue: 0,')); - expect(mocksContent, contains('returnValueForMissingStub: 0,')); - }); - test( 'generates mock methods with non-nullable return types, specifying ' 'legal default values for unknown types', () async { @@ -1273,58 +1233,6 @@ void main() { ); }); - test('given a pre-non-nullable library, does not override any members', - () async { - await testPreNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(r''' - abstract class Foo { - int f(int a); - } - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')) - }, - ); - }); - - test( - 'given a pre-non-nullable safe library, does not write "?" on interface ' - 'types', () async { - await testPreNonNullable( - { - ...annotationsAsset, - ...simpleTestAsset, - 'foo|lib/foo.dart': dedent(''' - abstract class Foo { - int f(int a); - } - '''), - 'foo|test/foo_test.dart': dedent(''' - import 'package:foo/foo.dart'; - import 'package:mockito/annotations.dart'; - @GenerateMocks( - [], customMocks: [MockSpec>(as: #MockFoo)]) - void main() {} - '''), - }, - outputs: { - 'foo|test/foo_test.mocks.dart': _containsAllOf(dedent(''' - class MockFoo extends _i1.Mock implements _i2.Foo { - $_constructorWithThrowOnMissingStub - } - ''')) - }, - ); - }); - test( 'generates a mock class which uses the new behavior of returning ' 'a valid value for missing stubs, if GenerateNiceMocks were used', From b772e3e776310504b004badc531b3abee48cd551 Mon Sep 17 00:00:00 2001 From: jld3103 Date: Thu, 14 Dec 2023 09:48:54 +0100 Subject: [PATCH 570/607] chore(deps): Allow test_api 0.7 --- pkgs/mockito/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 912b2313f..bcd49696b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: meta: ^1.3.0 path: ^1.8.0 source_gen: ^1.0.0 - test_api: '>=0.2.1 <0.7.0' + test_api: '>=0.2.1 <0.8.0' dev_dependencies: build_runner: ^2.0.0 From c9c664bcf2561252199f87d3ce7d89a5f90fbd78 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 14 Dec 2023 12:25:01 -0800 Subject: [PATCH 571/607] Bump mockito to 5.4.4 PiperOrigin-RevId: 591018862 --- pkgs/mockito/CHANGELOG.md | 5 +++++ pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 4a761a6ce..55253622a 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.4.4 + +* Use `posix` style for local imports. +* Allow test_api ^0.7.0. + ## 5.4.3 * Require analyzer 5.12.0, allow analyzer version 6.x; diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 48d6ef445..5a7af3263 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.3'; +const packageVersion = '5.4.4'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index bcd49696b..e5585b589 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.3 +version: 5.4.4 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From 8c05b4096bd717b5d4a657edb76d3bb94c1b43aa Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Wed, 10 Jan 2024 06:33:54 -0800 Subject: [PATCH 572/607] Ignore "must_be_immutable" warning in generated files. Mocks cannot be made immutable anyway, but this way users aren't prevented from using generated mocks altogether. PiperOrigin-RevId: 597233416 --- pkgs/mockito/CHANGELOG.md | 6 ++++++ pkgs/mockito/lib/src/builder.dart | 1 + 2 files changed, 7 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 55253622a..42533c005 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.4.5-wip + +* Ignore "must_be_immutable" warning in generated files. Mocks cannot be made + immutable anyway, but this way users aren't prevented from using generated + mocks altogether. + ## 5.4.4 * Use `posix` style for local imports. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 381b3a848..35d8ed74e 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -123,6 +123,7 @@ class MockBuilder implements Builder { // example. b.body.add(Code( '// ignore_for_file: invalid_use_of_visible_for_testing_member\n')); + b.body.add(Code('// ignore_for_file: must_be_immutable\n')); b.body.add(Code('// ignore_for_file: prefer_const_constructors\n')); // The code_builder `asA` API unconditionally adds defensive parentheses. b.body.add(Code('// ignore_for_file: unnecessary_parenthesis\n')); From 144748b7bcddcad2ef4885cdddc90b80827521d8 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Mon, 5 Feb 2024 07:38:50 -0800 Subject: [PATCH 573/607] Sort `import` directives in all *.dart code PiperOrigin-RevId: 604314093 --- pkgs/mockito/analysis_options.yaml | 4 +++- pkgs/mockito/lib/mockito.dart | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/analysis_options.yaml b/pkgs/mockito/analysis_options.yaml index 0cfb93b34..6a7f4863c 100644 --- a/pkgs/mockito/analysis_options.yaml +++ b/pkgs/mockito/analysis_options.yaml @@ -6,6 +6,8 @@ analyzer: linter: rules: + - combinators_ordering - comment_references + - directives_ordering - test_types_in_equals - - throw_in_finally \ No newline at end of file + - throw_in_finally diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 6febba3e6..312fee4c9 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -15,6 +15,8 @@ // ignore: deprecated_member_use export 'package:test_api/fake.dart' show Fake; +export 'src/dummies.dart' + show provideDummy, provideDummyBuilder, MissingDummyValueError; export 'src/mock.dart' show Mock, @@ -53,5 +55,3 @@ export 'src/mock.dart' MissingStubError, FakeUsedError, FakeFunctionUsedError; -export 'src/dummies.dart' - show provideDummy, provideDummyBuilder, MissingDummyValueError; From 49dfce07d10ae033a51b3d89fce8f3eeff61831d Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Tue, 27 Feb 2024 01:51:38 -0800 Subject: [PATCH 574/607] Add basic extension types support This includes allowing extension types as both methods' arguments and return types. Trying to mock an extension type currently silently produces a mock of its representation, which is likely undesired, but currently I don't see a way to detect this. It looks like constant evaluation always produces erased types. PiperOrigin-RevId: 610676339 --- pkgs/mockito/CHANGELOG.md | 3 ++ pkgs/mockito/lib/src/builder.dart | 53 ++++++++++--------- pkgs/mockito/pubspec.yaml | 4 +- .../mockito/test/builder/auto_mocks_test.dart | 26 ++++++++- .../test/builder/custom_mocks_test.dart | 2 +- pkgs/mockito/test/end2end/foo.dart | 11 ++++ .../test/end2end/generated_mocks_test.dart | 36 ++++++++++++- 7 files changed, 102 insertions(+), 33 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 42533c005..435e30b32 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -3,6 +3,9 @@ * Ignore "must_be_immutable" warning in generated files. Mocks cannot be made immutable anyway, but this way users aren't prevented from using generated mocks altogether. +* Require Dart >= 3.3.0. +* Require analyzer 6.4.1. +* Add support for extension types. ## 5.4.4 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 35d8ed74e..ee71999f2 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1631,32 +1631,33 @@ class _MockClassInfo { } Expression _dummyValueImplementing( - analyzer.InterfaceType dartType, Expression invocation) { - final elementToFake = dartType.element; - if (elementToFake is EnumElement) { - return _typeReference(dartType).property( - elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); - } else if (elementToFake is ClassElement) { - if (elementToFake.isBase || - elementToFake.isFinal || - elementToFake.isSealed) { - // This class can't be faked, so try to call `dummyValue` to get - // a dummy value at run time. - // TODO(yanok): Consider checking subtypes, maybe some of them are - // implementable. - return _dummyValueFallbackToRuntime(dartType, invocation); - } - return _dummyFakedValue(dartType, invocation); - } else if (elementToFake is MixinElement) { - // This is a mixin and not a class. This should not happen in Dart 3, - // since it is not possible to have a value of mixin type. But we - // have to support this for reverse comptatibility. - return _dummyFakedValue(dartType, invocation); - } else { - throw StateError("Interface type '$dartType' which is nether an enum, " - 'nor a class, nor a mixin. This case is unknown, please report a bug.'); - } - } + analyzer.InterfaceType dartType, Expression invocation) => + switch (dartType.element) { + EnumElement(:final fields) => _typeReference(dartType) + .property(fields.firstWhere((f) => f.isEnumConstant).name), + ClassElement() && final element + when element.isBase || element.isFinal || element.isSealed => + // This class can't be faked, so try to call `dummyValue` to get + // a dummy value at run time. + // TODO(yanok): Consider checking subtypes, maybe some of them are + // implementable. + _dummyValueFallbackToRuntime(dartType, invocation), + ClassElement() => _dummyFakedValue(dartType, invocation), + MixinElement() => + // This is a mixin and not a class. This should not happen in Dart 3, + // since it is not possible to have a value of mixin type. But we + // have to support this for reverse comptatibility. + _dummyFakedValue(dartType, invocation), + ExtensionTypeElement(:final typeErasure) + when !typeErasure.containsPrivateName => + _dummyValue(typeErasure, invocation), + ExtensionTypeElement() => + _dummyValueFallbackToRuntime(dartType, invocation), + _ => throw StateError( + "Interface type '$dartType' which is neither an enum, " + 'nor a class, nor a mixin, nor an extension type. This case is ' + 'unknown, please report a bug.') + }; /// Adds a `Fake` implementation of [elementToFake], named [fakeName]. void _addFakeClass(String fakeName, InterfaceElement elementToFake) { diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index e5585b589..5cd36f6b1 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -6,10 +6,10 @@ description: >- repository: https://github.com/dart-lang/mockito environment: - sdk: ^3.1.0 + sdk: ^3.3.0 dependencies: - analyzer: '>=5.12.0 <7.0.0' + analyzer: '>=6.4.1 <7.0.0' build: ^2.0.0 code_builder: ^4.5.0 collection: ^1.15.0 diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 2b7f6bb12..1e6a0f369 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -86,7 +86,7 @@ void main() { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(3, 0)) + languageVersion: LanguageVersion(3, 3)) ]); await testBuilder(buildMocks(BuilderOptions(config)), sourceAssets, writer: writer, outputs: outputs, packageConfig: packageConfig); @@ -98,7 +98,7 @@ void main() { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(3, 0)) + languageVersion: LanguageVersion(3, 3)) ]); await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, @@ -3589,6 +3589,28 @@ void main() { }); }); + group('Extension types', () { + test('are supported as arguments', () async { + await expectSingleNonNullableOutput(dedent(''' + extension type E(int v) {} + class Foo { + int m(E e); + } + '''), _containsAllOf('int m(_i2.E? e)')); + }); + + test('are supported as return types', () async { + await expectSingleNonNullableOutput( + dedent(''' + extension type E(int v) {} + class Foo { + E get v; + } + '''), + decodedMatches( + allOf(contains('E get v'), contains('returnValue: 0')))); + }); + }); group('build_extensions support', () { test('should export mocks to different directory', () async { await testWithNonNullable({ diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index b03ce4ca5..2b987cf34 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -95,7 +95,7 @@ void main() { final packageConfig = PackageConfig([ Package('foo', Uri.file('/foo/'), packageUriRoot: Uri.file('/foo/lib/'), - languageVersion: LanguageVersion(3, 0)) + languageVersion: LanguageVersion(3, 3)) ]); await testBuilder(buildMocks(BuilderOptions({})), sourceAssets, writer: writer, packageConfig: packageConfig); diff --git a/pkgs/mockito/test/end2end/foo.dart b/pkgs/mockito/test/end2end/foo.dart index 857321da7..09f676fe6 100644 --- a/pkgs/mockito/test/end2end/foo.dart +++ b/pkgs/mockito/test/end2end/foo.dart @@ -64,3 +64,14 @@ mixin HasPrivateMixin implements HasPrivate { @override Object? _p; } + +extension type Ext(int x) {} + +extension type ExtOfPrivate(_Private private) {} + +class UsesExtTypes { + bool extTypeArg(Ext _) => true; + Ext extTypeReturn(int _) => Ext(42); + bool privateExtTypeArg(ExtOfPrivate _) => true; + ExtOfPrivate privateExtTypeReturn(int _) => ExtOfPrivate(private); +} diff --git a/pkgs/mockito/test/end2end/generated_mocks_test.dart b/pkgs/mockito/test/end2end/generated_mocks_test.dart index a68e0e1c0..ca891e9bc 100644 --- a/pkgs/mockito/test/end2end/generated_mocks_test.dart +++ b/pkgs/mockito/test/end2end/generated_mocks_test.dart @@ -27,8 +27,11 @@ import 'generated_mocks_test.mocks.dart'; // ignore: deprecated_member_use_from_same_package MockSpec(mixingIn: [HasPrivateMixin]), ]) -@GenerateNiceMocks( - [MockSpec(as: #MockFooNice), MockSpec(as: #MockBarNice)]) +@GenerateNiceMocks([ + MockSpec(as: #MockFooNice), + MockSpec(as: #MockBarNice), + MockSpec() +]) void main() { group('for a generated mock,', () { late MockFoo foo; @@ -334,6 +337,35 @@ void main() { expect(await foo.returnsFuture(MockBar()), bar); }); }); + + group('for a class using extension types', () { + late MockUsesExtTypes usesExtTypes; + + setUp(() { + usesExtTypes = MockUsesExtTypes(); + }); + + test( + 'a method using extension type as an argument can be stubbed with any', + () { + when(usesExtTypes.extTypeArg(any)).thenReturn(true); + expect(usesExtTypes.extTypeArg(Ext(42)), isTrue); + }); + + test( + 'a method using extension type as an argument can be stubbed with a ' + 'specific value', () { + when(usesExtTypes.extTypeArg(Ext(42))).thenReturn(true); + expect(usesExtTypes.extTypeArg(Ext(0)), isFalse); + expect(usesExtTypes.extTypeArg(Ext(42)), isTrue); + }); + + test('a method using extension type as a return type can be stubbed', () { + when(usesExtTypes.extTypeReturn(2)).thenReturn(Ext(42)); + expect(usesExtTypes.extTypeReturn(2), equals(Ext(42))); + expect(usesExtTypes.extTypeReturn(42), equals(Ext(0))); + }); + }); }); test('a generated mock can be used as a stub argument', () { From 390cbb0925d4925836437f722087056f1098faf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 20:58:33 +0000 Subject: [PATCH 575/607] Bump dart-lang/setup-dart from 1.6.0 to 1.6.2 Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.6.0 to 1.6.2. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/b64355ae6ca0b5d484f0106a033dd1388965d06d...fedb1266e91cf51be2fdb382869461a434b920a3) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pkgs/mockito/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 7f607b5cd..be6e74946 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: 3.1.0 - id: install @@ -40,7 +40,7 @@ jobs: sdk: [3.1.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install @@ -62,7 +62,7 @@ jobs: sdk: [3.1.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install @@ -87,7 +87,7 @@ jobs: os: [ubuntu-latest] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: dev - id: install From 762ef90a8e521de6063e550e850ed2527bfac63e Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Tue, 27 Feb 2024 10:54:01 +0100 Subject: [PATCH 576/607] Bump SDK version using in CI to 3.3 Mockito now requires Dart SDK 3.3, so bump the SDK version used in CI. --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index be6e74946..1dfee031c 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: - sdk: 3.1.0 + sdk: 3.3.0 - id: install name: Install dependencies run: dart pub get @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [3.1.0, dev] + sdk: [3.3.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 @@ -59,7 +59,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [3.1.0, dev] + sdk: [3.3.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 From 298a6efc541ae9de210d09c933518ceb6b2d721c Mon Sep 17 00:00:00 2001 From: Ilya Yanok Date: Thu, 11 Apr 2024 05:55:48 -0700 Subject: [PATCH 577/607] Fix one README example that were broken by https://github.com/dart-lang/mockito/commit/34b9fdc5018911eb3776f2663adebf2476d754be Also sync the executable version of it with the README. Fixes https://github.com/dart-lang/mockito/issues/744 PiperOrigin-RevId: 623800048 --- pkgs/mockito/README.md | 6 ++++-- pkgs/mockito/example/example.dart | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index 0189d40c9..a9fdfac62 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -170,7 +170,10 @@ In most cases, both plain arguments and argument matchers can be passed into mock methods: ```dart -// You can use plain arguments themselves +// You can use `any` +when(cat.eatFood(any)).thenReturn(false); + +// ... or plain arguments themselves when(cat.eatFood("fish")).thenReturn(true); // ... including collections @@ -178,7 +181,6 @@ when(cat.walk(["roof","tree"])).thenReturn(2); // ... or matchers when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false); -when(cat.eatFood(any)).thenReturn(false); // ... or mix arguments with matchers when(cat.eatFood(argThat(startsWith("dry")), hungry: true)).thenReturn(true); diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 1439ed2e5..56270cd21 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -88,8 +88,11 @@ void main() { }); test('Argument matchers', () { - // You can use plain arguments themselves - when(cat.eatFood('fish')).thenReturn(true); + // You can use `any` + when(cat.eatFood(any)).thenReturn(false); + + // ... or plain arguments themselves + when(cat.eatFood("fish")).thenReturn(true); // ... including collections when(cat.walk(['roof', 'tree'])).thenReturn(2); From 8c0cdd462615d9bd2e17e88bd6269980dce19c3b Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 7 May 2024 13:40:11 -0700 Subject: [PATCH 578/607] blast_repo fixes (dart-lang/mockito#749) dependabot --- pkgs/mockito/.github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/mockito/.github/dependabot.yml b/pkgs/mockito/.github/dependabot.yml index 90dffc509..7c3a9d907 100644 --- a/pkgs/mockito/.github/dependabot.yml +++ b/pkgs/mockito/.github/dependabot.yml @@ -9,3 +9,7 @@ updates: interval: monthly labels: - autosubmit + groups: + github-actions: + patterns: + - "*" From 5e5c877009eeaac11f31dde5f46128c3b8342770 Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 17 May 2024 09:51:20 -0700 Subject: [PATCH 579/607] Stop using deprecated LibraryElement.isNonNullableByDefault Prepare for https://dart-review.googlesource.com/c/sdk/+/366700 PiperOrigin-RevId: 634807230 --- pkgs/mockito/lib/src/builder.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index ee71999f2..4888f2cc8 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -74,7 +74,7 @@ class MockBuilder implements Builder { Future build(BuildStep buildStep) async { if (!await buildStep.resolver.isLibrary(buildStep.inputId)) return; final entryLib = await buildStep.inputLibrary; - final sourceLibIsNonNullable = entryLib.isNonNullableByDefault; + final sourceLibIsNonNullable = true; final mockLibraryAsset = buildStep.allowedOutputs.singleOrNull; if (mockLibraryAsset == null) { @@ -144,13 +144,11 @@ class MockBuilder implements Builder { // The source lib may be pre-null-safety because of an explicit opt-out // (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To // allow for this situation, we must also add an opt-out comment here. - final dartVersionComment = sourceLibIsNonNullable ? '' : '// @dart=2.9'; final mockLibraryContent = DartFormatter().format(''' // Mocks generated by Mockito $packageVersion from annotations // in ${entryLib.definingCompilationUnit.source.uri.path}. // Do not manually edit this file. -$dartVersionComment $rawOutput '''); @@ -1051,7 +1049,7 @@ class _MockLibraryInfo { final fallbackGenerators = mockTarget.fallbackGenerators; mockClasses.add(_MockClassInfo( mockTarget: mockTarget, - sourceLibIsNonNullable: entryLib.isNonNullableByDefault, + sourceLibIsNonNullable: true, typeProvider: entryLib.typeProvider, typeSystem: entryLib.typeSystem, mockLibraryInfo: this, @@ -1175,9 +1173,8 @@ class _MockClassInfo { // The test can be pre-null-safety but if the class // we want to mock is defined in a null safe library, // we still need to override methods to get nice mocks. - final isNiceMockOfNullSafeClass = mockTarget.onMissingStub == - OnMissingStub.returnDefault && - typeToMock.element.enclosingElement.library.isNonNullableByDefault; + final isNiceMockOfNullSafeClass = + mockTarget.onMissingStub == OnMissingStub.returnDefault; if (sourceLibIsNonNullable || isNiceMockOfNullSafeClass) { cBuilder.methods.addAll( From 135e81fe2ad85758f6ad4e8f38109e148c358856 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 24 Jun 2024 10:22:42 -0700 Subject: [PATCH 580/607] Properly generate code for parameter default value Strings. Fixes https://github.com/dart-lang/mockito/issues/756 In order to properly allow single quotes and dollar signs in parameter default values, we must not print as raw, and we must escape dollar signs ourselves, before passing to code_builder. PiperOrigin-RevId: 646140324 --- pkgs/mockito/lib/src/builder.dart | 6 ++- .../mockito/test/builder/auto_mocks_test.dart | 47 ++++++++++++++++--- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 4888f2cc8..9f0a5873b 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1822,7 +1822,11 @@ class _MockClassInfo { } else if (constant.isInt) { return literalNum(constant.intValue); } else if (constant.isString) { - return literalString(constant.stringValue, raw: true); + // code_builder writes all strings with single quotes. + // Raw single quoted strings may not contain single quotes, + // so escape dollar signs and use a non-raw string instead. + final stringValue = constant.stringValue.replaceAll('\$', '\\\$'); + return literalString(stringValue); } else if (constant.isList) { return literalConstList([ for (final element in constant.listValue) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 1e6a0f369..83631677e 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -281,13 +281,46 @@ void main() { await expectSingleNonNullableOutput( dedent(r''' class Foo { - void m([String a = 'Hello', String b = 'Hello ' r"World"]) {} + void m([String a = 'Hello', String b = "World"]) {} } '''), _containsAllOf(dedent2(''' void m([ - String? a = r'Hello', - String? b = r'Hello World', + String? a = 'Hello', + String? b = 'World', + ]) => + ''')), + ); + }); + + test('matches string literal parameter default values with quote characters', + () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([String a = 'Hel"lo', String b = "Wor'ld"]) {} + } + '''), + _containsAllOf(dedent2(''' + void m([ + String? a = 'Hel"lo', + String? b = 'Wor\\'ld', + ]) => + ''')), + ); + }); + + test('matches raw string literal parameter default values', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m([String a = r'$Hello', String b = r"$World"]) {} + } + '''), + _containsAllOf(dedent2(''' + void m([ + String? a = '\\\$Hello', + String? b = '\\\$World', ]) => ''')), ); @@ -337,8 +370,8 @@ void main() { _containsAllOf(dedent2(''' void m( [Map? a = const { - 1: r'a', - 2: r'b', + 1: 'a', + 2: 'b', }]) => ''')), ); @@ -354,8 +387,8 @@ void main() { _containsAllOf(dedent2(''' void m( [Map? a = const { - 1: r'a', - 2: r'b', + 1: 'a', + 2: 'b', }]) => ''')), ); From fc0f7ce1b85126128c39f0c10f3e6d1de6784dca Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 25 Jun 2024 08:57:45 -0700 Subject: [PATCH 581/607] Fix lint in examples. PiperOrigin-RevId: 646496713 --- pkgs/mockito/example/example.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/example/example.dart b/pkgs/mockito/example/example.dart index 56270cd21..ead196c5f 100644 --- a/pkgs/mockito/example/example.dart +++ b/pkgs/mockito/example/example.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; @@ -92,7 +94,7 @@ void main() { when(cat.eatFood(any)).thenReturn(false); // ... or plain arguments themselves - when(cat.eatFood("fish")).thenReturn(true); + when(cat.eatFood('fish')).thenReturn(true); // ... including collections when(cat.walk(['roof', 'tree'])).thenReturn(2); @@ -202,7 +204,7 @@ void main() { } // Waiting for a call. - chewHelper(cat); + unawaited(chewHelper(cat)); await untilCalled(cat.chew()); // This completes when cat.chew() is called. // Waiting for a call that has already happened. From 31c2096cafc127a3a177e2610ebd79d6d9135b99 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 27 Jun 2024 09:34:55 -0700 Subject: [PATCH 582/607] Use curly braces in if statement, in accordance with upcoming lint rule change. This lint rule currently measures from the end of the condition, but needs to measure from the start of the if-statement. E.g. if ("long condition and other long condition" != "another long condition" && "more tests".isNotEmpty) return null; This is currently allowed, but should not be, as per Effective Dart. This will be fixed in a Dart SDK CL. PiperOrigin-RevId: 647347218 --- pkgs/mockito/lib/src/builder.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 9f0a5873b..5abd5d96f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -602,7 +602,9 @@ class _MockTargetGatherer { // turned into `dynamic` by the analyzer. typeArguments.forEachIndexed((typeArgIdx, typeArgument) { if (!(typeArgument is analyzer.DynamicType || - typeArgument is analyzer.InvalidType)) return; + typeArgument is analyzer.InvalidType)) { + return; + } if (typeArgIdx >= mockTypeArguments.arguments.length) return; final typeArgAst = mockTypeArguments.arguments[typeArgIdx]; if (typeArgAst is! ast.NamedType) { From 60c37b60c03e7331b606a1a0f3d3222116baf94b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:39:51 -0700 Subject: [PATCH 583/607] Bump the github-actions group across 1 directory with 2 updates (dart-lang/mockito#761) Bumps the github-actions group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart). Updates `actions/checkout` from 4.1.1 to 4.1.7 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/b4ffde65f46336ab88eb53be808477a3936bae11...692973e3d937129bcbf40652eb9f2f61becf3332) Updates `dart-lang/setup-dart` from 1.6.2 to 1.6.5 - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/fedb1266e91cf51be2fdb382869461a434b920a3...0a8a0fc875eb934c15d08629302413c671d3f672) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/mockito/.github/workflows/test-package.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 1dfee031c..ad33c6419 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,8 +21,8 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: 3.3.0 - id: install @@ -39,8 +39,8 @@ jobs: matrix: sdk: [3.3.0, dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install @@ -61,8 +61,8 @@ jobs: os: [ubuntu-latest] sdk: [3.3.0, dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install @@ -86,8 +86,8 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: dev - id: install From 598965b58b3583e7b7ea4edbd37f933b0492c66a Mon Sep 17 00:00:00 2001 From: James Lin Date: Wed, 5 Jun 2024 16:48:58 -0700 Subject: [PATCH 584/607] Provide better documentation for `provideDummy`/`provideDummyBuilder` Mostly fixes https://github.com/dart-lang/mockito/issues/697. I'm still not quite satisfied with the documentation for `provideDummyBuilder` since the `DummyBuilder` `typedef` remains undocumented, but I can't write documentation for that because I don't understand its arguments. --- pkgs/mockito/FAQ.md | 78 +++++++++++++++++++++++++++++-- pkgs/mockito/lib/mockito.dart | 8 +++- pkgs/mockito/lib/src/dummies.dart | 17 ++++--- 3 files changed, 91 insertions(+), 12 deletions(-) diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index bc3e09eb8..fad80540e 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -1,6 +1,6 @@ # Frequently asked questions -#### How do I mock a static method, constructor, or top-level function? +## How do I mock a static method, constructor, or top-level function? Mockito provides its stubbing and verification features by overriding class instance methods. Since there is no mechanism for overriding static methods, @@ -58,12 +58,12 @@ for production and a [MemoryFileSystem] for tests), and use its wrapper methods [io package]: https://pub.dev/packages/io [ProcessManager]: https://pub.dev/documentation/io/latest/io/ProcessManager-class.html -#### How do I mock an extension method? +## How do I mock an extension method? If there is no way to override some kind of function, then mockito cannot mock it. See the above answer for further explanation, and alternatives. -#### Why can a method call not be verified multiple times? +## Why can a method call not be verified multiple times? When mockito verifies a method call (via [`verify`] or [`verifyInOrder`]), it marks the call as "verified", which excludes the call from further @@ -100,7 +100,7 @@ expect(firstCall, equals(["birds"])); expect(secondCall, equals(["lizards"])); ``` -#### How does mockito work? +## How does mockito work? The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to catch all method invocations, and returns the value that you have configured @@ -169,7 +169,7 @@ it's done. It's very straightforward. [`verifyInOrder`]: https://pub.dev/documentation/mockito/latest/mockito/verifyInOrder.html -### How can I customize where Mockito outputs its mocks? +## How can I customize where Mockito outputs its mocks? Mockito supports configuration of outputs by the configuration provided by the `build` package by creating (if it doesn't exist already) the `build.yaml` at the root folder @@ -203,3 +203,71 @@ targets: ``` Also, you can also check out the example configuration in the Mockito repository. + + +## How do I mock a `sealed` class? + +Suppose that you have code such as: + +```dart +class Bar { + int value; + + Bar(this.value); + + Bar.zero() : value = 0; +} + +class Foo { + Bar someMethod(int value) => Bar(value); +} +``` + +and now you want to mock `Foo`. The generated implementation for `MockFoo` +needs to return *something* if `someMethod` is called. It can't return `null` +since its return type is non-nullable. It can't construct a `Bar` on its own +without understanding the semantics of `Bar`'s constructors. That is, which +`Bar` constructor should be called and with what arguments? To avoid this, +Mockito instead generates its own, fake implementation of `Bar` that it does +know how to construct, something like: + +```dart +class _FakeBar extends Fake implements Bar {} +``` + +And then the generated implementation of `MockFoo` can have its `someMethod` +override return a `_FakeBar` instance. + +However, if `Bar` is `sealed` (or is marked with `base` or `final`), then it +cannot be `implemented` in generated code. Therefore Mockito can't generate a +default value for a `Bar` on its own, and it needs users to specify the default +value to use via `provideDummy` or `provideDummyBuilder`: + +```dart +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +@GenerateNiceMocks([MockSpec()]) +import 'foo.mocks.dart'; + +sealed class Bar { + int value; + + Bar(this.value); + + Bar.zero() : value = 0; +} + +class Foo { + Bar someMethod(int value) => Bar(value); +} + +void main() { + provideDummy(Bar.zero()); + + var mockFoo = MockFoo(); +} +``` + +Note that the value used as the "dummy" usually doesn't matter since methods on +the mock typically will be stubbed, overriding the method's return value. diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 312fee4c9..7df11b8d6 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -12,11 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: combinators_ordering + // ignore: deprecated_member_use export 'package:test_api/fake.dart' show Fake; export 'src/dummies.dart' - show provideDummy, provideDummyBuilder, MissingDummyValueError; + show + provideDummy, + provideDummyBuilder, + DummyBuilder, + MissingDummyValueError; export 'src/mock.dart' show Mock, diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index f5b48c164..1b29638de 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -90,12 +90,13 @@ class MissingDummyValueError { MissingDummyValueError: $type This means Mockito was not smart enough to generate a dummy value of type -'$type'. Please consider using either 'provideDummy' or 'provideDummyBuilder' -functions to give Mockito a proper dummy value. +'$type'. Due to implementation details, Mockito sometimes needs users to +provide dummy values for some types, even if they plan to explicitly stub all +the called methods. Call either `provideDummy` or `provideDummyBuilder` to +provide Mockito with a dummy value. -Please note that due to implementation details Mockito sometimes needs users -to provide dummy values for some types, even if they plan to explicitly stub -all the called methods. +For more details, see: + '''; } @@ -156,7 +157,8 @@ T dummyValue(Object parent, Invocation invocation) { throw MissingDummyValueError(T); } -/// Provide a builder for that could create a dummy value of type `T`. +/// Specifies a builder to create a dummy value of type `T`. +/// /// This could be useful for nice mocks, such that information about the /// specific invocation that caused the creation of a dummy value could be /// preserved. @@ -165,6 +167,9 @@ void provideDummyBuilder(DummyBuilder dummyBuilder) => /// Provide a dummy value for `T` to be used both while adding expectations /// and as a default value for unstubbed methods, if using a nice mock. +/// +/// For details and for example usage, see: +/// https://github.com/dart-lang/mockito/blob/master/FAQ.md#how-do-i-mock-a-sealed-class void provideDummy(T dummy) => provideDummyBuilder((parent, invocation) => dummy); From 3516c47b4774cf5dad6f7fb4ec07752cb4716b01 Mon Sep 17 00:00:00 2001 From: James Lin Date: Wed, 3 Jul 2024 09:41:59 -0700 Subject: [PATCH 585/607] Update with review feedback from srawlins --- pkgs/mockito/FAQ.md | 25 ++++++++++++++++--------- pkgs/mockito/lib/src/dummies.dart | 11 +++++++---- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index fad80540e..5e3676d50 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -207,6 +207,12 @@ Also, you can also check out the example configuration in the Mockito repository ## How do I mock a `sealed` class? +`sealed` clases cannot be `extend`ed nor `implement`ed (outside of their Dart +library) and therefore cannot be mocked. + + +## How do I mock a class that requires instances of a `sealed` class? + Suppose that you have code such as: ```dart @@ -223,20 +229,21 @@ class Foo { } ``` -and now you want to mock `Foo`. The generated implementation for `MockFoo` -needs to return *something* if `someMethod` is called. It can't return `null` -since its return type is non-nullable. It can't construct a `Bar` on its own -without understanding the semantics of `Bar`'s constructors. That is, which -`Bar` constructor should be called and with what arguments? To avoid this, -Mockito instead generates its own, fake implementation of `Bar` that it does -know how to construct, something like: +and now you want to mock `Foo`. A mock implementation of `Foo`, generated by +`@GenerateNiceMocks([MockSpec()])`, needs to return *something* if +`someMethod` is called. It can't return `null` since its return type is +non-nullable. It can't construct a `Bar` on its own without understanding the +semantics of `Bar`'s constructors. That is, which `Bar` constructor should be +called and with what arguments? To avoid this, Mockito instead generates its +own, fake implementation of `Bar` that it does know how to construct, something +`like: ```dart class _FakeBar extends Fake implements Bar {} ``` -And then the generated implementation of `MockFoo` can have its `someMethod` -override return a `_FakeBar` instance. +And then `MockFoo` can have its `someMethod` override return a `_FakeBar` +instance. However, if `Bar` is `sealed` (or is marked with `base` or `final`), then it cannot be `implemented` in generated code. Therefore Mockito can't generate a diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index 1b29638de..0ac186571 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -95,8 +95,9 @@ provide dummy values for some types, even if they plan to explicitly stub all the called methods. Call either `provideDummy` or `provideDummyBuilder` to provide Mockito with a dummy value. -For more details, see: - +For more details, see the questions regarding `sealed` classes in the FAQ: + + '''; } @@ -168,8 +169,10 @@ void provideDummyBuilder(DummyBuilder dummyBuilder) => /// Provide a dummy value for `T` to be used both while adding expectations /// and as a default value for unstubbed methods, if using a nice mock. /// -/// For details and for example usage, see: -/// https://github.com/dart-lang/mockito/blob/master/FAQ.md#how-do-i-mock-a-sealed-class +/// For details and for example usage, see the questions regarding `sealed` +/// classes in the [FAQ]. +/// +/// [FAQ]: https://github.com/dart-lang/mockito/blob/master/FAQ.md void provideDummy(T dummy) => provideDummyBuilder((parent, invocation) => dummy); From 585c07e08e3525cb11e88e05cf76fcd24a9b2228 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 10 Sep 2024 13:54:19 -0700 Subject: [PATCH 586/607] mockito: stop using deprecated analyzer APIs. Also remove some unused elements. PiperOrigin-RevId: 673093545 --- pkgs/mockito/FAQ.md | 85 ++----------------- pkgs/mockito/lib/mockito.dart | 8 +- pkgs/mockito/lib/src/builder.dart | 15 ++-- pkgs/mockito/lib/src/dummies.dart | 20 ++--- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 2 +- .../test/builder/custom_mocks_test.dart | 8 -- 7 files changed, 19 insertions(+), 121 deletions(-) diff --git a/pkgs/mockito/FAQ.md b/pkgs/mockito/FAQ.md index 5e3676d50..bc3e09eb8 100644 --- a/pkgs/mockito/FAQ.md +++ b/pkgs/mockito/FAQ.md @@ -1,6 +1,6 @@ # Frequently asked questions -## How do I mock a static method, constructor, or top-level function? +#### How do I mock a static method, constructor, or top-level function? Mockito provides its stubbing and verification features by overriding class instance methods. Since there is no mechanism for overriding static methods, @@ -58,12 +58,12 @@ for production and a [MemoryFileSystem] for tests), and use its wrapper methods [io package]: https://pub.dev/packages/io [ProcessManager]: https://pub.dev/documentation/io/latest/io/ProcessManager-class.html -## How do I mock an extension method? +#### How do I mock an extension method? If there is no way to override some kind of function, then mockito cannot mock it. See the above answer for further explanation, and alternatives. -## Why can a method call not be verified multiple times? +#### Why can a method call not be verified multiple times? When mockito verifies a method call (via [`verify`] or [`verifyInOrder`]), it marks the call as "verified", which excludes the call from further @@ -100,7 +100,7 @@ expect(firstCall, equals(["birds"])); expect(secondCall, equals(["lizards"])); ``` -## How does mockito work? +#### How does mockito work? The basics of the `Mock` class are nothing special: It uses `noSuchMethod` to catch all method invocations, and returns the value that you have configured @@ -169,7 +169,7 @@ it's done. It's very straightforward. [`verifyInOrder`]: https://pub.dev/documentation/mockito/latest/mockito/verifyInOrder.html -## How can I customize where Mockito outputs its mocks? +### How can I customize where Mockito outputs its mocks? Mockito supports configuration of outputs by the configuration provided by the `build` package by creating (if it doesn't exist already) the `build.yaml` at the root folder @@ -203,78 +203,3 @@ targets: ``` Also, you can also check out the example configuration in the Mockito repository. - - -## How do I mock a `sealed` class? - -`sealed` clases cannot be `extend`ed nor `implement`ed (outside of their Dart -library) and therefore cannot be mocked. - - -## How do I mock a class that requires instances of a `sealed` class? - -Suppose that you have code such as: - -```dart -class Bar { - int value; - - Bar(this.value); - - Bar.zero() : value = 0; -} - -class Foo { - Bar someMethod(int value) => Bar(value); -} -``` - -and now you want to mock `Foo`. A mock implementation of `Foo`, generated by -`@GenerateNiceMocks([MockSpec()])`, needs to return *something* if -`someMethod` is called. It can't return `null` since its return type is -non-nullable. It can't construct a `Bar` on its own without understanding the -semantics of `Bar`'s constructors. That is, which `Bar` constructor should be -called and with what arguments? To avoid this, Mockito instead generates its -own, fake implementation of `Bar` that it does know how to construct, something -`like: - -```dart -class _FakeBar extends Fake implements Bar {} -``` - -And then `MockFoo` can have its `someMethod` override return a `_FakeBar` -instance. - -However, if `Bar` is `sealed` (or is marked with `base` or `final`), then it -cannot be `implemented` in generated code. Therefore Mockito can't generate a -default value for a `Bar` on its own, and it needs users to specify the default -value to use via `provideDummy` or `provideDummyBuilder`: - -```dart -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -@GenerateNiceMocks([MockSpec()]) -import 'foo.mocks.dart'; - -sealed class Bar { - int value; - - Bar(this.value); - - Bar.zero() : value = 0; -} - -class Foo { - Bar someMethod(int value) => Bar(value); -} - -void main() { - provideDummy(Bar.zero()); - - var mockFoo = MockFoo(); -} -``` - -Note that the value used as the "dummy" usually doesn't matter since methods on -the mock typically will be stubbed, overriding the method's return value. diff --git a/pkgs/mockito/lib/mockito.dart b/pkgs/mockito/lib/mockito.dart index 7df11b8d6..312fee4c9 100644 --- a/pkgs/mockito/lib/mockito.dart +++ b/pkgs/mockito/lib/mockito.dart @@ -12,17 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ignore_for_file: combinators_ordering - // ignore: deprecated_member_use export 'package:test_api/fake.dart' show Fake; export 'src/dummies.dart' - show - provideDummy, - provideDummyBuilder, - DummyBuilder, - MissingDummyValueError; + show provideDummy, provideDummyBuilder, MissingDummyValueError; export 'src/mock.dart' show Mock, diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 5abd5d96f..599d24fa3 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -329,8 +329,8 @@ class _TypeVisitor extends RecursiveElementVisitor { if (!alreadyVisitedElement) { type.element.typeParameters.forEach(visitTypeParameterElement); - final toStringMethod = - type.element.lookUpMethod('toString', type.element.library); + final toStringMethod = type.element.augmented + .lookUpMethod(name: 'toString', library: type.element.library); if (toStringMethod != null && toStringMethod.parameters.isNotEmpty) { // In a Fake class which implements a class which overrides `toString` // with additional (optional) parameters, we must also override @@ -594,9 +594,7 @@ class _MockTargetGatherer { var type = _determineDartType(typeToMock, entryLib.typeProvider); final mockTypeArguments = mockType?.typeArguments; if (mockTypeArguments != null) { - final typeName = - type.alias?.element.getDisplayString(withNullability: false) ?? - 'type $type'; + final typeName = type.alias?.element.getDisplayString() ?? 'type $type'; final typeArguments = type.alias?.typeArguments ?? type.typeArguments; // Check explicit type arguments for unknown types that were // turned into `dynamic` by the analyzer. @@ -1849,7 +1847,7 @@ class _MockClassInfo { // TODO(srawlins): It seems like this might be revivable, but Angular // does not revive Types; we should investigate this if users request it. final type = object.toTypeValue()!; - final typeStr = type.getDisplayString(withNullability: false); + final typeStr = type.getDisplayString(); throw _ReviveException('default value is a Type: $typeStr.'); } else { // If [constant] is not null, a literal, or a type, then it must be an @@ -2176,10 +2174,7 @@ class _MockClassInfo { {for (final f in type.namedFields) f.name: _typeReference(f.type)}) ..isNullable = forceNullable || typeSystem.isNullable(type)); } else { - return referImported( - type.getDisplayString(withNullability: false), - _typeImport(type.element), - ); + return referImported(type.getDisplayString(), _typeImport(type.element)); } } diff --git a/pkgs/mockito/lib/src/dummies.dart b/pkgs/mockito/lib/src/dummies.dart index 0ac186571..f5b48c164 100644 --- a/pkgs/mockito/lib/src/dummies.dart +++ b/pkgs/mockito/lib/src/dummies.dart @@ -90,14 +90,12 @@ class MissingDummyValueError { MissingDummyValueError: $type This means Mockito was not smart enough to generate a dummy value of type -'$type'. Due to implementation details, Mockito sometimes needs users to -provide dummy values for some types, even if they plan to explicitly stub all -the called methods. Call either `provideDummy` or `provideDummyBuilder` to -provide Mockito with a dummy value. +'$type'. Please consider using either 'provideDummy' or 'provideDummyBuilder' +functions to give Mockito a proper dummy value. -For more details, see the questions regarding `sealed` classes in the FAQ: - - +Please note that due to implementation details Mockito sometimes needs users +to provide dummy values for some types, even if they plan to explicitly stub +all the called methods. '''; } @@ -158,8 +156,7 @@ T dummyValue(Object parent, Invocation invocation) { throw MissingDummyValueError(T); } -/// Specifies a builder to create a dummy value of type `T`. -/// +/// Provide a builder for that could create a dummy value of type `T`. /// This could be useful for nice mocks, such that information about the /// specific invocation that caused the creation of a dummy value could be /// preserved. @@ -168,11 +165,6 @@ void provideDummyBuilder(DummyBuilder dummyBuilder) => /// Provide a dummy value for `T` to be used both while adding expectations /// and as a default value for unstubbed methods, if using a nice mock. -/// -/// For details and for example usage, see the questions regarding `sealed` -/// classes in the [FAQ]. -/// -/// [FAQ]: https://github.com/dart-lang/mockito/blob/master/FAQ.md void provideDummy(T dummy) => provideDummyBuilder((parent, invocation) => dummy); diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 5a7af3263..92fac2172 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.4'; +const packageVersion = '5.4.5-wip'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 5cd36f6b1..a68b32887 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.4 +version: 5.4.5-wip description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index 2b987cf34..cdf253ae5 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -81,11 +81,6 @@ void main() {} ''' }; -const _constructorWithThrowOnMissingStub = ''' -MockFoo() { - _i1.throwOnMissingStub(this); - }'''; - void main() { late InMemoryAssetWriter writer; @@ -1755,9 +1750,6 @@ void main() { }); } -TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( - b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); - /// Expect that [testBuilder], given [assets], throws an /// [InvalidMockitoAnnotationException] with a message containing [message]. void _expectBuilderThrows({ From a18de654db3e6cee174aba90fd874045b2a11ff0 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 11 Sep 2024 10:45:31 -0700 Subject: [PATCH 587/607] Pass a Dart language version to the Dart formatter. This will be required in an upcoming version of the Dart formatter. PiperOrigin-RevId: 673452282 --- pkgs/mockito/CHANGELOG.md | 2 ++ pkgs/mockito/lib/src/builder.dart | 4 +++- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 435e30b32..967e32715 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -6,6 +6,8 @@ * Require Dart >= 3.3.0. * Require analyzer 6.4.1. * Add support for extension types. +* Require dart_style >= 2.3.7, so that the current Dart language version can be + passed to `DartFormatter`. ## 5.4.4 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 599d24fa3..2b6a65600 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -144,7 +144,9 @@ class MockBuilder implements Builder { // The source lib may be pre-null-safety because of an explicit opt-out // (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To // allow for this situation, we must also add an opt-out comment here. - final mockLibraryContent = DartFormatter().format(''' + final mockLibraryContent = + DartFormatter(languageVersion: DartFormatter.latestLanguageVersion) + .format(''' // Mocks generated by Mockito $packageVersion from annotations // in ${entryLib.definingCompilationUnit.source.uri.path}. // Do not manually edit this file. diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index a68b32887..39ddb471e 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: build: ^2.0.0 code_builder: ^4.5.0 collection: ^1.15.0 - dart_style: ^2.0.0 + dart_style: ^2.3.7 matcher: ^0.12.15 meta: ^1.3.0 path: ^1.8.0 From 11fc929c08c6334bb5e50b48e3e7db3184afc826 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 11 Sep 2024 13:21:51 -0700 Subject: [PATCH 588/607] Bump CI tasks to use Dart 3.5.0 as stable --- pkgs/mockito/.github/workflows/test-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index ad33c6419..614f9f7f2 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: - sdk: 3.3.0 + sdk: 3.5.0 - id: install name: Install dependencies run: dart pub get @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [3.3.0, dev] + sdk: [3.5.0, dev] steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 @@ -59,7 +59,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [3.3.0, dev] + sdk: [3.5.0, dev] steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 From 2a627e5ef3b719fa4cdf1b00eb61d742eeca9f09 Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 13 Sep 2024 15:09:38 -0700 Subject: [PATCH 589/607] Migration for analyzer APIs. PiperOrigin-RevId: 674448702 --- pkgs/mockito/lib/src/builder.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 2b6a65600..db956fbe8 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -484,7 +484,7 @@ class _MockTargetGatherer { // annotations, on one element or even on different elements in a library. for (final annotation in element.metadata) { if (annotation.element is! ConstructorElement) continue; - final annotationClass = annotation.element!.enclosingElement!.name; + final annotationClass = annotation.element!.enclosingElement3!.name; switch (annotationClass) { case 'GenerateMocks': mockTargets @@ -1734,7 +1734,7 @@ class _MockClassInfo { parameter.computeConstantValue()!, parameter) .code; } on _ReviveException catch (e) { - final method = parameter.enclosingElement!; + final method = parameter.enclosingElement3!; throw InvalidMockitoAnnotationException( 'Mockito cannot generate a valid override for method ' "'${mockTarget.interfaceElement.displayName}.${method.displayName}'; " @@ -1765,8 +1765,8 @@ class _MockClassInfo { if (!parameter.isCovariant) { return type; } - final method = parameter.enclosingElement as MethodElement; - final class_ = method.enclosingElement as InterfaceElement; + final method = parameter.enclosingElement3 as MethodElement; + final class_ = method.enclosingElement3 as InterfaceElement; final name = Name(method.librarySource.uri, method.name); final overriddenMethods = inheritanceManager.getOverridden2(class_, name); if (overriddenMethods == null) { @@ -1776,7 +1776,7 @@ class _MockClassInfo { while (allOverriddenMethods.isNotEmpty) { final overriddenMethod = allOverriddenMethods.removeFirst(); final secondaryOverrides = inheritanceManager.getOverridden2( - overriddenMethod.enclosingElement as InterfaceElement, name); + overriddenMethod.enclosingElement3 as InterfaceElement, name); if (secondaryOverrides != null) { allOverriddenMethods.addAll(secondaryOverrides); } @@ -2313,12 +2313,12 @@ extension on Element { } else if (this is EnumElement) { return "The enum '$name'"; } else if (this is MethodElement) { - final className = enclosingElement!.name; + final className = enclosingElement3!.name; return "The method '$className.$name'"; } else if (this is MixinElement) { return "The mixin '$name'"; } else if (this is PropertyAccessorElement) { - final className = enclosingElement!.name; + final className = enclosingElement3!.name; return "The property accessor '$className.$name'"; } else { return 'unknown element'; From 08b81042783343b3dcc0e958b2835047b1a7cfa2 Mon Sep 17 00:00:00 2001 From: Googler Date: Thu, 3 Oct 2024 01:28:20 -0700 Subject: [PATCH 590/607] Add "topics" to package mockito PiperOrigin-RevId: 681769728 --- pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/pubspec.yaml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 967e32715..51c8ada35 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -8,6 +8,7 @@ * Add support for extension types. * Require dart_style >= 2.3.7, so that the current Dart language version can be passed to `DartFormatter`. +* Add topics to `pubspec.yaml`. ## 5.4.4 diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 39ddb471e..561450741 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -5,6 +5,10 @@ description: >- behavior verification, and stubbing. repository: https://github.com/dart-lang/mockito +topics: + - testing + - mocking + environment: sdk: ^3.3.0 From 6b42a76ed5b9e2a7eb2b6b924d8d758748301419 Mon Sep 17 00:00:00 2001 From: Googler Date: Thu, 10 Oct 2024 09:43:47 -0700 Subject: [PATCH 591/607] Migration for analyzer APIs. PiperOrigin-RevId: 684477097 --- pkgs/mockito/lib/src/builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index db956fbe8..309e5b0a8 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -474,8 +474,8 @@ class _MockTargetGatherer { final mockTargets = <_MockTarget>{}; final possiblyAnnotatedElements = [ - ...entryLib.libraryExports, - ...entryLib.libraryImports, + ...entryLib.definingCompilationUnit.libraryExports, + ...entryLib.definingCompilationUnit.libraryImports, ...entryLib.topLevelElements, ]; From 4d27b5627eba4565bcf0d6bf4e6cd9de20a7ea51 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Wed, 30 Oct 2024 20:08:05 -0700 Subject: [PATCH 592/607] In custom_mocks_test, there were only a handful that were sensitive to formatting, so I just loosened the tests a bit manually. In auto_mocks_test, there were about 50 failures, so I cobbled together a more generic approach. I used the existing _containsAllOf() function to hook in a custom matcher that ignores differences in whitespace and trailing commas. PiperOrigin-RevId: 691644163 --- .../mockito/test/builder/auto_mocks_test.dart | 81 ++++++++++++++----- .../test/builder/custom_mocks_test.dart | 51 ++++++++---- 2 files changed, 97 insertions(+), 35 deletions(-) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 83631677e..b6b2c573a 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -2250,7 +2250,7 @@ void main() { bool m() => false; } '''), - _containsAllOf('returnValue: false,'), + _containsAllOf('returnValue: false'), ); }); @@ -2261,7 +2261,7 @@ void main() { double m() => 3.14; } '''), - _containsAllOf('returnValue: 0.0,'), + _containsAllOf('returnValue: 0.0'), ); }); @@ -2272,7 +2272,7 @@ void main() { int m() => 7; } '''), - _containsAllOf('returnValue: 0,'), + _containsAllOf('returnValue: 0'), ); }); @@ -2295,7 +2295,7 @@ void main() { List m() => [Foo()]; } '''), - _containsAllOf('returnValue: <_i2.Foo>[],'), + _containsAllOf('returnValue: <_i2.Foo>[]'), ); }); @@ -2306,7 +2306,7 @@ void main() { Set m() => {Foo()}; } '''), - _containsAllOf('returnValue: <_i2.Foo>{},'), + _containsAllOf('returnValue: <_i2.Foo>{}'), ); }); @@ -2317,7 +2317,7 @@ void main() { Map m() => {7: Foo()}; } '''), - _containsAllOf('returnValue: {},'), + _containsAllOf('returnValue: {}'), ); }); @@ -2328,7 +2328,7 @@ void main() { Map m(); } '''), - _containsAllOf('returnValue: {},'), + _containsAllOf('returnValue: {}'), ); }); @@ -2339,7 +2339,7 @@ void main() { Future m() async => false; } '''), - _containsAllOf('returnValue: _i3.Future.value(false),'), + _containsAllOf('returnValue: _i3.Future.value(false)'), ); }); @@ -2362,7 +2362,7 @@ void main() { Stream m(); } '''), - _containsAllOf('returnValue: _i3.Stream.empty(),'), + _containsAllOf('returnValue: _i3.Stream.empty()'), ); }); @@ -2384,7 +2384,7 @@ void main() { #m, [], ), - ),'''), + )'''), ); }); @@ -2403,7 +2403,7 @@ void main() { #m, [], ), - ),'''), + )'''), ); }); @@ -2418,7 +2418,7 @@ void main() { two, } '''), - _containsAllOf('returnValue: _i2.Bar.one,'), + _containsAllOf('returnValue: _i2.Bar.one'), ); }); @@ -2435,7 +2435,7 @@ void main() { returnValue: ( int __p0, [ String? __p1, - ]) {},'''), + ]) {}'''), ); }); @@ -2452,7 +2452,7 @@ void main() { returnValue: ( _i2.Foo __p0, { bool? b, - }) {},'''), + }) {}'''), ); }); @@ -2469,7 +2469,7 @@ void main() { returnValue: ( _i2.Foo __p0, { required bool b, - }) {},'''), + }) {}'''), ); }); @@ -2489,7 +2489,7 @@ void main() { #m, [], ), - ),'''), + )'''), ); }); @@ -2510,7 +2510,7 @@ void main() { #m, [], ), - ), + ) '''), ); }); @@ -2523,7 +2523,7 @@ void main() { T? Function(T) m() => (int i, [String s]) {}; } '''), - _containsAllOf('returnValue: (T __p0) => null,'), + _containsAllOf('returnValue: (T __p0) => null'), ); }); @@ -3753,8 +3753,49 @@ void main() { }); } -TypeMatcher> _containsAllOf(a, [b]) => decodedMatches( - b == null ? allOf(contains(a)) : allOf(contains(a), contains(b))); +TypeMatcher> _containsAllOf(String a, [String? b]) => + decodedMatches(b == null + ? _ContainsIgnoringFormattingMatcher(a) + : allOf(_ContainsIgnoringFormattingMatcher(a), + _ContainsIgnoringFormattingMatcher(b))); + +/// Matches a string that contains a given string, ignoring differences related +/// to formatting: whitespace and trailing commas. +class _ContainsIgnoringFormattingMatcher extends Matcher { + /// Matches one or more whitespace characters. + static final _whitespacePattern = RegExp(r'\s+'); + + /// Matches a trailing comma preceding a closing bracket character. + static final _trailingCommaPattern = RegExp(r',\s*([)}\]])'); + + /// The string that the actual value must contain in order for the match to + /// succeed. + final String _expected; + + _ContainsIgnoringFormattingMatcher(this._expected); + + @override + Description describe(Description description) { + return description + .add('Contains "$_expected" when ignoring source formatting'); + } + + @override + bool matches(item, Map matchState) => + _stripFormatting(item.toString()).contains(_stripFormatting(_expected)); + + /// Removes whitespace and trailing commas. + /// + /// Note that the result is not valid code because it means adjacent + ///.identifiers and operators may be joined in ways that break the semantics. + /// The goal is not to produce an but valid version of the code, just to + /// produce a string that will reliably match the actual string when it has + /// also been stripped the same way. + String _stripFormatting(String code) => code + .replaceAll(_whitespacePattern, '') + .replaceAllMapped(_trailingCommaPattern, (match) => match[1]!) + .trim(); +} /// Expect that [testBuilder], given [assets], in a package which has opted into /// null safety, throws an [InvalidMockitoAnnotationException] with a message diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index cdf253ae5..fce83e1e1 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -439,10 +439,11 @@ void main() { void main() {} ''' }); + expect(mocksContent, contains('m() => throw UnsupportedError(\n')); expect( mocksContent, - contains(' m() => throw UnsupportedError(\n' - ' r\'"m" cannot be used without a mockito fallback generator.\');')); + contains( + 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); test('generates mock getters with private types, given unsupportedMembers', @@ -465,10 +466,11 @@ void main() { void main() {} ''' }); + expect(mocksContent, contains('get f => throw UnsupportedError(')); expect( mocksContent, - contains(' get f => throw UnsupportedError(\n' - ' r\'"f" cannot be used without a mockito fallback generator.\');')); + contains( + 'r\'"f" cannot be used without a mockito fallback generator.\'')); }); test('generates mock setters with private types, given unsupportedMembers', @@ -491,10 +493,11 @@ void main() { void main() {} ''' }); + expect(mocksContent, contains('set f(value) => throw UnsupportedError(')); expect( mocksContent, - contains(' set f(value) => throw UnsupportedError(\n' - ' r\'"f=" cannot be used without a mockito fallback generator.\');')); + contains( + 'r\'"f=" cannot be used without a mockito fallback generator.\'')); }); test( @@ -518,10 +521,11 @@ void main() { void main() {} ''' }); + expect(mocksContent, contains('m() => throw UnsupportedError(')); expect( mocksContent, - contains(' m() => throw UnsupportedError(\n' - ' r\'"m" cannot be used without a mockito fallback generator.\');')); + contains( + 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); test( @@ -545,10 +549,11 @@ void main() { void main() {} ''' }); + expect(mocksContent, contains('m() => throw UnsupportedError(')); expect( mocksContent, - contains(' m() => throw UnsupportedError(\n' - ' r\'"m" cannot be used without a mockito fallback generator.\');')); + contains( + 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); test( @@ -572,10 +577,11 @@ void main() { void main() {} ''' }); + expect(mocksContent, contains('void m(b) => throw UnsupportedError(')); expect( mocksContent, - contains(' void m(b) => throw UnsupportedError(\n' - ' r\'"m" cannot be used without a mockito fallback generator.\');')); + contains( + 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); test( @@ -629,7 +635,20 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains(''' + + // TODO(rnystrom): Allow the test to pass using the old or new formatting + // styles. Remove the test for the old style once google3 is migrated to + // the new formatter. + expect( + mocksContent, + anyOf( + contains(''' + returnValue: _FakeBar_0(this, Invocation.method(#m, [])), + returnValueForMissingStub: _FakeBar_0( + this, + Invocation.method(#m, []), + ),'''), + contains(''' returnValue: _FakeBar_0( this, Invocation.method( @@ -643,7 +662,9 @@ void main() { #m, [], ), - ),''')); + ),'''), + ), + ); }); test('generates mock classes including a fallback generator for a getter', @@ -672,7 +693,7 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains('returnValue: _i3.fShim(),')); + expect(mocksContent, contains('returnValue: _i3.fShim()')); }); test( From db755d55c490c0ef1ea07d1380bba480cb971233 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Thu, 31 Oct 2024 09:42:12 -0700 Subject: [PATCH 593/607] In my previous change, I tried a simpler approach in custom_mocks_test to try to not be sensitive to formatting changes before I realized that wouldn't work in auto_mocks_test and came up with something more robust. It turns out the simpler approach is still too brittle and some tests fail under the new style (specifically, the new formatter splits after `=>` when the old one doesn't). So I hoisted containsIgnoringFormatting() into a separate library and refactored custom_mocks_test to use it too. PiperOrigin-RevId: 691836139 --- .../mockito/test/builder/auto_mocks_test.dart | 45 ++------------ .../builder/contains_ignoring_formatting.dart | 59 +++++++++++++++++++ .../test/builder/custom_mocks_test.dart | 44 ++++---------- 3 files changed, 73 insertions(+), 75 deletions(-) create mode 100644 pkgs/mockito/test/builder/contains_ignoring_formatting.dart diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index b6b2c573a..5ae98b852 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -22,6 +22,8 @@ import 'package:mockito/src/builder.dart'; import 'package:package_config/package_config.dart'; import 'package:test/test.dart'; +import 'contains_ignoring_formatting.dart'; + const annotationsAsset = { 'mockito|lib/annotations.dart': ''' class GenerateMocks { @@ -3755,47 +3757,8 @@ void main() { TypeMatcher> _containsAllOf(String a, [String? b]) => decodedMatches(b == null - ? _ContainsIgnoringFormattingMatcher(a) - : allOf(_ContainsIgnoringFormattingMatcher(a), - _ContainsIgnoringFormattingMatcher(b))); - -/// Matches a string that contains a given string, ignoring differences related -/// to formatting: whitespace and trailing commas. -class _ContainsIgnoringFormattingMatcher extends Matcher { - /// Matches one or more whitespace characters. - static final _whitespacePattern = RegExp(r'\s+'); - - /// Matches a trailing comma preceding a closing bracket character. - static final _trailingCommaPattern = RegExp(r',\s*([)}\]])'); - - /// The string that the actual value must contain in order for the match to - /// succeed. - final String _expected; - - _ContainsIgnoringFormattingMatcher(this._expected); - - @override - Description describe(Description description) { - return description - .add('Contains "$_expected" when ignoring source formatting'); - } - - @override - bool matches(item, Map matchState) => - _stripFormatting(item.toString()).contains(_stripFormatting(_expected)); - - /// Removes whitespace and trailing commas. - /// - /// Note that the result is not valid code because it means adjacent - ///.identifiers and operators may be joined in ways that break the semantics. - /// The goal is not to produce an but valid version of the code, just to - /// produce a string that will reliably match the actual string when it has - /// also been stripped the same way. - String _stripFormatting(String code) => code - .replaceAll(_whitespacePattern, '') - .replaceAllMapped(_trailingCommaPattern, (match) => match[1]!) - .trim(); -} + ? containsIgnoringFormatting(a) + : allOf(containsIgnoringFormatting(a), containsIgnoringFormatting(b))); /// Expect that [testBuilder], given [assets], in a package which has opted into /// null safety, throws an [InvalidMockitoAnnotationException] with a message diff --git a/pkgs/mockito/test/builder/contains_ignoring_formatting.dart b/pkgs/mockito/test/builder/contains_ignoring_formatting.dart new file mode 100644 index 000000000..9a254044d --- /dev/null +++ b/pkgs/mockito/test/builder/contains_ignoring_formatting.dart @@ -0,0 +1,59 @@ +// Copyright 2024 Dart Mockito authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:test/test.dart'; + +/// Returns a [Matcher] that checks that the actual string of Dart code +/// contains [expected] when ignoring differences due to formatting: whitespace +/// and trailing commas. +Matcher containsIgnoringFormatting(String expected) => + _ContainsIgnoringFormattingMatcher(expected); + +/// Matches a string that contains a given string, ignoring differences related +/// to formatting: whitespace and trailing commas. +class _ContainsIgnoringFormattingMatcher extends Matcher { + /// Matches one or more whitespace characters. + static final _whitespacePattern = RegExp(r'\s+'); + + /// Matches a trailing comma preceding a closing bracket character. + static final _trailingCommaPattern = RegExp(r',\s*([)}\]])'); + + /// The string that the actual value must contain in order for the match to + /// succeed. + final String _expected; + + _ContainsIgnoringFormattingMatcher(this._expected); + + @override + Description describe(Description description) { + return description + .add('Contains "$_expected" when ignoring source formatting'); + } + + @override + bool matches(item, Map matchState) => + _stripFormatting(item.toString()).contains(_stripFormatting(_expected)); + + /// Removes whitespace and trailing commas. + /// + /// Note that the result is not valid code because it means adjacent + ///.identifiers and operators may be joined in ways that break the semantics. + /// The goal is not to produce an but valid version of the code, just to + /// produce a string that will reliably match the actual string when it has + /// also been stripped the same way. + String _stripFormatting(String code) => code + .replaceAll(_whitespacePattern, '') + .replaceAllMapped(_trailingCommaPattern, (match) => match[1]!) + .trim(); +} diff --git a/pkgs/mockito/test/builder/custom_mocks_test.dart b/pkgs/mockito/test/builder/custom_mocks_test.dart index fce83e1e1..0ec23adeb 100644 --- a/pkgs/mockito/test/builder/custom_mocks_test.dart +++ b/pkgs/mockito/test/builder/custom_mocks_test.dart @@ -21,6 +21,8 @@ import 'package:mockito/src/builder.dart'; import 'package:package_config/package_config.dart'; import 'package:test/test.dart'; +import 'contains_ignoring_formatting.dart'; + Builder buildMocks(BuilderOptions options) => MockBuilder(); const annotationsAsset = { @@ -439,10 +441,9 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains('m() => throw UnsupportedError(\n')); expect( mocksContent, - contains( + containsIgnoringFormatting('m() => throw UnsupportedError(' 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); @@ -466,10 +467,9 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains('get f => throw UnsupportedError(')); expect( mocksContent, - contains( + containsIgnoringFormatting('get f => throw UnsupportedError(' 'r\'"f" cannot be used without a mockito fallback generator.\'')); }); @@ -493,10 +493,9 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains('set f(value) => throw UnsupportedError(')); expect( mocksContent, - contains( + containsIgnoringFormatting('set f(value) => throw UnsupportedError(' 'r\'"f=" cannot be used without a mockito fallback generator.\'')); }); @@ -521,10 +520,9 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains('m() => throw UnsupportedError(')); expect( mocksContent, - contains( + containsIgnoringFormatting('m() => throw UnsupportedError(' 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); @@ -549,10 +547,9 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains('m() => throw UnsupportedError(')); expect( mocksContent, - contains( + containsIgnoringFormatting('m() => throw UnsupportedError(' 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); @@ -577,10 +574,9 @@ void main() { void main() {} ''' }); - expect(mocksContent, contains('void m(b) => throw UnsupportedError(')); expect( mocksContent, - contains( + containsIgnoringFormatting('void m(b) => throw UnsupportedError(' 'r\'"m" cannot be used without a mockito fallback generator.\'')); }); @@ -636,34 +632,14 @@ void main() { ''' }); - // TODO(rnystrom): Allow the test to pass using the old or new formatting - // styles. Remove the test for the old style once google3 is migrated to - // the new formatter. expect( mocksContent, - anyOf( - contains(''' + containsIgnoringFormatting(''' returnValue: _FakeBar_0(this, Invocation.method(#m, [])), returnValueForMissingStub: _FakeBar_0( this, Invocation.method(#m, []), - ),'''), - contains(''' - returnValue: _FakeBar_0( - this, - Invocation.method( - #m, - [], - ), - ), - returnValueForMissingStub: _FakeBar_0( - this, - Invocation.method( - #m, - [], - ), - ),'''), - ), + )'''), ); }); From fd812e6c128fb6fe562db67b766b66f37c4f85b9 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 13 Nov 2024 09:09:45 -0800 Subject: [PATCH 594/607] Prepare mockito for the 'wildcard-variables' language feature. We generally preserve positional parameter names, but in the case of wildcard parameters, we must rename them. We cannot override a wildcard parameter with a wildcard parameter, since we do use the parameter (we pass it to `super.noSuchMethod` in a call to `Invocation.method`). This change introduces some renaming logic for wildcard parameters, using new method-unique parameter names. PiperOrigin-RevId: 696158750 --- pkgs/mockito/lib/src/builder.dart | 34 ++++++++++++------ .../mockito/test/builder/auto_mocks_test.dart | 35 ++++++++++++++++--- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 309e5b0a8..7fd81d7f8 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -1318,14 +1318,21 @@ class _MockClassInfo { if (parameter.isRequiredPositional || parameter.isOptionalPositional) { final superParameterType = _escapeCovariance(parameter, position: position); - final matchingParameter = _matchingParameter(parameter, - superParameterType: superParameterType, forceNullable: true); + final matchingParameter = _matchingParameter( + parameter, + superParameterType: superParameterType, + // A parameter in the overridden method may be a wildcard, in which + // case we need to rename it, as we use the parameter when we pass + // it to `Invocation.method`. + defaultName: '_$position', + forceNullable: true, + ); if (parameter.isRequiredPositional) { builder.requiredParameters.add(matchingParameter); } else { builder.optionalParameters.add(matchingParameter); } - invocationPositionalArgs.add(refer(parameter.displayName)); + invocationPositionalArgs.add(refer(matchingParameter.name)); position++; } else if (parameter.isNamed) { final superParameterType = @@ -1712,12 +1719,14 @@ class _MockClassInfo { {required analyzer.DartType superParameterType, String? defaultName, bool forceNullable = false}) { + final parameterHasName = parameter.name.isNotEmpty && parameter.name != '_'; assert( - parameter.name.isNotEmpty || defaultName != null, - 'parameter must have a non-empty name, or non-null defaultName must be ' - 'passed, but parameter name is "${parameter.name}" and defaultName is ' - '$defaultName'); - final name = parameter.name.isEmpty ? defaultName! : parameter.name; + parameterHasName || defaultName != null, + 'parameter must have a non-empty name, or non-null defaultName must be ' + 'passed, but parameter name is "${parameter.name}" and defaultName is ' + '$defaultName', + ); + final name = !parameterHasName ? defaultName! : parameter.name; return Parameter((pBuilder) { pBuilder.name = name; if (!superParameterType.containsPrivateName) { @@ -1980,8 +1989,13 @@ class _MockClassInfo { assert(setter.parameters.length == 1); final parameter = setter.parameters.single; + // The parameter in the overridden setter may be a wildcard, in which case + // we need to rename it, as we use the parameter when we pass it to + // `Invocation.setter`. + final parameterName = + parameter.displayName == '_' ? '_value' : parameter.displayName; builder.requiredParameters.add(Parameter((pBuilder) { - pBuilder.name = parameter.displayName; + pBuilder.name = parameterName; if (!parameter.type.containsPrivateName) { pBuilder.type = _typeReference(parameter.type, forceNullable: true, overrideVoid: true); @@ -2012,7 +2026,7 @@ class _MockClassInfo { final invocation = referImported('Invocation', 'dart:core').property('setter').call([ refer('#$name'), - refer(parameter.displayName), + refer(parameterName), ]); final returnNoSuchMethod = refer('super') .property('noSuchMethod') diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 5ae98b852..4c2d0d46f 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -1364,7 +1364,6 @@ void main() { test('prefixes parameter type on generic function-typed parameter', () async { await expectSingleNonNullableOutput( dedent(r''' - import 'dart:async'; class Foo { dynamic m(void Function(Foo f) a) {} } @@ -1377,7 +1376,6 @@ void main() { test('prefixes return type on generic function-typed parameter', () async { await expectSingleNonNullableOutput( dedent(r''' - import 'dart:async'; class Foo { void m(Foo Function() a) {} } @@ -1389,7 +1387,6 @@ void main() { test('prefixes parameter type on function-typed parameter', () async { await expectSingleNonNullableOutput( dedent(r''' - import 'dart:async'; class Foo { void m(void a(Foo f)) {} } @@ -1402,7 +1399,6 @@ void main() { test('prefixes return type on function-typed parameter', () async { await expectSingleNonNullableOutput( dedent(r''' - import 'dart:async'; class Foo { void m(Foo a()) {} } @@ -1411,6 +1407,20 @@ void main() { ); }); + test('renames wildcard parameters', () async { + await expectSingleNonNullableOutput( + dedent(r''' + class Foo { + void m(int _, int _) {} + } + '''), + _containsAllOf( + 'void m(int? _0, int? _1) => super.noSuchMethod(Invocation.method(', + 'Invocation.method(#m, [_0, _1])', + ), + ); + }); + test('widens the type of parameters to be nullable', () async { await expectSingleNonNullableOutput( dedent(r''' @@ -2046,6 +2056,23 @@ void main() { ); }); + test('overrides nullable instance setters with wildcard parameters', + () async { + await expectSingleNonNullableOutput( + dedent(''' + class Foo { + void set m(int? _) {} + } + '''), + _containsAllOf(dedent2(''' + set m(int? _value) => super.noSuchMethod( + Invocation.setter(#m, _value), + returnValueForMissingStub: null, + ); + ''')), + ); + }); + test('overrides inherited non-nullable instance setters', () async { await expectSingleNonNullableOutput( dedent(''' From 03eb15a3948db3088718b72a470601e70cbdd89f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 20:53:08 +0000 Subject: [PATCH 595/607] Bump the github-actions group across 1 directory with 2 updates Bumps the github-actions group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart). Updates `actions/checkout` from 4.1.7 to 4.2.2 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/692973e3d937129bcbf40652eb9f2f61becf3332...11bd71901bbe5b1630ceea73d27597364c9af683) Updates `dart-lang/setup-dart` from 1.6.5 to 1.7.0 - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/0a8a0fc875eb934c15d08629302413c671d3f672...e630b99d28a3b71860378cafdc2a067c71107f94) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- pkgs/mockito/.github/workflows/test-package.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index 614f9f7f2..ad600e32c 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -21,8 +21,8 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: 3.5.0 - id: install @@ -39,8 +39,8 @@ jobs: matrix: sdk: [3.5.0, dev] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: ${{ matrix.sdk }} - id: install @@ -61,8 +61,8 @@ jobs: os: [ubuntu-latest] sdk: [3.5.0, dev] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: ${{ matrix.sdk }} - id: install @@ -86,8 +86,8 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: dev - id: install From dbb8e112ec89cb73fc8840a0c0b91200bf5d18bb Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 2 Dec 2024 22:56:13 -0800 Subject: [PATCH 596/607] Restrict all CI actions to Dart dev channel --- .../mockito/.github/workflows/test-package.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/pkgs/mockito/.github/workflows/test-package.yml index ad600e32c..b5331a389 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/pkgs/mockito/.github/workflows/test-package.yml @@ -15,7 +15,7 @@ env: permissions: read-all jobs: - # Check code formatting with the stable SDK. + # Check code formatting with the dev SDK. format: runs-on: ubuntu-latest strategy: @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: - sdk: 3.5.0 + sdk: dev - id: install name: Install dependencies run: dart pub get @@ -37,7 +37,9 @@ jobs: strategy: fail-fast: false matrix: - sdk: [3.5.0, dev] + # TODO(srawlins): Re-enable stable Dart when 3.6.0 is available. + #sdk: [3.5.0, dev] + sdk: [dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 @@ -59,7 +61,9 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - sdk: [3.5.0, dev] + # TODO(srawlins): Re-enable stable Dart when 3.6.0 is available. + #sdk: [3.5.0, dev] + sdk: [dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 @@ -75,7 +79,11 @@ jobs: run: dart run build_runner build --fail-on-severe if: always() && steps.install.outcome == 'success' - name: Run DDC tests - run: dart run build_runner test -- --platform chrome + # We run with `--concurrency=1` because of a bug in the test package, + # I believe: https://github.com/dart-lang/test/issues/2294. We can + # look into removing this flag when we are using a version of the test + # package without this bug. + run: dart run build_runner test -- --platform chrome --concurrency=1 if: always() && steps.install.outcome == 'success' document: From 304c7655f67aa70f06e683f03ea399a9efaa0663 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 29 Oct 2024 11:55:18 -0400 Subject: [PATCH 597/607] parse records and typedef in type arguments --- pkgs/mockito/CHANGELOG.md | 3 + pkgs/mockito/lib/src/builder.dart | 27 ++++--- .../mockito/test/builder/auto_mocks_test.dart | 76 +++++++++++++++++++ 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 51c8ada35..1f384b0aa 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -9,6 +9,9 @@ * Require dart_style >= 2.3.7, so that the current Dart language version can be passed to `DartFormatter`. * Add topics to `pubspec.yaml`. +* Fix a bug where typedef-aliases in type arguments were not correctly + resolved. +* Fix a bug where record types were not correctly resolved. ## 5.4.4 diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 7fd81d7f8..edce0402f 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -167,25 +167,28 @@ $rawOutput // `Future`, which is needed when overriding some methods which return // `FutureOr`. final typeVisitor = _TypeVisitor(entryLib.typeProvider.futureDynamicType); - final seenTypes = {}; + final seenTypes = {}; final librariesWithTypes = {}; - void addTypesFrom(analyzer.InterfaceType type) { + void addTypesFrom(analyzer.DartType type) { // Prevent infinite recursion. if (seenTypes.contains(type)) { return; } seenTypes.add(type); - librariesWithTypes.add(type.element.library); - type.element.accept(typeVisitor); - if (type.alias != null) type.alias!.element.accept(typeVisitor); - // For a type like `Foo`, add the `Bar`. - type.typeArguments - .whereType() - .forEach(addTypesFrom); - // For a type like `Foo extends Bar`, add the `Baz`. - for (final supertype in type.allSupertypes) { - addTypesFrom(supertype); + librariesWithTypes.addAll([ + if (type.element?.library != null) type.element!.library!, + if (type.alias?.element.library != null) type.alias!.element.library, + ]); + type.element?.accept(typeVisitor); + type.alias?.element.accept(typeVisitor); + switch (type) { + case analyzer.InterfaceType interface: + interface.typeArguments.forEach(addTypesFrom); + interface.allSupertypes.forEach(addTypesFrom); + case analyzer.RecordType record: + record.positionalTypes.forEach(addTypesFrom); + record.namedTypes.map((e) => e.type).forEach(addTypesFrom); } } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index 4c2d0d46f..cc5287017 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3582,6 +3582,34 @@ void main() { expect(mocksContent, contains('class MockBaz extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Baz')); }); + + test('when a type parameter is a typedef a function', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + typedef CreateInt = int Function(); + + class BaseFoo { + BaseFoo(this.t); + final T t; + } + + class Foo extends BaseFoo { + Foo() : super(() => 1); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Foo]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); }); test('generation throws when the aliased type is nullable', () { @@ -3649,6 +3677,54 @@ void main() { contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('), contains('bar: _FakeBar_0(')))); }); + test('are supported as typedefs', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + class BaseFoo { + BaseFoo(this.t); + final T t; + } + class Foo extends BaseFoo<(Bar, Bar)> { + Foo() : super((Bar(), Bar())); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); + test('are supported as nested typedefs', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + class BaseFoo { + BaseFoo(this.t); + final T t; + } + class Foo extends BaseFoo<(int, (Bar, Bar))> { + Foo() : super(((1, (Bar(), Bar())))); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); }); group('Extension types', () { From 76bae53ac264c307cd2258677b4616d3f8152d39 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 29 Oct 2024 13:05:39 -0400 Subject: [PATCH 598/607] parse the contents of a typedef --- pkgs/mockito/lib/src/builder.dart | 1 + pkgs/mockito/test/builder/auto_mocks_test.dart | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index edce0402f..85ffc6131 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -317,6 +317,7 @@ class _TypeVisitor extends RecursiveElementVisitor { @override void visitTypeAliasElement(TypeAliasElement element) { + _addType(element.aliasedType); _elements.add(element); super.visitTypeAliasElement(element); } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index cc5287017..d92a18fd5 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3583,18 +3583,21 @@ void main() { expect(mocksContent, contains('implements _i2.Baz')); }); - test('when a type parameter is a typedef a function', () async { + test( + 'when a type parameter is a typedef a function which returns another type', + () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' - typedef CreateInt = int Function(); + class Bar {} + typedef CreateBar = Bar Function(); class BaseFoo { BaseFoo(this.t); final T t; } - class Foo extends BaseFoo { + class Foo extends BaseFoo { Foo() : super(() => 1); } '''), From 1a5c9d27ad8d174a38cd978c28a2d27c565c7027 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 29 Oct 2024 13:58:31 -0400 Subject: [PATCH 599/607] allow duplicate typedefs --- pkgs/mockito/lib/src/builder.dart | 4 +++ .../mockito/test/builder/auto_mocks_test.dart | 31 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 85ffc6131..cfef0f64a 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -173,6 +173,10 @@ $rawOutput void addTypesFrom(analyzer.DartType type) { // Prevent infinite recursion. if (seenTypes.contains(type)) { + if (type.alias != null) { + // To check for duplicate typdefs that have different names + type.alias!.element.accept(typeVisitor); + } return; } seenTypes.add(type); diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index d92a18fd5..f5a0eef72 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3583,8 +3583,7 @@ void main() { expect(mocksContent, contains('implements _i2.Baz')); }); - test( - 'when a type parameter is a typedef a function which returns another type', + test('when its a type parameter of function which returns another type', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, @@ -3610,6 +3609,34 @@ void main() { ''' }); + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); + test('when its a duplicate type parameter', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + typedef BarDef = int Function(); + typedef BarDef2 = int Function(); + class BaseFoo { + BaseFoo(this.t1, this.t2); + final T t1; + final P t2; + } + class Foo extends BaseFoo { + Foo() : super(() => 1, () => 2); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Foo]) + void main() {} + ''' + }); + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Foo')); }); From 1e4b35886bd433f2ba2feb03a9f0edd3ba801aa1 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Wed, 6 Nov 2024 14:33:21 -0500 Subject: [PATCH 600/607] fix nits --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/lib/src/builder.dart | 12 ++++++++---- pkgs/mockito/test/builder/auto_mocks_test.dart | 12 ++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index 1f384b0aa..da6ae6fb8 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -9,7 +9,7 @@ * Require dart_style >= 2.3.7, so that the current Dart language version can be passed to `DartFormatter`. * Add topics to `pubspec.yaml`. -* Fix a bug where typedef-aliases in type arguments were not correctly +* Fix a bug where type aliases in type arguments were not correctly resolved. * Fix a bug where record types were not correctly resolved. diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index cfef0f64a..534343be2 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -180,10 +180,14 @@ $rawOutput return; } seenTypes.add(type); - librariesWithTypes.addAll([ - if (type.element?.library != null) type.element!.library!, - if (type.alias?.element.library != null) type.alias!.element.library, - ]); + + if (type.element?.library case var library?) { + librariesWithTypes.add(library); + } + if (type.alias?.element.library case var library?) { + librariesWithTypes.add(library); + } + type.element?.accept(typeVisitor); type.alias?.element.accept(typeVisitor); switch (type) { diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index f5a0eef72..d25bc25c9 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3583,8 +3583,7 @@ void main() { expect(mocksContent, contains('implements _i2.Baz')); }); - test('when its a type parameter of function which returns another type', - () async { + test('when it\'s a function which returns any type', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -3612,14 +3611,15 @@ void main() { expect(mocksContent, contains('class MockFoo extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Foo')); }); - test('when its a duplicate type parameter', () async { + test('when the underlying type is identical to another type alias', + () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' class Bar {} typedef BarDef = int Function(); typedef BarDef2 = int Function(); - class BaseFoo { + class BaseFoo { BaseFoo(this.t1, this.t2); final T t1; final P t2; @@ -3707,7 +3707,7 @@ void main() { contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('), contains('bar: _FakeBar_0(')))); }); - test('are supported as typedefs', () async { + test('are supported as type arguments', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -3731,7 +3731,7 @@ void main() { expect(mocksContent, contains('class MockFoo extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Foo')); }); - test('are supported as nested typedefs', () async { + test('are supported as nested type arguments', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' From 25f284f90b73e5fae0830dea7ed5beb0f3264c0e Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Wed, 6 Nov 2024 14:44:16 -0500 Subject: [PATCH 601/607] fix identical type alias test --- pkgs/mockito/test/builder/auto_mocks_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index d25bc25c9..c6d66512e 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3639,6 +3639,8 @@ void main() { expect(mocksContent, contains('class MockFoo extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Foo')); + expect(mocksContent, contains('_i2.BarDef get t1')); + expect(mocksContent, contains('_i2.BarDef2 get t2')); }); }); From 385c043c96ba8c468e601d4786776ffeeacba617 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 4 Dec 2024 07:07:04 -0800 Subject: [PATCH 602/607] Resolve for typedefs/aliases and records in typeArguments & resolve duplicate typedefs https://github.com/dart-lang/mockito/pull/776 Closes: https://github.com/dart-lang/mockito/issues/775 Closes: https://github.com/dart-lang/mockito/issues/777 Closes: https://github.com/dart-lang/mockito/issues/778 The fix for these issues is closely related so I've done both of them in a single PR. - Mockito wasn't checking for typedefs/aliases in `typeArguments` - it also wasn't looking into records on type args. - Typedefs which had the same underlying type were ignored even if the typedef had a different name These issues cause the dreaded `is missing from the asset URI mapping` error. See the related issue for a repro of each of these. I've also added regression tests for all of these. PiperOrigin-RevId: 702712435 --- pkgs/mockito/lib/src/builder.dart | 10 +++++----- pkgs/mockito/test/builder/auto_mocks_test.dart | 4 ---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pkgs/mockito/lib/src/builder.dart b/pkgs/mockito/lib/src/builder.dart index 534343be2..57d2ca1a6 100644 --- a/pkgs/mockito/lib/src/builder.dart +++ b/pkgs/mockito/lib/src/builder.dart @@ -181,22 +181,22 @@ $rawOutput } seenTypes.add(type); - if (type.element?.library case var library?) { + if (type.element?.library case final library?) { librariesWithTypes.add(library); } - if (type.alias?.element.library case var library?) { + if (type.alias?.element.library case final library?) { librariesWithTypes.add(library); } type.element?.accept(typeVisitor); type.alias?.element.accept(typeVisitor); switch (type) { - case analyzer.InterfaceType interface: + case final analyzer.InterfaceType interface: interface.typeArguments.forEach(addTypesFrom); interface.allSupertypes.forEach(addTypesFrom); - case analyzer.RecordType record: + case final analyzer.RecordType record: record.positionalTypes.forEach(addTypesFrom); - record.namedTypes.map((e) => e.type).forEach(addTypesFrom); + record.namedFields.map((e) => e.type).forEach(addTypesFrom); } } diff --git a/pkgs/mockito/test/builder/auto_mocks_test.dart b/pkgs/mockito/test/builder/auto_mocks_test.dart index c6d66512e..7a5aec291 100644 --- a/pkgs/mockito/test/builder/auto_mocks_test.dart +++ b/pkgs/mockito/test/builder/auto_mocks_test.dart @@ -3589,12 +3589,10 @@ void main() { 'foo|lib/foo.dart': dedent(r''' class Bar {} typedef CreateBar = Bar Function(); - class BaseFoo { BaseFoo(this.t); final T t; } - class Foo extends BaseFoo { Foo() : super(() => 1); } @@ -3602,7 +3600,6 @@ void main() { 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; - @GenerateMocks([Foo]) void main() {} ''' @@ -3631,7 +3628,6 @@ void main() { 'foo|test/foo_test.dart': ''' import 'package:foo/foo.dart'; import 'package:mockito/annotations.dart'; - @GenerateMocks([Foo]) void main() {} ''' From bd0b56b9bc34b7e18a0c89c2037f695280420cab Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 4 Dec 2024 09:10:44 -0800 Subject: [PATCH 603/607] Bump version to 5.4.5 Also bump some dep constraints: * analyzer ^6.9.0 is required as mockito uses some APIs that were introduced in that version. * That version of analyzer requires a Dart 3.6.0 SDK, so bump that constraint. * build_runner ^6.11.0 is required to avoid an issue looking for the wrong frontend_server snapshot. PiperOrigin-RevId: 702747878 --- pkgs/mockito/CHANGELOG.md | 11 ++++++----- pkgs/mockito/lib/src/version.dart | 2 +- pkgs/mockito/pubspec.yaml | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index da6ae6fb8..fe2babbfb 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,13 +1,14 @@ -## 5.4.5-wip +## 5.4.5 * Ignore "must_be_immutable" warning in generated files. Mocks cannot be made immutable anyway, but this way users aren't prevented from using generated mocks altogether. -* Require Dart >= 3.3.0. -* Require analyzer 6.4.1. -* Add support for extension types. -* Require dart_style >= 2.3.7, so that the current Dart language version can be +* Require Dart SDK ^3.6.0-0. +* Require analyzer ^6.9.0. +* Require build_runner ^2.4.11. +* Require dart_style ^2.3.7, so that the current Dart language version can be passed to `DartFormatter`. +* Add support for extension types. * Add topics to `pubspec.yaml`. * Fix a bug where type aliases in type arguments were not correctly resolved. diff --git a/pkgs/mockito/lib/src/version.dart b/pkgs/mockito/lib/src/version.dart index 92fac2172..21a36ad6d 100644 --- a/pkgs/mockito/lib/src/version.dart +++ b/pkgs/mockito/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.4.5-wip'; +const packageVersion = '5.4.5'; diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index 561450741..c41ff6c88 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.5-wip +version: 5.4.5 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. @@ -10,10 +10,10 @@ topics: - mocking environment: - sdk: ^3.3.0 + sdk: ^3.6.0-0 dependencies: - analyzer: '>=6.4.1 <7.0.0' + analyzer: '>=6.9.0 <7.0.0' build: ^2.0.0 code_builder: ^4.5.0 collection: ^1.15.0 @@ -25,7 +25,7 @@ dependencies: test_api: '>=0.2.1 <0.8.0' dev_dependencies: - build_runner: ^2.0.0 + build_runner: ^2.4.11 build_test: ^2.0.0 build_web_compilers: '>=3.0.0 <5.0.0' http: ^1.0.0 From 6876fe9fb00f60adfba724cf433d38fa7741bc59 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 12 Dec 2024 13:32:00 +0100 Subject: [PATCH 604/607] Add issue template and other fixes --- .github/ISSUE_TEMPLATE/mockito.md | 5 +++++ pkgs/mockito/CONTRIBUTING.md | 29 ----------------------------- pkgs/mockito/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 30 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/mockito.md delete mode 100644 pkgs/mockito/CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/mockito.md b/.github/ISSUE_TEMPLATE/mockito.md new file mode 100644 index 000000000..27d7b1ba7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/mockito.md @@ -0,0 +1,5 @@ +--- +name: "package:mockito" +about: "Create a bug or file a feature request against package:mockito." +labels: "package:mockito" +--- \ No newline at end of file diff --git a/pkgs/mockito/CONTRIBUTING.md b/pkgs/mockito/CONTRIBUTING.md deleted file mode 100644 index c363dda1b..000000000 --- a/pkgs/mockito/CONTRIBUTING.md +++ /dev/null @@ -1,29 +0,0 @@ -Want to contribute? Great! First, read this page (including the small print at the end). - -### Before you contribute - -Before we can use your code, you must sign the -[Google Individual Contributor License Agreement] -(https://cla.developers.google.com/about/google-individual) -(CLA), which you can do online. The CLA is necessary mainly because you own the -copyright to your changes, even after your contribution becomes part of our -codebase, so we need your permission to use and distribute your code. We also -need to be sure of various other things—for instance that you'll tell us if you -know that your code infringes on other people's patents. You don't have to sign -the CLA until after you've submitted your code for review and a member has -approved it, but you must do it before we can put your code into our codebase. -Before you start working on a larger contribution, you should get in touch with -us first through the issue tracker with your idea so that we can help out and -possibly guide you. Coordinating up front makes it much easier to avoid -frustration later on. - -### Code reviews - -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. - -### The small print - -Contributions made by corporations are covered by a different agreement than -the one above, the -[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index c41ff6c88..de86fd90e 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -3,7 +3,7 @@ version: 5.4.5 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. -repository: https://github.com/dart-lang/mockito +repository: https://github.com/dart-lang/test/tree/master/pkgs/mockito topics: - testing From 7c7cbf58a2ab45c7ad558846222ec5b1349a4785 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 12 Dec 2024 13:43:14 +0100 Subject: [PATCH 605/607] Moving fixes for mockito --- .../workflows/mockito.yaml | 24 ++++++++++++------- README.md | 1 + pkgs/mockito/.github/dependabot.yml | 15 ------------ pkgs/mockito/CHANGELOG.md | 1 + pkgs/mockito/README.md | 6 ++--- pubspec.yaml | 1 + 6 files changed, 21 insertions(+), 27 deletions(-) rename pkgs/mockito/.github/workflows/test-package.yml => .github/workflows/mockito.yaml (90%) delete mode 100644 pkgs/mockito/.github/dependabot.yml diff --git a/pkgs/mockito/.github/workflows/test-package.yml b/.github/workflows/mockito.yaml similarity index 90% rename from pkgs/mockito/.github/workflows/test-package.yml rename to .github/workflows/mockito.yaml index b5331a389..90fd4d018 100644 --- a/pkgs/mockito/.github/workflows/test-package.yml +++ b/.github/workflows/mockito.yaml @@ -1,18 +1,24 @@ -name: Dart CI +name: package:mockito + +permissions: read-all on: - # Run on PRs and pushes to the default branch. - push: - branches: [ master ] + # Run CI on all PRs (against any branch) and on pushes to the master branch. pull_request: + paths: + - '.github/workflows/mockito.yaml' + - 'pkgs/mockito/**' + push: branches: [ master ] + paths: + - '.github/workflows/mockito.yaml' + - 'pkgs/mockito/**' schedule: - - cron: "0 0 * * 0" - -env: - PUB_ENVIRONMENT: bot.github + - cron: '0 0 * * 0' # weekly -permissions: read-all +defaults: + run: + working-directory: pkgs/mockito jobs: # Check code formatting with the dev SDK. diff --git a/README.md b/README.md index 25b50aab7..c42195894 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ literate API. | [checks](pkgs/checks/) | A framework for checking values against expectations and building custom expectations. | [![pub package](https://img.shields.io/pub/v/checks.svg)](https://pub.dev/packages/checks) | | [fake_async](pkgs/fake_async/) | Fake asynchronous events such as timers and microtasks for deterministic testing. | [![pub package](https://img.shields.io/pub/v/fake_async.svg)](https://pub.dev/packages/fake_async) | | [matcher](pkgs/matcher/) | Support for specifying test expectations via an extensible Matcher class. | [![pub package](https://img.shields.io/pub/v/matcher.svg)](https://pub.dev/packages/matcher) | +| [mockito](pkgs/mockito/) | A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. | [![pub package](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito) | | [test](pkgs/test/) | A full featured library for writing and running Dart tests across platforms. | [![pub package](https://img.shields.io/pub/v/test.svg)](https://pub.dev/packages/test) | | [test_api](pkgs/test_api/) | | [![pub package](https://img.shields.io/pub/v/test_api.svg)](https://pub.dev/packages/test_api) | | [test_core](pkgs/test_core/) | | [![pub package](https://img.shields.io/pub/v/test_core.svg)](https://pub.dev/packages/test_core) | diff --git a/pkgs/mockito/.github/dependabot.yml b/pkgs/mockito/.github/dependabot.yml deleted file mode 100644 index 7c3a9d907..000000000 --- a/pkgs/mockito/.github/dependabot.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Dependabot configuration file. -# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates - -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: monthly - labels: - - autosubmit - groups: - github-actions: - patterns: - - "*" diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index fe2babbfb..b9c93e9d0 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -13,6 +13,7 @@ * Fix a bug where type aliases in type arguments were not correctly resolved. * Fix a bug where record types were not correctly resolved. +* Move to `dart-lang/test` monorepo. ## 5.4.4 diff --git a/pkgs/mockito/README.md b/pkgs/mockito/README.md index a9fdfac62..ee8aca0c4 100644 --- a/pkgs/mockito/README.md +++ b/pkgs/mockito/README.md @@ -1,4 +1,4 @@ -[![Dart CI](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml) +[![Build Status](https://github.com/dart-lang/test/actions/workflows/mockito.yaml/badge.svg)](https://github.com/dart-lang/test/actions/workflows/mockito.yaml) [![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito) [![package publisher](https://img.shields.io/pub/publisher/mockito.svg)](https://pub.dev/packages/mockito/publisher) @@ -65,7 +65,7 @@ you want to add Mockito annotations in other places, you will need to add a The generated mock class, `MockCat`, extends Mockito's Mock class and implements the Cat class, giving us a class which supports stubbing and verifying. -[NULL_SAFETY_README]: https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md +[NULL_SAFETY_README]: https://github.com/dart-lang/test/blob/master/pkgs/mockito/NULL_SAFETY_README.md ## Let's verify some behavior! @@ -460,7 +460,7 @@ utilities on a class which `extends Mock` if it helps with code structure. ## Frequently asked questions Read more information about this package in the -[FAQ](https://github.com/dart-lang/mockito/blob/master/FAQ.md). +[FAQ](https://github.com/dart-lang/test/blob/master/pkgs/mockito/FAQ.md). [`verify`]: https://pub.dev/documentation/mockito/latest/mockito/verify.html [`verifyInOrder`]: https://pub.dev/documentation/mockito/latest/mockito/verifyInOrder.html diff --git a/pubspec.yaml b/pubspec.yaml index 2e08374f9..3107fa98d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,5 +10,6 @@ workspace: - pkgs/test - pkgs/test_api - pkgs/test_core + - pkgs/mockito dev_dependencies: dart_flutter_team_lints: ^3.1.0 From 1b7c9b20e88e4314f13c5b295196b6822384a00e Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 12 Dec 2024 13:46:38 +0100 Subject: [PATCH 606/607] Use dev version --- pkgs/mockito/CHANGELOG.md | 2 +- pkgs/mockito/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/mockito/CHANGELOG.md b/pkgs/mockito/CHANGELOG.md index b9c93e9d0..b280afd61 100644 --- a/pkgs/mockito/CHANGELOG.md +++ b/pkgs/mockito/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.5 +## 5.4.5-dev.0 * Ignore "must_be_immutable" warning in generated files. Mocks cannot be made immutable anyway, but this way users aren't prevented from using generated diff --git a/pkgs/mockito/pubspec.yaml b/pkgs/mockito/pubspec.yaml index de86fd90e..d177db12b 100644 --- a/pkgs/mockito/pubspec.yaml +++ b/pkgs/mockito/pubspec.yaml @@ -1,5 +1,5 @@ name: mockito -version: 5.4.5 +version: 5.4.5-dev.0 description: >- A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing. From 99f18437344947cf63e1d41bbf40198bc7d3341f Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 12 Dec 2024 13:47:51 +0100 Subject: [PATCH 607/607] Remove from workspace resolution --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 3107fa98d..2e08374f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,5 @@ workspace: - pkgs/test - pkgs/test_api - pkgs/test_core - - pkgs/mockito dev_dependencies: dart_flutter_team_lints: ^3.1.0
Release notes

Sourced from actions/checkout's releases.

v3.5.2

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v3.5.1...v3.5.2

v3.5.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3.5.0...v3.5.1