zcourts
zcourts

Reputation: 5043

Applying argument constraints with Spock tests

I've got some Java classes that I've written Spock tests for. When I try to verify the exact object used in an argument, the test fails.

    def "all events are published to subscribers"() {
        given:
        EventSubscriber subscriber = Mock()
        when:
        subscriber.shouldRegister() >> true
        subscriber.interests() >> new HashSet<Intent>(Arrays.asList(COUNTER, GAUGE))
        publisher.subscribe(subscriber)
        def event = publisher.newEvent("test", COUNTER)
        event.start()
        def child = event.newChild("child", GAUGE)
        event.finish()
        then:
//        2 * subscriber.onCreate(event)
//        1 * subscriber.onStart(event, event.start)
//        1 * subscriber.onEnd(event, event.end)
//        1 * subscriber.onChild(event, child)
        2 * subscriber.onCreate(_)
        1 * subscriber.onStart(_, _)
        1 * subscriber.onEnd(_, _)
        1 * subscriber.onChild(_, _)
    }

The commented out lines fail with

groovy.lang.MissingPropertyException: No such property: event for class: io.hypi.faf.api.EventPublisherTest

at io.hypi.faf.api.EventPublisherTest.all events are published to subscribers(EventPublisherTest.groovy:28)

http://spockframework.org/spock/docs/1.3/all_in_one.html#_argument_constraints seems to suggests I can just pass a value in. The first example is a string but I presume type doesn't matter.

What have I done wrong with this?

Spock: 1.3 Groovy: 2.5 groovy-test-junit5-2.5.7 JDK: 11

Upvotes: 2

Views: 1661

Answers (1)

cgrim
cgrim

Reputation: 5031

Object instances used in the interaction testing should be defined in the given section.

This would have work:

def 'all events are published to subscribers'() {
    given:
    EventSubscriber subscriber = Mock()
    subscriber.shouldRegister() >> true
    subscriber.interests() >> new HashSet<Intent>(Arrays.asList(COUNTER, GAUGE))
    publisher.subscribe(subscriber)
    def event = publisher.newEvent('test', COUNTER)
    def child = event.newChild('child', GAUGE)

    when:
    event.start()
    event.finish()

    then:
    2 * subscriber.onCreate(event)
    1 * subscriber.onStart(event, event.start)
    1 * subscriber.onEnd(event, event.end)
    1 * subscriber.onChild(event, child)
}

But then there will be a problem with testing of invocation of onCreate() and onChild() because these calls are then not in the when section and only onStart() and onEnd() can match.

I don't have your classes definitions to see details but I guess that event.start and event.end fields are initialized in start() and finish() methods and then 1 * onStart(event, event.start) and 1 * onEnd(event, event.end) test will not match because invocation test will be done in the phase when the event has different start and end values. So then only 1 * onStart(event, _) and 1 * onEnd(event, _) tests will only match.


Look on this simple example which works and is similar to your case but with the difference that the event instance is prepared in the given section and not modified in the when so it will pass the test:

class InvocationWithArgumentsSpec extends Specification {
    void 'Invocation test works with arguments other then String'() {
        given:
        def subscriber = Mock(Subscriber)
        def simplePublisher = new Publisher()
        simplePublisher.subscribe(subscriber)
        def event = new SimpleEvent(name: 'test')

        when:
        simplePublisher.fireEvent(event)

        then:
        1 * subscriber.onEvent(event)
    }
}

interface Subscriber {
    void onEvent(SimpleEvent event)
}

class Publisher {
    Subscriber subscriber

    void subscribe(Subscriber subscriber) {
        this.subscriber = subscriber
    }

    void fireEvent(SimpleEvent event) {
        subscriber.onEvent(event)
    }
}

class SimpleEvent {
    String name
}

Update: But there is a workaround for you. It is not so nice but it works and tests that correct events are passed into subscriber methods (I guess that event has something like name):

def 'all events are published to subscribers'() {
    given:
    EventSubscriber subscriber = Mock()
    subscriber.shouldRegister() >> true
    subscriber.interests() >> new HashSet<Intent>(Arrays.asList(COUNTER, GAUGE))
    publisher.subscribe(subscriber)

    when:
    def event = publisher.newEvent('test', COUNTER)
    event.start()
    event.newChild('child', GAUGE)
    event.finish()

    then:
    1 * subscriber.onCreate({ it.name == 'test' })
    1 * subscriber.onCreate({ it.name == 'child' })
    1 * subscriber.onStart({ it.name == 'test' }, !null)
    1 * subscriber.onEnd({ it.name == 'test' }, !null)
    1 * subscriber.onChild({ it.name == 'test' }, { it.name == 'child' })
}

Upvotes: 2

Related Questions