Spring JMS监听器测试无限循环问题求助(TestContainers环境)
Spring JmsListener测试陷入无限循环问题排查与解决
我尝试为Spring JmsListener编写测试,但测试陷入无限循环。怀疑和Context Caching有关,但不确定。当前使用TestContainers环境,测试代码如下:
@Autowired private lateinit var jmsTemplate: JmsTemplate @Autowired private lateinit var jackson: ObjectMapper @Autowired private lateinit var jdbcTemplate: JdbcTemplate @Test fun testJmsListner() { val message = "message" val queue = "queue" jmsTemplate.convertAndSend(queue, message) val sentMessage: Message? = jmsTemplate.receive(queue) if (sentMessage != null) { assertTrue(sentMessage::class.java.isInstance(TextMessage::class.java)) } assertEquals(message, (sentMessage as TextMessage).text) }
日志显示消息接收操作在另一个线程执行,具体日志如下:
2024-05-06T16:22:58.646+03:00 INFO 10455 --- [tasks] [ Test worker] .j.a.l.p.testJmsListner : Started testJmsListner in 1.746 seconds (process running for 6.678)
2024-05-06T16:22:58.909+03:00 INFO 10455 --- [tasks] [ntContainer#0-1] 收到了我的消息相关日志
问题核心原因
- 测试逻辑冲突:你调用
jmsTemplate.convertAndSend发送消息后,又用jmsTemplate.receive尝试消费这条消息,但实际上你的@JmsListener监听容器已经在另一个线程(日志中的ntContainer#0-1线程)抢先消费了消息,导致receive方法一直阻塞等待,最终陷入无限循环。 - 类型判断写法错误:
sentMessage::class.java.isInstance(TextMessage::class.java)是错误的判断方式,Kotlin中应该用sentMessage is TextMessage来做类型校验。
修复方案
方案1:验证Listener的业务处理结果(推荐)
不要直接消费队列消息,而是通过验证Listener处理后的业务输出(比如数据库数据变化、状态变更等)来完成测试。假设你的Listener会将消息内容写入数据库,修改测试代码如下:
@Autowired private lateinit var jmsTemplate: JmsTemplate @Autowired private lateinit var jdbcTemplate: JdbcTemplate @Test fun testJmsListener() { val message = "message" val queue = "queue" // 发送测试消息 jmsTemplate.convertAndSend(queue, message) // 轮询数据库,验证Listener是否处理完成(避免固定等待时间的不确定性) var messageProcessed = false repeat(10) { val count = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM your_business_table WHERE content = ?", Int::class.java, message ) if (count != null && count > 0) { messageProcessed = true return@repeat } Thread.sleep(500) // 每次轮询间隔500ms } // 断言消息已被处理 assertTrue(messageProcessed, "消息未被Listener成功处理") }
方案2:用CountDownLatch同步线程处理
如果需要更精准的线程同步,可以通过CountDownLatch监听Listener的处理完成事件:
// 测试类中定义CountDownLatch,计数为1表示等待一次消息处理 private val processingLatch = CountDownLatch(1) // 测试环境下的Listener(或修改原有Listener添加 latch 触发逻辑) @JmsListener(destination = "queue") fun handleMessage(message: String) { // 原有业务逻辑处理 // ... // 处理完成后触发 latch processingLatch.countDown() } @Test fun testJmsListener() { val message = "message" val queue = "queue" jmsTemplate.convertAndSend(queue, message) // 等待最多5秒,直到Listener处理完成 val isProcessed = processingLatch.await(5, TimeUnit.SECONDS) assertTrue(isProcessed, "Listener未在指定时间内处理消息") // 可选:添加业务结果验证 }
方案3:禁用Context Caching(针对缓存怀疑)
如果确认是Spring Test的上下文缓存导致的问题,可以在测试类上添加@DirtiesContext注解,强制每次测试重新加载上下文:
@SpringBootTest @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class JmsListenerTest { // ... 测试代码 }
内容的提问来源于stack exchange,提问作者Mankasss
相关产品推荐
相关产品推荐

