简体   繁体   English

运行多个 Spring Boot 测试时,@MockBean 在 JMS 侦听器中使用不同的实例

[英]@MockBean uses different instance in JMS listener when running multiple Spring Boot tests

I've written a Spring Boot Test, that writes into a JMS queue and is expecting some processing via an JMS listener.我写了一个 Spring Boot 测试,它写入一个 JMS 队列,并期待通过 JMS 侦听器进行一些处理。 In the listener, I'm trying to read an object from S3.在侦听器中,我试图从 S3 读取一个对象。 The AmazonS3 class should be replaced by a MockBean. AmazonS3类应替换为 MockBean。 In my test I set up the mock like this:在我的测试中,我设置了这样的模拟:

@SpringBootTest
public class MyTest {

    @Autowired
    MyJmsPublisher jmsPlublisher;
    @MockBean
    AmazonS3 amazonS3;

    @Test
    public void test() {
        final S3Object s3Object = mock(S3Object.class);
        when(s3Object.getObjectContent()).thenReturn(mock(S3ObjectInputStream.class));
        when(amazonS3.getObject(anyString(), anyString())).thenReturn(s3Object);
        
        jmsPlublisher.publishMessage("mymessage");
        Awaitility.await().untilAsserted(() -> {
            //wait for something here
        });
    }
}


@Component
@RequiredArgsConstructor
public class MyJmsPublisher {

    private final JmsTemplate jmsTemplate;

    public void publishMessage(String message) {
        jmsTemplate.convertAndSend("destination", message);
    }
}

@Component
@RequiredArgsConstructor
public class MyJmsListener {

    private final AmazonS3 amazonS3;

    @JmsListener(destination = "destination")
    public void onMessageReceived(String message) {
        final S3ObjectInputStream objectContent = amazonS3.getObject("a", "b").getObjectContent();
        // some logic here
    }
}

But the issue is that when running multiple Spring Boot tests , the MyJmsListener class contains a mock that is different from the one created in the Test.但问题是,当运行多个 Spring Boot 测试时MyJmsListener类包含一个与测试中创建的不同的模拟。 It's a mock, but for example the getObjectContent() returns null.这是一个模拟,但例如getObjectContent()返回 null。 But when I run the test alone, everything works fine.但是当我单独运行测试时,一切正常。

I've tried to inject the AmazonS3 bean into the MyJmsPublisher and call the mocked method there and it worked.我尝试将AmazonS3 bean 注入MyJmsPublisher并在那里调用MyJmsPublisher方法并且它起作用了。 So I suspect, that it's because the JMS listener operates in a different thread.所以我怀疑,这是因为 JMS 侦听器在不同的线程中运行。

I've found this thread and also set the reset to all available options, but does not make any difference.我找到了这个线程并将reset为所有可用选项,但没有任何区别。 I also tried this OP's approach that worked for them, where I create a mock via the @Bean annotation like this:我还尝试了这个 OP 对他们@Bean的方法,在那里我通过@Bean注释创建了一个模拟,如下所示:

@Configuration
public class MyConfig {
   @Bean
   @Primary
   public AmazonS3 amazonS3() {
      return Mockito.mock(AmazonS3.class);
   }
}

But this just has the same behavior as mentioned above.但这只是具有与上述相同的行为。

So can you actually use the @MockBean annotation when using different Threads like using a @JMSListener ?那么在使用不同的线程(例如使用@JMSListener时,您实际上可以使用@MockBean注释吗? Or am I missing something?或者我错过了什么?

Spring Beans with methods annotated with @JmsListener are injecting beans leaked from previous test executions when activated by a secondary thread.带有@JmsListener注释的方法的 Spring Bean 正在注入由辅助线程激活时从先前测试执行中泄漏的 bean。 A practical workaround is to configure the test executor to use an isolated VM for each class to avoid this issue.一个实用的解决方法是将测试执行程序配置为对每个类使用一个隔离的 VM 以避免此问题。

For Maven executions you can configure the maven-failsafe-plugin or maven-surefire-plugin by setting the reuseForks option.对于Maven 执行,您可以通过设置reuseForks选项来配置maven-failsafe-pluginmaven-surefire-plugin reuseForks maven-surefire-plugin eg:例如:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <configuration>
        <reuseForks>false</reuseForks>
    </configuration>
</plugin>

You can also easily change the Fork mode to Class in JUnit IDE tools for multiple tests execution.您还可以在 JUnit IDE 工具中轻松地将Fork mode更改为Class以执行多个测试。 Example for IntelliJ: IntelliJ 示例:

IntelliJ JUnit Fork 模式

Using @DirtiesContext does not work, and unfortunately, I still couldn't find the root cause for this - My hunch is that it could be something related to using an in-memory instance of the ActiveMQ broker, which lives in the VM instance shared by the executions.使用@DirtiesContext不起作用,不幸的是,我仍然找不到根本原因 - 我的预感是它可能与使用 ActiveMQ 代理的内存实例有关,该实例位于共享的 VM 实例中通过处决。

We had a similar issue when using the @JmsListener annotation in combination with @MockBean / @SpyBean .@JmsListener注释与@MockBean / @SpyBean结合使用时,我们遇到了类似的问题。 In our case using a separate destination for each test class solved this problem:在我们的例子中,为每个测试类使用单独的目的地解决了这个问题:

@JmsListener(destination = "${my.mq.topic.name}")
void consume(TextMessage message){
...
}

@SpringBootTest
@TestPropertySource(properties = "my.mq.topic.name=UniqueTopicName"})
class MyConsumerIT{
...
}

As far as I understand Spring has to create a separate JMS Consumer for each topic/queue.据我了解,Spring 必须为每个主题/队列创建一个单独的 JMS 消费者。 This configuration forces Spring to create a separate JMS Consumer for this class and Inject it correctly into the TestContext.此配置强制 Spring 为此类创建单独的 JMS 使用者并将其正确注入到 TestContext 中。 In comparison without this configuration, Spring reuses the once created Consumer for all test classes.与没有此配置的情况相比,Spring 为所有测试类重用了曾经创建的 Consumer。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM