0

I'm trying to verify some methods are called for a newly created instance.

I followed Spock guide of mock constructor but it says too few invocations.

Too few invocations for:

3 * mockStep.addTarget(_)   (0 invocations)

Unmatched invocations (ordered by similarity):

None

Pasting my code here...

public class Workflow {
    public void createStep() {
        Step step = new Step();
        step.addTarget("A");
        step.addTarget("B");
        step.addTarget("C");
    }
}
class WorkflowTest extends Specification {
    Workflow workflow

    def setup() {
        workflow = new Workflow()
    }

    def "each new step should have 3 targets" () {
        given:
        def mockStep = Mock(Step)
        GroovySpy(Step, global: true)
        new Step() >> mockStep

        when:
        workflow.createStep()

        then:
        3 * mockStep.addTarget(_)
    }
}

In the above code I'm trying to let all new Step() returns the mocked Step and to verify the mockStep.addTarget() is called 3 times. When I ran in debug mode, it seems Step step = new Step(); still returns a new instance instead of the mocked Step.

Steven.Q
  • 9
  • 1

2 Answers2

1

Don't use dirty tricks like global Groovy mocks but refactor for decoupling, dependency injection and the resulting better testability instead. Please read the "general comments" and "some more remarks" sections of my answer here for a reason why you should refactor. I do not want to repeat everything here.

As for the technical reason why your global Groovy mock does not work from Java code, I am quoting the Spock manaual:

When should Groovy mocks be favored over regular mocks?

Groovy mocks should be used when the code under specification is written in Groovy and some of the unique Groovy mock features are needed. When called from Java code, Groovy mocks will behave like regular mocks. Note that it isn’t necessary to use a Groovy mock merely because the code under specification and/or mocked type is written in Groovy. Unless you have a concrete reason to use a Groovy mock, prefer a regular mock.


Update:

The quickest way to refactor is to separate creating the step from adding targets. Actually the name createStep implies the method does just that. But instead it also adds targets. Use two methods for that and a method doing the workflow. Then you can use a Spy for stubbing createStep (which returns the created Step instance) and then check interactions if you really think that should be tested at all. Checking internal interactions is often a code smell (over-specification).

Alternatively, add a factory class for steps and inject an instance into the workflow class. Then you can easily mock the factory class and make it return a mock step.

If you don't understand what I mean by the two alternatives, please let me know.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Accepting the answer by clicking the grey checkmark (making it green) would be even nicer. – kriegaex Apr 12 '20 at 10:30
  • Thank you so much for the quick answer @kriegaex! I totally understand your suggestions. However, the code in the answer is actually a small part of a large code base. I'm trying to add unit test for our legacy code and it's very hard/risky to refactor for some reason. What I'm trying to understand here is why my above GroovySpy wasn't work as expected. I believe this "dirty trick" as you mentioned should work, but I can't figure out why `new Step() >> mockStep` didn't make it return the mockStep – Steven.Q Apr 12 '20 at 10:43
  • Then you did not read my answer carefully, even though I quoted the Spock manual for you and even highlighted the specific explanation in ***bold and italics***. So no, the dirty trick should not work because your target code is Java, not Groovy. Meta-programming features used for global mocks are unavailable in Java, they only work in Groovy, i.e. for classes implemented in Groovy. – kriegaex Apr 12 '20 at 12:23
0

This is a beta feature and no grantee it will work as expected and it will be depending on the version you are using .

Please refer to official documentation