spock icon indicating copy to clipboard operation
spock copied to clipboard

Global Groovy mock doesn't work as expected

Open flyingclamking opened this issue 7 years ago • 2 comments

Hello, I made a unit test example based on the documentation here: http://spockframework.org/spock/docs/1.1/interaction_based_testing.html

Specifically, I am following the example under the section: Mocking All Instances of a Type

Here is my code:

package library
import spock.lang.Specification

class Publisher {
    def subscribers = []

    def send(event) {
        subscribers.each {
            it.receive(event)
        }
    }
}

interface Subscriber {
    def receive(event)
}

class RealSubscriber implements Subscriber {
    @Override
    def receive(Object event) {
        return null
    }
}


class PublisherSpec extends Specification {


    def "delivers events to all subscribers"() {
        given:
        def pub = new Publisher()
        def sub1 = Mock(Subscriber)
        def sub2 = Mock(Subscriber)
        pub.subscribers << sub1 << sub2

        when:
        pub.send("event")

        then:
        1 * sub1.receive("event")
        1 * sub2.receive("event")
    }

    def "test global mock"() {
        given:
        def pub = new Publisher()
        pub.subscribers << new RealSubscriber() << new RealSubscriber()
        def anySub = GroovyMock(RealSubscriber, global:true)

        when:
        pub.send("event")

        then:
        2 * anySub.receive("event")
    }
}

After running these 2 tests, the first one passes. However the second one failed with below error:

Too few invocations for:

2 * anySub.receive("event")   (0 invocations)

Unmatched invocations (ordered by similarity):

None



	at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:78)
	at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:76)
	at library.PublisherSpec.test global mock(PublisherSpec.groovy:51)

I would expect the second test passes as well, did I miss something in the test? Please help!

Here is my local env: (and I am using latest spock: spock-core:1.1-groovy-2.4-rc-3)

Gradle 4.2

Build time: 2017-09-20 14:48:23 UTC Revision: 5ba503cc17748671c83ce35d7da1cffd6e24dfbd

Groovy: 2.4.11 Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015 JVM: 1.8.0_102 (Oracle Corporation 25.102-b14) OS: Linux 4.10.0-35-generic amd64

flyingclamking avatar Oct 27 '17 19:10 flyingclamking

I also find a weird situation when I try to use global GroovySpy. I copied the unit test example here: https://github.com/spockframework/spock/blob/master/spock-specs/src/test/groovy/org/spockframework/smoke/mock/GroovySpiesThatAreGlobal.groovy and I modify it a litter bit. Here is my code example:

    def "mock dynamic instance method called via MOP and it doesn't work"() {
        def person = new Person()
        def anyPerson = GroovySpy(Person, global: true)

        when:
        person.invokeMethod("foo", [42] as Object[])

        then:
        1 * anyPerson.foo(42) >> "done"
    }

The above test case failed which gives me this error:

groovy.lang.MissingMethodException: No signature of method: static library.GroovySpiesThatAreGlobal.foo() is applicable for argument types: (java.lang.Integer) values: [42] Possible solutions: Mock(), Spy(), any(), find(), Mock(groovy.lang.Closure), Mock(java.lang.Class)

at library.GroovySpiesThatAreGlobal.mock dynamic instance method called via MOP and it doesn't work(GroovySpiesThatAreGlobal.groovy:125)

The only thing I changed is the order of mock and actual creation of the instance. Can someone explain why this test case fails?

flyingclamking avatar Oct 27 '17 21:10 flyingclamking

I have similar issue:

def someData = "cba"

def "test something"() {
    given: "some initial data"
    def initialData = "abc"
    ....
    when: "calling test method"
    def result = testClass.testMethod(initialData)
    then:
    1 * someMockObject.callSomeMethod(initialData) >> someData
}

but when I try to execute such test case -- mock behaviour defined in then clause not working and someMockObject.callSomeMethod() always return null. Spock version is 1.3-groovy-2.5

lyoumi avatar Feb 19 '21 14:02 lyoumi

@flyingclamking So I guess the issue is, that you are creating the new Person() before the GroovySpy(Person, global: true) and that an instance inherits its MetaClass during construction. This means your Person is getting the original "real" Groovy MetaClass. So the GroovySpy can only change instances, which were not already created. Every other Person object which will be created after the GroovySpy call, will get the GroovyMockMetaClass, which will implement the mocking behavior. And this gets reverted in the cleanup phase.

Also to your first example, if you create the pub.subscribers << new RealSubscriber() << new RealSubscriber() after you are doing the GroovySpy(). It will work just fine. Note: You are using an GroovyMock above, which will answer everything with null incl. the Constructor, so you can't just switch the lines, you also need to change it from GroovyMock to GroovySpy to use the constructor of RealSubscriber.

@leonard84 Maybe we should state in the documentation that the order of declaration for a global:true GroovyMock/Stub/Spy is relevant, that the MetaClass changes takes effect.

AndreasTu avatar Aug 12 '23 12:08 AndreasTu