Search code examples
spock

How to delegate Spock Mock behavior to a real object with argument


I have a class Table with a method getDataFromTable which takes one argument. Under the hood, Table::getDataFromTable just delegates to TableConfig::getTableData which further delegates to ConfigDO::getTableMap. How can I have my Spock Mock of Tabledelegate this method call with its argument to the real instance ofConfigDO`? I.e. I want

getDataFromTable(argument) >> configDo.getTableDataMap().get(argument)

Classes involved:

package com.place.core;

import org.springframework.stereotyp.Component;
import com.place.configuration.TableConfig;

@Component
public class Table {
    private final TableConfig tableConfig;
    
    public Table(TableConfig config) {
        this.tableConfig = config;
    }

    public String getDataFromTable(String tableName) {
        return tableConfig.getTableData(tableName);
    }
}
package com.place.configuration;

import com.place.representation.ConfigDO;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TableConfig {
    private final ConfigDO configDo;

    public TableConfig(ConfigDO config) {
        this.configDo = config;
    }

    public String getTableData(String tableName) {
        return configDo.getTableMap().get(tableName);
    }
}
package com.place.representation;

import lombok.Data;
import java.util.Map;

@Data
public class ConfigDO {
    private final Map<String, String> tableDataMap;
}

Minimal spec:

class TableTest extends Specification {
    def configDo = new ConfigDO()

    def mockTableConfig = Mock(TableConfig) {
        // What I feel like I'd like to do: take the argument from TableConfig::getTableData and pass it to the Map::get call
        getTableData(argument) >> configDo.getTableDataMap().get(argument)
    }
}

Solution

  • I recreated simplified Groovy versions of your Java classes without any references to Spring or Lombok, because the latter are irrelevant for your question.

    • The first problem is your method stubbing syntax for the mock in general.
    • The second is that you need to learn how to access mock method arguments when stubbing.
    • The third is that you tried to stub method getDataFromTable, which does not exist in TableConfig. You cannot stub a Table method in TableConfig. Please be more careful to actually stub methods which really exist in the class to be mocked/stubbed.
    package de.scrum_master.stackoverflow.q76038114
    
    import spock.lang.Specification
    
    class TableTest extends Specification {
      def configDo = new ConfigDO()
      def mockTableConfig = Mock(TableConfig) {
        getTableData(_) >> { args -> configDo.tableDataMap[args[0]] }
      }
    
      def test() {
        given:
        def table = new Table(mockTableConfig)
    
        expect:
        table.getDataFromTable('foo') == 'Hello world!'
        table.getDataFromTable('bar') == 'These are just test data.'
      }
    }
    
    package de.scrum_master.stackoverflow.q76038114
    
    class Table {
      private final TableConfig tableConfig
    
      Table(TableConfig config) {
        this.tableConfig = config
      }
    
      String getDataFromTable(String tableName) {
        return tableConfig.getTableData(tableName)
      }
    }
    
    package de.scrum_master.stackoverflow.q76038114
    
    class TableConfig {
      private final ConfigDO configDo
    
      TableConfig(ConfigDO config) {
        this.configDo = config
      }
    
      String getTableData(String tableName) {
        return configDo.getTableMap().get(tableName)
      }
    }
    
    package de.scrum_master.stackoverflow.q76038114
    
    class ConfigDO {
      private final Map<String, String> tableDataMap = [
        foo: 'Hello world!',
        bar: 'These are just test data.'
      ]
    
      Map<String, String> getTableDataMap() {
        return tableDataMap
      }
    }
    

    Try it in the Groovy Web Console.