[spring-projects/spring-boot]@MockBean 方法不能在测试中存根

2024-04-11 848 views
9
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(profiles = "test")
public class AppTest {

  @MockBean
  private A a;

  @Test
  public void test() throws Exception {
      doReturn(3).when(a).getNumber(any());
  }
}

所以我正在测试上面的类,看起来 MockBean 本身的创建没有任何问题。但测试内的存根未正确设置。如果我使用@Testconfiguration方法@Bean,它可以工作,但我确实想在整个测试中动态地存根这些行为,就像使用普通的mockito一样。 Mockbean 的行为是固定的并且不能被删除吗?

回答

9

您没有提供足够的信息来确定,但我会做出有根据的猜测,您正在尝试配置应用程序上下文刷新期间依赖的行为。如果是这种情况,在测试方法中配置它就太晚了,因为那时上下文已经创建了。@Bean在这种情况下,建议使用方法来创建和配置模拟。

假设我的猜测是准确的,我们可以利用这个问题来更新文档。

1

@wilkinsona在这种情况下,我如何动态存根方法?甚至在每次测试中?因此,对于每次测试,当我希望我的模拟 bean 表现不同时,我该如何实现呢?

4

如果不了解您想要做什么,我无法回答这个问题。如果您希望我花更多时间来帮助您,请花一些时间描述您正在模拟的组件以及它们的使用方式和时间。

2
@Service
public class UpdateEvents {

  private final QueueConnectionManager queueConnectionManager;
  private final UpdateEventsService updateEventsService;

  private final AtomicBoolean isAlive = new AtomicBoolean(true);

  @Autowired
  public UpdateEventsListener (
      String queueName,
      UpdateEventsService updateEventsService,
      QueueConnectionManager queueConnectionManager
  ) {
    this.updateEventsService = updateEventsService;
    this.queueConnectionManager = queueConnectionManager;
    consumeEvents();
  }

  @PostConstruct
  @SneakyThrows
  private void init() {
    queueConnectionManager.start();
  }

  @PreDestroy
  @SneakyThrows
  private void stop() {
    isAlive.set(false);
    queueConnectionManager.stop();
  }

  public void consumeEvents() {
    while (isAlive.get())

          // the method i want to stub
          Message receivedMessage = queueConnectionManager.receive();

          updateEventsService.refresh(((TextMessage) receivedMessage).getText()))
          receivedMessage.acknowledge();
        }
    }
  }
}

这是我想要测试的简化代码。QueueConnectionManagerMockBean在测试中,我想存根该receive行为。事实证明,在测试 @wilkinsona 中存根后,它一直返回 null。我也把consumeEvents它放进去@PostConstruct但没有运气

6

谢谢你,@fsw0422。鉴于它UpdateEventsListener只有两个依赖项,并且您想要模拟至少其中之一,我会对其进行单元测试,而不是对其进行集成测试。

除了模拟问题之外,我还会避免调用从构造函数中执行任何操作的方法。就目前情况而言,在我看来,调用构造函数的线程将循环进入,consumeEvents()直到stop()被调用为止。这将在应用程序上下文刷新期间阻塞主线程,并阻止您的应用程序完全启动。

我打开了https://github.com/spring-projects/spring-boot/issues/16333来改进文档。我现在要关闭这个问题,因为 Spring Boot 中没有什么可做的了。如果您需要有关使用 Spring Boot 进行测试和模拟的更多指导,请关注 Stack Overflow 或 Gitter。正如贡献指南中提到的,我们更喜欢仅使用 GitHub 问题来解决错误和增强功能。

4

@wilkinsona 谢谢!

1

@wilkinsona 在摆弄了一些其他框架之后,拥有这个功能仍然很好。我一直在使用 Play Framework,它提供 Guice 作为 DI 容器框架,并且它在运行时完美地存根。存根很好,因为您不必为不同的行为创建多个测试 bean 或在运行时交换 bean

4

@fsw0422您可以使用 Spring Boot 模拟在应用程序上下文刷新期间使用的事物,您只需远离@MockBean这样做即可。相反,您需要提供一个@Configuation供测试使用的类。该类将为@Bean模拟提供一个方法,并根据您在应用程序上下文中需要的任何期望进行预先配置。

5

@wilkinsona您可以在同一测试中的同一运行时更改行为吗?

我想要的是类似的东西

public void test() { 
  when(mockBean.i_do_this()).thenReturn(**this**)

  some code()
  some code()

  when(mockBean.i_do_this()).thenReturn(**that**)
}

当然是在春天的背景下。

5

是的,你可以这么做。您需要将@Autowired模拟放入您的测试类中,然后您可以根据需要对其设置期望。这是标准的 Mockito 功能,与 Spring 没有任何关系。

3

谢谢,我没有想到自动装配模拟。我会尝试这个

5

我有同样的问题,但有一个限制,即使用 post 构造的 bean 是属性的条件 bean,并且出于某种原因,如果我使用@SpringBootTest或设置属性@TestPropertySource(properties = "shouldInitConditionalBean=true")

我得到这个 bean,它取决于我的模拟卡在应用程序上下文中。如果属性不同,它不应该创建一个新的上下文吗?

3

是的,应该的。请打开一个新的问题,附加或链接到重现该行为的最小示例,我们将进行查看。