Search code examples
javaunit-testinggroovymockingspock

How to verify interactions within the class under test?


// class under specification
public class TeamService {

  // method under specification
  public void deleteTeam(String id) {
     /* some other calls */
     this.moveAssets(team) // calls method within the class under spec. 
  }

  // I would like to stub / mock this method
  public void moveAssets(Team team){
    // logic
  } 
  
}

Spock Spec

def deleteTeam(){
   given: 
   TeamService teamService = new TeamService()

   when: 'I delete the team'
   teamService.deleteTeam()
   
   then: 'I want to check that moveAssets gets called(team resources going to be preserved 
and that deleteTeam (external class that deletes the team) gets called (has no issues here)'

   1 * teamService.moveAssets(id) >> {} //See Q1
/* 
Q1: I want to test that this call is made 
but currently it does not count as interaction for some reason so I get an error. 
From what I read you can't stub the calls unless the class is mocked -
 
but here I do have an important need to check on the method within the class under specification 
(that is not mocked). What are my options?

I know I can in theory move moveAssets to some other class,
which I can then mock and accomplish it but that does not feel right. 
*/
}

So in Spock's Spec for this class, TeamService is the class under specification, and the deleteTeam is a method under specification.

The problem I get an error 0 invocations for the moveAssets method. I want to be able to stub / test the moveAssets method there is not a lot of info on how to accomplish that. I don't want to relocate the moveAssets to anotherClass (so I could then mock it). Any guidance is appreciated.

There has to be some better way of dealing with this scenario.


Summary:

How do you test a 'Class Under Specification' teamServices.moveAssets()) method, that is being called from the 'Method Under Specification (teamServices.deleteTeam())'?


https://github.com/spockframework/spock/discussions/1346


Solution

  • Like you noticed already, you can only check interactions on a mocked object type, i.e. mock, stub or spy. The latter, a spy, is what you need in this case, i.e. something like:

    TeamService teamService =
      Spy(constructorArgs: [mockedInjectedService1, mockedInjectedService2])
    

    Here is an MCVE:

    package de.scrum_master.stackoverflow.q67950174
    
    class Team {
      String id
    }
    
    package de.scrum_master.stackoverflow.q67950174
    
    class TeamService {
      def dep1, dep2
    
      TeamService(dep1, dep2) {
        this.dep1 = dep1
        this.dep2 = dep2
      }
    
      boolean moveAssets(Team team) {
        println "Moving assets for team $team"
        return false
      }
    
      void deleteTeam(String id) {
        Team team = findTeamById(id)
        moveAssets(team)
        delete(team)
      }
    
      Team findTeamById(String id) {
        println "Searching for team $team"
        return new Team(id: id)
      }
    
      void delete(Team team) {
        println "Deleting team $team"
      }
    }
    
    package de.scrum_master.stackoverflow.q67950174
    
    import spock.lang.Specification
    
    class TeamServiceTest extends Specification {
      def deleteTeam() {
        given:
        Team teamDocument = new Team(id: "dummy")
        String id = "foo"
        def mockedInjectedService1 = "X"
        def mockedInjectedService2 = "Y"
        TeamService teamService = Spy(constructorArgs: [mockedInjectedService1, mockedInjectedService2])
    
        when: 'I delete the team'
        teamService.deleteTeam(id)
    
        then: 'I want to check that moveAssets gets called(team resources going to be preserved'
        1 * teamService.findTeamById(id) >> teamDocument
        1 * teamService.moveAssets(teamDocument) >> true
      }
    }