【问题标题】:How to replace a constructor injected object with mocked object in spock如何用spock中的模拟对象替换构造函数注入的对象
【发布时间】:2016-02-22 14:53:23
【问题描述】:

我知道这个问题很大,但我只是想弄清楚我所处的情况。 我正在开发一个使用来自消息代理的 JMS 消息的应用程序。 我们在消费者方面使用骆驼路线。路由构建器中所需的所有对象都是使用 spring 通过构造函数注入来注入的。 我想模拟实际处理的行为,一旦消费者从队列中接收到消息。所有类都通过 spring 配置加载。 以下是三个类: CustomRouteBuilder.java

public CustomRouteBuilder extends RouteBuilder{

private CustomRouteAdapter customAdapter;
  public CustomRouteBuilder (CustomRouteAdapter customAdapter){
    this.customAdapter = customAdapter
    }
  public void configure(RouteDefinition route){
   route.bean(customAdapter);
   }

}

CustomRouteAdapter.java

public class CustomRouteAdapter {
  private Orchestrator orchestrator;
  public CustomRouteAdapter (Orchestrator orchestrator){
  this.orchestrator = orchestrator;
   }

  @Handler
   public void process(String message){
   orchestrator.generate(message) ;
   }
}

Orchestrator.java

 public class Orchestrator{
    private Service service;
    public Orchestrator(Service service){
     this.service = service;
    }

   public void generateData(String message){
    service.process(message);
   }
}

根据我们的要求,我们必须加载此配置文件,然后使用 spock 编写功能测试。 下面是我的

CustomRouteBuilderTest.groovy 文件。

 import org.springframework.test.util.ReflectionTestUtils
 import spock.lang.Specification

 @ContextConfiguration(classes=[CustomRouteBuilderTest.Config.class])
 class CustomRouteBuilderTest extends Specification{
    private static final String message = "Hello";
Orchestrator orchestrator;
@Autowired
CustomRouteAdapter customRouteAdapter;

def setup(){
    orchestrator = Mock(Orchestrator)
    ReflectionTestUtils.setField(customRouteAdapter,"orchestrator",orchestrator)
    orchestrator.generate(message )
}

private String getMessageAsJson() {
    //return json string;

}


private String getMessage() {
    //  return message; 
}

private Map<String, Object> doMakeHeaders() {

    //Create message headers
}


private void doSendMessage(){
    Thread.sleep(5000)
    Map<String,Object> messageHeader = doMakeHeaders()
    byte [] message = getMessageAsJson().getBytes()

    CamelContext context = new DefaultCamelContext()
    ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(jmsBrokerUrl)
    context.addComponent("activeMQComponent",JmsComponent.jmsComponent(connectionFactory))
    ProducerTemplate template = context.createProducerTemplate()
    context.start();
    template.sendBodyAndHeaders("queueName", message, messageHeader)
}

def  "test message consumption"(){
    given:
    doSendMessage()
}

    @Configuration
    @Import([FunctionalTestCommonConfig.class,CustomRouteBuilderConfig.class])
    @PropertySource(value="classpath:test.properties")
   static class Config{
    }
}

这里的问题是即使我使用 ReflectionTestUtils 将模拟对象注入到适配器,我也无法正确定义其行为。 当收到消息时,编排器会尝试处理它。 我的要求是: 应该自动从骆驼路线调用适配器,但是 当从适配器调用 orechestrator.generate 时,什么都不应该发生,它应该简单地返回。 但这里没有发生这样的事情。 每次我发送消息时,消费者(RouteBuilder)都会收到它并调用处理函数,然后调用

 orchestrator.generate(message) 

函数,编排器开始处理并从服务级别抛出异常。 任何人都可以请帮助我。

【问题讨论】:

  • 你确定CustomRouteAdapter 没有被代理吗?或者orchestrator 字段不是最终的?执行测试时有 NPE 吗?
  • 之前是最终版本。我删除了 final 修饰符,然后再次测试了代码。还是一样的。根据此代码,我应该收到 NPE,但什么也没有。它调用 service.process(message) 并从那里得到自定义的 ServiceException。
  • 签入调试器,但您没有正确注入模拟,它应该使用您发布的代码抛出 NPE :-) 此外,在 groovy 中,您不需要 ReflectionTestUtils,如果该字段不是最终的,您可以写customRouteAdapter.orchestrator = ..
  • 当然让我试试这个。但我担心,customRouteAdapter.orchestrator = mockedOrchestrator 会起作用,因为我的协调器在实际代码中是私有的。
  • 现在我遇到了另一个问题,groovy.lang.MissingPropertyException: No such property: orchestrator for class: CustomRouteBuilder$$EnhancerBySpringCGLIB$$ad2783ae 即使我删除了私有访问说明符

标签: spring-boot apache-camel spock


【解决方案1】:

我想你的 bean 已经被 Spring 代理了,并且这个代理使用了 cglib(因为你看到了CustomRouteBuilder$$EnhancerBySpringCGLIB$$ad2783ae)。

如果真的是这样,你在测试中没有@Autowired CustomRouteAdapter 的真实实例,而是一个 cglib 代理:Spring 创建了一个新类,扩展了 realclass,并覆盖了这个类的所有方法.新方法委托给真实实例。

当您更改orchestrator 字段时,您实际上是在更改代理的orchestrator 字段,实际实例并未使用该字段。

有几种方法可以实现您想要做的事情:

  1. CustomRouteAdapter 中添加setOrchestrator 方法
  2. 在您的 spring 配置中创建模拟,并让 spring 注入这个模拟而不是 Orchestrator 的真实实例
  3. 在真实实例中注入orchestrator(丑陋 - 我不建议你这样做,它对代码的可测试性没有帮助!)

    customRouteAdapter.targetSource.target.orchestrator = _themock_
    

【讨论】:

  • 我尝试了在 customRouteAdapter 中添加 setOrchestrator 的第一个选项,并且成功了。谢谢,非常感谢,我也会尝试其他选项。
猜你喜欢
  • 2016-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-17
  • 1970-01-01
  • 2021-09-17
  • 1970-01-01
相关资源
最近更新 更多