Search code examples
unit-testinggroovyspock

Mocking does not work in parameterized test Spock


So I am trying to learn Spock and I am struggling with creation of tests.

private WeaponWriter writer;
@Shared
def emptyObject = Mock(Weapon)
@Shared
def weaponMocked = Mock(Weapon)

def: "should flatten object"(){
    given: 
        emptyObject.content() >> ""
        weaponMocked.content() >> "BF Sword"
        List<Weapon> weaponList = Arrays.asList(weapon1, weapon2)
        writer = new WeaponWriter(weaponList)
    when:
        def text = writer.writeWeapon()
    then:
        text == expectedText
    where:
        weapon1     | weapon2       | expectedText
        emptyObject | emptyObject   | "Headline"
        weaponMocked| emptyObject   | "Headline" + "BF Sword"
        weaponMocked| weaponMocked  | "Headline" + "BF Sword" + "BF Sword"      
}

As you can see, I want to test class with method which convert list of Weapon to one string. And I have three test cases, and only first one is working correctly.

WeaponWriter#writeWeapon is just iterating over the list of weapons and for each of them calling Weapon#content and then combines them into one String.

From what I see for some reason mocking of weaponMocked.content() >> "BF Sword" does not return BF Sword, anyone know why?


When I am not using paramtrized test everything works ok.


Solution

  • Okay, I think I have spotted it: As I said, you use @Shared mocks instead of creating new ones for each iteration of each feature method, probably because you want to use them from within the where: block. Then you are trying to stub methods for those mocks from within your test even though shared variables should be initialised either directly during declaration or from a setupSpec() method. What is even worse, you are trying to re-initialise the stub methods for each iteration, which definitely is a bad idea.

    Let us assume we have these classes under test (I am using Groovy, can also be similar Java classes):

    package de.scrum_master.stackoverflow.q64013999
    
    class Weapon {
      private String name
    
      Weapon(String name) {
        this.name = name
      }
    
      String content() {
        name
      }
    }
    
    package de.scrum_master.stackoverflow.q64013999
    
    class WeaponWriter {
      private List<Weapon> weaponList
    
      WeaponWriter(List<Weapon> weaponList) {
        this.weaponList = weaponList
      }
    
      String writeWeapon() {
        "Headline" + weaponList*.content().join("")
      }
    }
    

    So now you have at least two choices:

    1. Initialise the stubs directly during mock creation:

    package de.scrum_master.stackoverflow.q64013999
    
    import spock.lang.Shared
    import spock.lang.Specification
    import spock.lang.Unroll
    
    class WeaponWriterTest extends Specification {
      private WeaponWriter writer
      @Shared
      def emptyObject = Mock(Weapon) {
        content() >> ""
      }
      @Shared
      def weaponMocked = Mock(Weapon) {
        content() >> "BF Sword"
      }
    
      @Unroll
      def "flatten weapon list into '#expectedText'"() {
        given:
        writer = new WeaponWriter([weapon1, weapon2])
    
        expect:
        writer.writeWeapon() == expectedText
    
        where:
        weapon1      | weapon2      | expectedText
        emptyObject  | emptyObject  | "Headline"
        weaponMocked | emptyObject  | "Headline" + "BF Sword"
        weaponMocked | weaponMocked | "Headline" + "BF Sword" + "BF Sword"
      }
    }
    

    2. Initialise the stubs in setupSpec():

    package de.scrum_master.stackoverflow.q64013999
    
    import spock.lang.Shared
    import spock.lang.Specification
    import spock.lang.Unroll
    
    class WeaponWriterTest extends Specification {
      private WeaponWriter writer
      @Shared
      def emptyObject = Mock(Weapon)
      @Shared
      def weaponMocked = Mock(Weapon)
    
      def setupSpec() {
        emptyObject.content() >> ""
        weaponMocked.content() >> "BF Sword"
      }
    
      @Unroll
      def "flatten weapon list into '#expectedText'"() {
        given:
        writer = new WeaponWriter([weapon1, weapon2])
    
        expect:
        writer.writeWeapon() == expectedText
    
        where:
        weapon1      | weapon2      | expectedText
        emptyObject  | emptyObject  | "Headline"
        weaponMocked | emptyObject  | "Headline" + "BF Sword"
        weaponMocked | weaponMocked | "Headline" + "BF Sword" + "BF Sword"
      }
    }
    

    Please, please provide an MCVE by yourself next time. Thank you. This was your free shot for me doing your job, asking a proper question.