如何让基于JBoss配置的IBM MQ消息发送Bean通过Testcontainers进行测试
如何让基于JBoss配置的IBM MQ消息发送Bean通过Testcontainers进行测试
嗨,我来帮你解决这个问题~首先得明确:你邮件服务的测试方式之所以能生效,是因为JavaMail的Session默认会读取系统属性来构建连接,但你的MQ Bean是通过@Resource(lookup)从JBoss的JNDI上下文获取连接工厂和目的地的,这俩机制完全不一样,所以直接套邮件的方法行不通。不过有几种靠谱的方案可以实现,我给你一一梳理:
方案一:快速临时方案——用反射手动注入测试依赖
如果你只是想快速跑通测试,不想改动太多业务代码,可以用反射把Testcontainers里的MQ连接信息手动设置到MessageSenderImpl的字段里。毕竟你在测试里是直接new的Bean,容器不会帮你处理@Resource注入,得自己来:
首先要确保测试依赖里加了IBM MQ的客户端包,然后修改测试代码:
@Testcontainers class MQTests { private static MessageSenderImpl messageSender = new MessageSenderImpl(); @Container static GenericContainer<?> mqContainer = new GenericContainer<>(DockerImageName.parse("ibmcom/mq")) .withEnv("LICENSE", "accept") .withEnv("MQ_QMGR_NAME", "test") .withEnv("MQ_USER_NAME", "admin") .withEnv("MQ_PASSWORD", "passw0rd") // IBM MQ默认密码,可按需修改 .withEnv("JMS_QUEUES", "To,From") .withEnv("MQ_CHANNELS", "DEV.ADMIN.SVRCONN") // 默认管理通道 .withExposedPorts(1414); @Test void test1() throws Exception { assertTrue(mqContainer.isRunning()); // 1. 构建测试用的MQ连接工厂 MQConnectionFactory testCf = new MQConnectionFactory(); testCf.setHostName(mqContainer.getHost()); testCf.setPort(mqContainer.getMappedPort(1414)); testCf.setQueueManager("test"); testCf.setChannel("DEV.ADMIN.SVRCONN"); testCf.setUserName("admin"); testCf.setPassword("passw0rd"); // 2. 构建测试用的队列目的地 Destination testDest = testCf.createQueue("To"); // 3. 用反射把测试依赖注入到Bean里 Field cfField = MessageSenderImpl.class.getDeclaredField("connectionFactory"); cfField.setAccessible(true); cfField.set(messageSender, testCf); Field destField = MessageSenderImpl.class.getDeclaredField("destination"); destField.setAccessible(true); destField.set(messageSender, testDest); // 4. 执行测试 messageSender.send(List.of(new OutboundMessage("test"))); // 可选:验证消息确实发送成功,比如从队列里接收消息 Connection conn = testCf.createConnection(); Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createConsumer(testDest); conn.start(); TextMessage receivedMsg = (TextMessage) consumer.receive(5000); assertNotNull(receivedMsg); assertEquals("test", receivedMsg.getText()); } }
方案二:规范Java EE方案——用CDI测试框架替换JNDI注入
如果你的项目是Jakarta EE/Java EE技术栈,可以用Weld的JUnit扩展来模拟CDI环境,通过自定义@Produces方法来替代JBoss的JNDI资源,让测试时自动注入Testcontainers的MQ依赖:
首先添加Weld JUnit5的测试依赖,然后编写测试:
@Testcontainers @ExtendWith(WeldJunit5Extension.class) class MQTests { @Container static GenericContainer<?> mqContainer = new GenericContainer<>(DockerImageName.parse("ibmcom/mq")) .withEnv("LICENSE", "accept") .withEnv("MQ_QMGR_NAME", "test") .withEnv("MQ_USER_NAME", "admin") .withEnv("MQ_PASSWORD", "passw0rd") .withEnv("JMS_QUEUES", "To,From") .withExposedPorts(1414); // 自定义Producer,替代原有的JNDI lookup,提供测试用的连接工厂 @Produces @Resource(lookup = "java:jboss/ConnectionFactory") public ConnectionFactory produceTestConnectionFactory() throws JMSException { MQConnectionFactory cf = new MQConnectionFactory(); cf.setHostName(mqContainer.getHost()); cf.setPort(mqContainer.getMappedPort(1414)); cf.setQueueManager("test"); cf.setChannel("DEV.ADMIN.SVRCONN"); cf.setUserName("admin"); cf.setPassword("passw0rd"); return cf; } // 自定义Producer,提供测试用的队列目的地 @Produces @Resource(lookup = "java:jboss/to") public Destination produceTestDestination() throws JMSException { return produceTestConnectionFactory().createQueue("To"); } // 让Weld自动注入MessageSenderImpl,会用上面的Producer替换原有的@Resource依赖 @Inject private MessageSenderImpl messageSender; @Test void test1() throws Exception { assertTrue(mqContainer.isRunning()); messageSender.send(List.of(new OutboundMessage("test"))); // 验证消息发送成功 Connection conn = produceTestConnectionFactory().createConnection(); Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createConsumer(produceTestDestination()); conn.start(); TextMessage receivedMsg = (TextMessage) consumer.receive(5000); assertNotNull(receivedMsg); assertEquals("test", receivedMsg.getText()); } }
方案三:长期最优方案——重构代码实现依赖反转
如果允许改动业务代码,最推荐的方式是把MessageSenderImpl的依赖改成构造函数注入,摆脱对JNDI的强依赖,这样测试时可以直接传入Testcontainers的MQ资源,完全不需要反射或容器框架:
首先重构业务Bean:
@Stateless public class MessageSenderImpl { private final ConnectionFactory connectionFactory; private final Destination destination; // 构造函数注入,生产环境下容器会自动从JNDI查找对应资源注入 @Inject public MessageSenderImpl( @Resource(lookup = "java:jboss/ConnectionFactory") ConnectionFactory connectionFactory, @Resource(lookup = "java:jboss/to") Destination destination) { this.connectionFactory = connectionFactory; this.destination = destination; } // 无参构造函数,供容器实例化使用(部分Java EE容器要求) public MessageSenderImpl() { this(null, null); } // 原有的send方法保持不变 public void send(List<OutboundMessage> omms) { // ... 你的业务代码 } }
然后测试代码就变得非常简洁:
@Testcontainers class MQTests { @Container static GenericContainer<?> mqContainer = new GenericContainer<>(DockerImageName.parse("ibmcom/mq")) .withEnv("LICENSE", "accept") .withEnv("MQ_QMGR_NAME", "test") .withEnv("MQ_USER_NAME", "admin") .withEnv("MQ_PASSWORD", "passw0rd") .withEnv("JMS_QUEUES", "To,From") .withExposedPorts(1414); private MessageSenderImpl messageSender; @BeforeEach void setUp() throws JMSException { // 构建测试用的MQ资源 MQConnectionFactory testCf = new MQConnectionFactory(); testCf.setHostName(mqContainer.getHost()); testCf.setPort(mqContainer.getMappedPort(1414)); testCf.setQueueManager("test"); testCf.setChannel("DEV.ADMIN.SVRCONN"); testCf.setUserName("admin"); testCf.setPassword("passw0rd"); Destination testDest = testCf.createQueue("To"); // 直接传入依赖构造Bean messageSender = new MessageSenderImpl(testCf, testDest); } @Test void test1() throws Exception { assertTrue(mqContainer.isRunning()); messageSender.send(List.of(new OutboundMessage("test"))); // 验证逻辑 Connection conn = ((MQConnectionFactory) messageSender.connectionFactory).createConnection(); // ... 接收消息验证 } }
总结
- 邮件服务的测试方式不适用MQ,因为两者的配置机制完全不同:JavaMail靠系统属性,MQ靠JNDI lookup
- 快速测试用反射,规范Java EE项目用CDI测试框架,长期维护优先重构为构造函数注入
备注:内容来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

