From 2a921297938eadd91ee2b0349597bcd19bc7575e Mon Sep 17 00:00:00 2001 From: Denis Kudriashov Date: Wed, 12 Feb 2020 23:53:41 +0000 Subject: [PATCH 1/3] failing test describing the idea that any expectation should fail when it's defined without #stub message --- Mocketry-Domain-Tests/MockAcceptanceTests.class.st | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Mocketry-Domain-Tests/MockAcceptanceTests.class.st b/Mocketry-Domain-Tests/MockAcceptanceTests.class.st index 9eb78b9..5a9b545 100644 --- a/Mocketry-Domain-Tests/MockAcceptanceTests.class.st +++ b/Mocketry-Domain-Tests/MockAcceptanceTests.class.st @@ -120,6 +120,13 @@ MockAcceptanceTests >> testBuildingExpectationForMocksReturnedFromMessages [ mock someMessage2 extraMessage should be: #result ] +{ #category : #tests } +MockAcceptanceTests >> testBuildingExpectationWithoutStubShouldAlwaysFail [ + + [mock someMessage willReturn: #result] + should fail +] + { #category : #tests } MockAcceptanceTests >> testBuildingExpectationsByBlock [ From 594e2e70260fbcd3a3b833697b56971ef9800d47 Mon Sep 17 00:00:00 2001 From: Denis Kudriashov Date: Thu, 20 Feb 2020 22:07:49 +0000 Subject: [PATCH 2/3] Expectation methods (teacher API) are marked with pragma . Such methods are not allowed to be send to mocks to prevent user mistakes when it forget #stub message with defining an expectation for a mock. --- .../MockAcceptanceTests.class.st | 8 ++++- Mocketry-Domain/MockBehaviour.class.st | 5 ++- Mocketry-Domain/MockExpectedMessage.class.st | 32 +++++++++++++++---- Mocketry-Domain/MockOccurredMessage.class.st | 17 ++++++++++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Mocketry-Domain-Tests/MockAcceptanceTests.class.st b/Mocketry-Domain-Tests/MockAcceptanceTests.class.st index 5a9b545..b73fdc3 100644 --- a/Mocketry-Domain-Tests/MockAcceptanceTests.class.st +++ b/Mocketry-Domain-Tests/MockAcceptanceTests.class.st @@ -124,7 +124,13 @@ MockAcceptanceTests >> testBuildingExpectationForMocksReturnedFromMessages [ MockAcceptanceTests >> testBuildingExpectationWithoutStubShouldAlwaysFail [ [mock someMessage willReturn: #result] - should fail + should fail. + + [mock willReturn: #result] + should fail. + + [mock stub willReturn: #result] + should fail. ] { #category : #tests } diff --git a/Mocketry-Domain/MockBehaviour.class.st b/Mocketry-Domain/MockBehaviour.class.st index 2586fb5..424935c 100644 --- a/Mocketry-Domain/MockBehaviour.class.st +++ b/Mocketry-Domain/MockBehaviour.class.st @@ -233,7 +233,10 @@ MockBehaviour >> send: aMessage to: aMockObject [ receiver: aMockObject selector: aMessage selector arguments: aMessage arguments. - + "Following is a hook to prevent user mistakes do to missing #stub message for expectations. + Check method comment for details" + occurredMessage shouldBeAllowedForMock. + ^mockRole processMessageSend: occurredMessage by: self ] diff --git a/Mocketry-Domain/MockExpectedMessage.class.st b/Mocketry-Domain/MockExpectedMessage.class.st index 3b6f078..8c81f83 100644 --- a/Mocketry-Domain/MockExpectedMessage.class.st +++ b/Mocketry-Domain/MockExpectedMessage.class.st @@ -29,9 +29,17 @@ And to specify conditions which should be valid when expectation is executed use - when: aBlock is: aSpecOfObjectState - when: aBlock satisfying: aBlock -- shouldOccurInThisThread -- shouldOccurInAnotherThread - +- shouldBeSentInThisThread +- shouldBeSentInAnotherThread + +In addition I mark particular methods with pragma . It allows to prevent user mistakes when expectations are defined without #stub message to the mock. It is easy to do such mistake. So such messages (mock teacher API) are completely forbidden for mocks. For example following expressions fail: + + mock someMessage willReturn: 1. + mock will: [2]. + mock stub willRaise: Error new + +It means that users can't stub such messages but it is reasonable restriction. + Internal Representation and Key Implementation Points. Instance Variables @@ -49,7 +57,7 @@ Class { 'actions', 'conditionsSpec' ], - #category : 'Mocketry-Domain' + #category : #'Mocketry-Domain' } { #category : #'instance creation' } @@ -125,11 +133,13 @@ MockExpectedMessage >> restrictUsage [ { #category : #conditions } MockExpectedMessage >> shouldBeSentInAnotherProcess [ + conditionsSpec addSpec: SpecOfAsynchMessage forActiveProcess ] { #category : #conditions } MockExpectedMessage >> shouldBeSentInThisProcess [ + conditionsSpec addSpec: SpecOfAsynchMessage forActiveProcess not ] @@ -178,52 +188,62 @@ MockExpectedMessage >> useTwice [ ] { #category : #conditions } -MockExpectedMessage >> when: subjectBlock is: aSpecOfOBjectState [ - conditionsSpec addSpec: (SpecOfMessageSendCondition of: subjectBlock by: aSpecOfOBjectState) +MockExpectedMessage >> when: subjectBlock is: aSpecOfObjectState [ + + conditionsSpec addSpec: (SpecOfMessageSendCondition of: subjectBlock by: aSpecOfObjectState) ] { #category : #conditions } MockExpectedMessage >> when: subjectBlock satisfy: conditionBlock [ + self when: subjectBlock is: (Satisfying for: conditionBlock) ] { #category : #actions } MockExpectedMessage >> will: anObject [ + actions add: anObject asMockExpectationAction ] { #category : #actions } MockExpectedMessage >> willCallOriginalMethod [ + self will: MockExpectedOriginalMethodCall new ] { #category : #actions } MockExpectedMessage >> willLogMessage [ + self will: MockExpectedMessageLogging new ] { #category : #actions } MockExpectedMessage >> willRaise: anExceptionOrClass [ + self will: (MockExpectedException exception: anExceptionOrClass) ] { #category : #actions } MockExpectedMessage >> willReturn: anObject [ + self will: (MockExpectedValueReturn value: anObject) ] { #category : #actions } MockExpectedMessage >> willReturnValueFrom: anArray [ + self will: (MockExpectedValueForForEachCall values: anArray). spec usage maxCount: anArray size ] { #category : #actions } MockExpectedMessage >> willReturnYourself [ + self will: MockExpectedReceiverReturn new ] { #category : #actions } MockExpectedMessage >> willStubRealResult [ + self will: MockExpectedMethodResultStub new ] diff --git a/Mocketry-Domain/MockOccurredMessage.class.st b/Mocketry-Domain/MockOccurredMessage.class.st index 8436d7f..79e224f 100644 --- a/Mocketry-Domain/MockOccurredMessage.class.st +++ b/Mocketry-Domain/MockOccurredMessage.class.st @@ -152,6 +152,23 @@ MockOccurredMessage >> setUpUnexpectedResult [ ^self extractResultFrom: [ receiver stubDoesNotExpect: self ] ] +{ #category : #controlling } +MockOccurredMessage >> shouldBeAllowedForMock [ + "Method is introduced to prevent user mistakes due to missing #stub message. + It is easy to forget to send #stub message when defining an expectation for a mock. + So with this method following examples fail with explicit error: + mock someMessage willReturn: 3. + mock will: [3]. + mock stub willRaise: Error new" + | expectationMethod | + expectationMethod := MockExpectedMessage compiledMethodAt: selector ifAbsent: [ ^self ]. + (expectationMethod hasPragmaNamed: #dontSendToMock) ifFalse: [ ^self ]. + + GHCurrentMetaLevelDepth increaseFor: [ + self error: '#stub is missing to define expectation using ', selector printString + ] +] + { #category : #printing } MockOccurredMessage >> stringForResultSpec [ From 7817afa9b4279d97b18fddc702aa5b8c3cb3f9ea Mon Sep 17 00:00:00 2001 From: Denis Kudriashov Date: Thu, 20 Feb 2020 22:24:10 +0000 Subject: [PATCH 3/3] Better errors for forbidden messages --- Mocketry-Domain/MockOccurredMessage.class.st | 6 ++++-- Mocketry-Domain/MockRole.class.st | 5 +++++ Mocketry-Domain/MockStubTeacher.class.st | 5 +++++ Mocketry-Domain/MockTeacher.class.st | 5 +++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Mocketry-Domain/MockOccurredMessage.class.st b/Mocketry-Domain/MockOccurredMessage.class.st index 79e224f..adbf04f 100644 --- a/Mocketry-Domain/MockOccurredMessage.class.st +++ b/Mocketry-Domain/MockOccurredMessage.class.st @@ -164,8 +164,10 @@ MockOccurredMessage >> shouldBeAllowedForMock [ expectationMethod := MockExpectedMessage compiledMethodAt: selector ifAbsent: [ ^self ]. (expectationMethod hasPragmaNamed: #dontSendToMock) ifFalse: [ ^self ]. - GHCurrentMetaLevelDepth increaseFor: [ - self error: '#stub is missing to define expectation using ', selector printString + GHCurrentMetaLevelDepth increaseFor: [ | error | + error := receiver ghostBehaviour mockRole isTeaching + ifTrue: [ 'Missing expected message (after #stub)' ] ifFalse: [ 'Missing #stub' ]. + self error: error, ' to define expectation using ', selector printString ] ] diff --git a/Mocketry-Domain/MockRole.class.st b/Mocketry-Domain/MockRole.class.st index 92184fc..223dcca 100644 --- a/Mocketry-Domain/MockRole.class.st +++ b/Mocketry-Domain/MockRole.class.st @@ -26,6 +26,11 @@ MockRole class >> default [ ^default ifNil: [ default := self new ] ] +{ #category : #testing } +MockRole >> isTeaching [ + ^false +] + { #category : #processing } MockRole >> processMessageSend: anOccurredMessage by: aMockBehaviour [ self subclassResponsibility diff --git a/Mocketry-Domain/MockStubTeacher.class.st b/Mocketry-Domain/MockStubTeacher.class.st index f3b8112..f5ee562 100644 --- a/Mocketry-Domain/MockStubTeacher.class.st +++ b/Mocketry-Domain/MockStubTeacher.class.st @@ -18,6 +18,11 @@ MockStubTeacher >> anyMessage [ Any stub anyMessage willReturn: 10 " ] +{ #category : #testing } +MockStubTeacher >> isTeaching [ + ^true +] + { #category : #processing } MockStubTeacher >> processTransformedMessageSend: anOccurredMessage by: aMockBehaviour [ diff --git a/Mocketry-Domain/MockTeacher.class.st b/Mocketry-Domain/MockTeacher.class.st index 1ea72ce..5a09419 100644 --- a/Mocketry-Domain/MockTeacher.class.st +++ b/Mocketry-Domain/MockTeacher.class.st @@ -11,6 +11,11 @@ Class { #category : 'Mocketry-Domain' } +{ #category : #testing } +MockTeacher >> isTeaching [ + ^true +] + { #category : #processing } MockTeacher >> processMessageSend: anOccurredMessage by: aMockBehaviour [