简体   繁体   中英

@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. In the listener, I'm trying to read an object from S3. The AmazonS3 class should be replaced by a 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. It's a mock, but for example the getObjectContent() returns 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. So I suspect, that it's because the JMS listener operates in a different thread.

I've found this thread and also set the reset to all available options, but does not make any difference. I also tried this OP's approach that worked for them, where I create a mock via the @Bean annotation like this:

@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 ? 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. A practical workaround is to configure the test executor to use an isolated VM for each class to avoid this issue.

For Maven executions you can configure the maven-failsafe-plugin or maven-surefire-plugin by setting the reuseForks option. 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. Example for 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.

We had a similar issue when using the @JmsListener annotation in combination with @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. This configuration forces Spring to create a separate JMS Consumer for this class and Inject it correctly into the TestContext. In comparison without this configuration, Spring reuses the once created Consumer for all test classes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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