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:
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.