Search code examples
javaunit-testingtestinggroovyspock

Spock Spy/Mock not registering the invocations


I have a method in my test class that just calls two other methods. I am trying to write a test that checks that those two methods are actually invoced, but no invocations are registered. Java code I'm testing:

    public void populateEdgeInfo(Map<Actor, SchedulableNode> knownNodes) {
        populateDestinationInfo(knownNodes);
        populateSourceInfo(knownNodes);
    }

My test code:

def "Populating edge info means both source and destination information will be populated" () {
    given:
    actor.getDstChannels() >> []
    actor.getSrcChannels() >> []
    SchedulableNode schedulable = Spy(SchedulableNode, constructorArgs: [actor])

    when:
    schedulable.populateEdgeInfo([:])

    then:
    1 * schedulable.populateDestinationInfo(_)
    1 * schedulable.populateSourceInfo(_)
}

The only thing registered is the call to populateEdgeInfo. Is there something obvious that I am doing wrong? Also tried using Mock instead of Spy to no avail.


Solution

  • I tried to create an MCVE from your sparse information and found no problems in your test:

    package de.scrum_master.stackoverflow.q60926015;
    
    import java.util.List;
    
    public class Actor {
      public List getDstChannels() {
        return null;
      }
    
      public List getSrcChannels() {
        return null;
      }
    }
    
    package de.scrum_master.stackoverflow.q60926015;
    
    import java.util.Map;
    
    public class SchedulableNode {
      private Actor actor;
    
      public SchedulableNode(Actor actor) {
        this.actor = actor;
      }
    
      public void populateEdgeInfo(Map<Actor, SchedulableNode> knownNodes) {
        populateDestinationInfo(knownNodes);
        populateSourceInfo(knownNodes);
      }
    
      public void populateDestinationInfo(Map<Actor, SchedulableNode> knownNodes) {}
    
      public void populateSourceInfo(Map<Actor, SchedulableNode> knownNodes) {}
    }
    
    package de.scrum_master.stackoverflow.q60926015
    
    import spock.lang.Specification
    
    class SchedulableNodeTest extends Specification {
      def actor = Mock(Actor)
    
      def "Populating edge info means both source and destination information will be populated"() {
        given:
        actor.getDstChannels() >> []
        actor.getSrcChannels() >> []
        SchedulableNode schedulable = Spy(SchedulableNode, constructorArgs: [actor])
    
        when:
        schedulable.populateEdgeInfo([:])
    
        then:
        1 * schedulable.populateDestinationInfo(_)
        1 * schedulable.populateSourceInfo(_)
      }
    }
    

    That means that your code must be different from mine. My guess is that both populate* methods are private in your class, which makes it impossible to mock them because mocks use dynamic proxies and the latter are sub-classes technically. Sub-classes do not see private super-class methods, though, thus a dynamic proxy cannot intercept (calls to) them.

    Possible solutions:

    • Stop over-specifying your tests and testing internal interactions. It makes the test brittle and you have to refactor it often if you also refactor the class under test.

    • Make the populate* methods protected or package-scoped if public is not right. Then you can stub them and check interactions on them.