You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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] 收到了我的消息相关日志


问题核心原因

  1. 测试逻辑冲突:你调用jmsTemplate.convertAndSend发送消息后,又用jmsTemplate.receive尝试消费这条消息,但实际上你的@JmsListener监听容器已经在另一个线程(日志中的ntContainer#0-1线程)抢先消费了消息,导致receive方法一直阻塞等待,最终陷入无限循环。
  2. 类型判断写法错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 10:05:16