Question

I'm being hit with the issue that spock doesn't allow Mocks to be created outside of the specification - How to create Spock mocks outside of a specification class?

This seems to be still outstanding so am asking is that giving that i've got a complex and nested DI graph what is the most efficient way to 'inject' a mock representation deep in the graph?

Ideally, I have one bean definition set for normal deployment and another when running unit tests and it is this definition set being the applicable Mocks

e.g.

@Configuration
@Profile("deployment")
public class MyBeansForDeployment {

   @Bean
   public MyInterface myBean() {
       return new MyConcreateImplmentation();
   } 

}

&&

@Configuration
@Profile("test")
public class MyBeansForUnitTests {

   @Bean
   public MyInterface myBean() {
       return new MyMockImplementation();
   } 

}
Was it helpful?

Solution

Since Spock 1.1, you can create mocks outside of a specification class (detached mocks). One of the options is DetachedMockFactory. Take a look at the documentation or my answer to the question you linked.

OTHER TIPS

You could try to implement a BeanPostProcessor that will replace the beans that you want with test doubles, such as shown below:

public class TestDoubleInjector implements BeanPostProcessor {
...

private static Map<String, Object[]> testDoubleBeanReplacements = new HashMap<>();

public void replaceBeanWithTestDouble(String beanName, Object testDouble, Class testDoubleType) {
    testDoubleBeanReplacements.put(beanName, new Object[]{testDouble, testDoubleType});
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (testDoubleBeanReplacements.containsKey(beanName)) {
        return testDoubleBeanReplacements.get(beanName)[TEST_DOUBLE_OBJ];
    }

    return bean;
}

In your test, setup your mocks like shown below before initializing the application context. Make sure to include the TestDoubleInjector as a bean in your test context.

TestDoubleInjector testDoubleInjector = new TestDoubleInjector()
testDoubleInjector.replaceBeanWithTestDouble('beanToReplace', mock(MyBean.class), MyBean.class)

It could be done using HotSwappableTargetSource

@WebAppConfiguration
@SpringApplicationConfiguration(TestApp)
@IntegrationTest('server.port:0')

class HelloSpec extends Specification {

@Autowired
@Qualifier('swappableHelloService')
HotSwappableTargetSource swappableHelloService

def "test mocked"() {
  given: 'hello service is mocked'
  def mockedHelloService = Mock(HelloService)
  and:
  swappableHelloService.swap(mockedHelloService)

  when:
  //hit endpoint
  then:
  //asserts 
  and: 'check interactions'
  interaction {
      1 * mockedHelloService.hello(postfix) >> { ""Mocked, $postfix"" as String }
  }
  where:
  postfix | _
  randomAlphabetic(10) | _
}
}

And this is TestApp (override the bean you want to mock with proxy)

class TestApp extends App {

//override hello service bean
@Bean(name = HelloService.HELLO_SERVICE_BEAN_NAME)
public ProxyFactoryBean helloService(@Qualifier("swappableHelloService") HotSwappableTargetSource targetSource) {
def proxyFactoryBean = new ProxyFactoryBean()
proxyFactoryBean.setTargetSource(targetSource)
proxyFactoryBean
}

@Bean
public HotSwappableTargetSource swappableHelloService() {
  new HotSwappableTargetSource(new HelloService());
}
}

Have a look at this example https://github.com/sf-git/spock-spring

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top