Spock 测试:调用过多

Spock testing: Too many invocation

我编写了用于将事件从一个队列手动重新排队到另一个队列的服务。

public class ReQueueService {
  private final RabbitTemplate rabbitTemplate;

  public void retry() {
    InfoLog infoLog;
    while (rabbitTemplate != null && 
      (infoLog = (InfoLog) rabbitTemplate.receiveAndConvert(EVENT_WAITING_FOR_REQUEUE)) != null
    ) {
      rabbitTemplate.convertAndSend(SOME_QUEUE, infoLog.getSomeEvent());
    }
  }
}

我面临的问题是:

Too many invocations for:

1 * rabbitTemplate.convertAndSend(SOME_QUEUE, _ as SomeEvent) >> {
      arguments ->
        assert infoLog.getSomeEvent() == arguments[1]
    }   (2 invocations)

Matching invocations (ordered by last occurrence):

2 * rabbitTemplate.convertAndSend(SOME_QUEUE, ...

而我的测试代码如下所示:

class ReQueueServiceTest extends Specification {
  def "should resend single event to some queue" () {
    given:
    InfoLog infoLog = Fixtures.createInfoLog()
    def rabbitTemplate = Mock(RabbitTemplate){
      receiveAndConvert(EVENT_WAITING_FOR_REQUEUE) >> { infoLog }
    }
    ReQueueService reSyncService = new ReQueueService(rabbitTemplate)

    when:
    reSyncService.retry()

    then:
    1 * rabbitTemplate.convertAndSend(SOME_QUEUE, _ as SomeEvent) >> {
      arguments ->
        assert infoLog.getSomeEvent() == arguments[1]
    }
  }
}

问题是,如果我只存根一个事件,为什么我有 2 个调用?

编辑:

link 回购示例:https://gitlab.com/bartekwichowski/spock-too-many

感谢回购 link。一旦我可以 运行 测试并实时检查行为,就很容易找出问题所在。首先,我将对您实际想要测试的内容进行有根据的猜测:

  1. mock 的 receiveAndConvert 方法在首次调用时应 return str 再次调用时应 null
  2. 随后您想要验证 while 循环 运行 恰好是 1 次迭代,即 convertAndSend 是使用您期望的参数调用的。

这可以通过

实现
  1. receiveAndConvert("FOO") >>> [str, null]
  2. 1 * rabbitTemplate.convertAndSend("BAR", str)(在存根方法中不需要丑陋的断言,参数已经根据您的参数约束进行了验证)

如果我稍微重构您的规范以获得更漂亮的变量名和更少的冗长,它看起来像这样:

class ReSyncServiceTest extends Specification {
  def "should resend single event to resource sync queue"() {
    given:
    def message = "someValue"
    def rabbitTemplate = Mock(RabbitTemplate) {
      receiveAndConvert("FOO") >>> [message, null]
    }

    when:
    new ReSyncService(rabbitTemplate).retry()

    then:
    1 * rabbitTemplate.convertAndSend("BAR", message)
  }
}

P.S.: 你的带有断言的版本没有明确地 return 任何东西,但隐含地是最后一个断言的结果。小心点。使用 >> { ... } 你是在存根方法结果!在 Git 中的版本中,它总是 return true 并且测试仅终止,因为您添加了 1 * 限制。如果它不存在,你就会有一个无限循环。您的代码没有按照您的想法进行操作。也许 Spock 手册可以帮助您。 :-)


P.P.S.: 也许您想重构您的应用程序代码,使其更易于理解和维护,并减少一些 "smart"。也没有必要在每次迭代中检查 rabbitTemplate != null ,一次就足够了。这个怎么样?

@Slf4j
@Service
@AllArgsConstructor
public class ReSyncService {
  private final RabbitTemplate rabbitTemplate;

  public void retry() {
    if (rabbitTemplate == null)
      return;
    String event;
    while (null != (event = getEventFromQueue()))
      rabbitTemplate.convertAndSend("BAR", event);
  }

  protected String getEventFromQueue() {
    return (String) rabbitTemplate.receiveAndConvert("FOO");
  }
}