1. 概述
在开发中,如果应用依赖 JNDI 获取数据源,单元测试时我们通常不希望连接真实数据库。✅
取而代之的是,使用一个 mock 的数据源,让测试更轻量、隔离性更强,避免环境依赖导致测试不稳定。
本文将展示如何在 Spring 环境下,通过 Spring 自带工具 和第三方库 Simple-JNDI 两种方式,测试一个 mock 的 JNDI 数据源。
⚠️ 注意:本文只关注单元测试。关于如何在 Spring 应用中使用 JNDI + JPA 构建正式项目,可参考我们另一篇文章:Spring 集成 JPA 与 JNDI 数据源。
2. JNDI 快速回顾
简单来说,JNDI(Java Naming and Directory Interface)的作用是:
将逻辑名称绑定到外部资源(如数据库连接、消息队列等),让应用通过名称查找资源,而无需关心具体实现。
核心概念:
- 所有操作都基于一个 上下文(Context)
- 起始点是
InitialContext
,它是整个 JNDI 查找的入口 - 通过
bind("name", object)
绑定资源 - 通过
lookup("name")
查找资源
❌ 没有 InitialContext
,JNDI 就无法工作。
举个例子,应用代码可能这样获取数据源:
DataSource ds = (DataSource) new InitialContext().lookup("java:comp/env/jdbc/datasource");
我们的目标就是在测试中,让这个 lookup
调用能返回一个 mock 的 DataSource
,而不是去连真实数据库。
3. 使用 Spring 的 SimpleNamingContextBuilder 测试 JNDI 数据源
Spring 提供了一个非常方便的工具类:SimpleNamingContextBuilder
,可以快速搭建一个内存中的 JNDI 环境,专用于测试。
✅ 优点:无需额外依赖,Spring 自带
⚠️ 注意:该类自 Spring 5.2 起已标记为废弃,推荐使用 Simple-JNDI 替代
步骤一:初始化 JNDI 上下文
我们在每个测试前构建一个空的命名上下文:
@BeforeEach
public void init() throws Exception {
SimpleNamingContextBuilder.emptyActivatedContextBuilder();
this.initContext = new InitialContext();
}
emptyActivatedContextBuilder()
会创建或复用一个全局的上下文构建器,比直接 new 更灵活。
步骤二:绑定并查找数据源
接下来写测试,验证能否成功 bind 和 lookup 一个 DataSource
:
@Test
public void whenMockJndiDataSource_thenReturnJndiDataSource() throws Exception {
this.initContext.bind("java:comp/env/jdbc/datasource",
new DriverManagerDataSource("jdbc:h2:mem:testdb"));
DataSource ds = (DataSource) this.initContext.lookup("java:comp/env/jdbc/datasource");
assertNotNull(ds.getConnection());
}
关键点:
- 使用
bind()
将DriverManagerDataSource
绑定到标准 JNDI 名 - 使用
lookup()
按名查找,类型转换后验证连接可用 - 如果 lookup 名称不存在,会直接抛异常 ❌
💡 小贴士:
DriverManagerDataSource
来自 Spring JDBC,适合测试场景,生产环境请用 HikariCP 等连接池。
4. 使用 Simple-JNDI 测试 JNDI 数据源(推荐方式)
由于 SimpleNamingContextBuilder
已废弃,更推荐使用功能更强、配置更灵活的 Simple-JNDI 库。
✅ 优势
- 支持从属性文件加载数据源配置
- 兼容非 Java EE 环境(如 Spring Boot)
- 可模拟复杂的 JNDI 层级结构
步骤一:引入依赖
在 pom.xml
中添加:
<dependency>
<groupId>com.github.h-thurow</groupId>
<artifactId>simple-jndi</artifactId>
<version>0.23.0</version>
<scope>test</scope>
</dependency>
最新版可在 Maven Central 查询。
步骤二:配置 jndi.properties
在 src/main/resources
下创建 jndi.properties
文件:
java.naming.factory.initial=org.osjava.sj.SimpleContextFactory
org.osjava.sj.jndi.shared=true
org.osjava.sj.delimiter=.
jndi.syntax.separator=/
org.osjava.sj.space=java:/comp/env
org.osjava.sj.root=src/main/resources/jndi
关键配置说明:
配置项 | 说明 |
---|---|
java.naming.factory.initial |
指定 JNDI 上下文工厂 |
org.osjava.sj.jndi.shared |
所有 InitialContext 共享内存 ✅ |
org.osjava.sj.space |
设置 JNDI 查找的根路径 |
org.osjava.sj.root |
指定属性文件存放目录 |
⚠️ 注意路径分隔符和命名空间设置,避免 ENC(Environment Naming Context)查找失败。
步骤三:定义数据源配置文件
在 src/main/resources/jndi
目录下创建 datasource.properties
:
ds.type=javax.sql.DataSource
ds.driver=org.h2.Driver
ds.url=jdbc:h2:mem:testdb
ds.user=sa
ds.password=password
文件名和路径由
jndi.properties
中的org.osjava.sj.root
决定。
步骤四:编写测试代码
@BeforeEach
public void setup() throws Exception {
this.initContext = new InitialContext();
}
@Test
public void whenMockJndiDataSource_thenReturnJndiDataSource() throws Exception {
String dsString = "org.h2.Driver::::jdbc:h2:mem:testdb::::sa";
Context envContext = (Context) this.initContext.lookup("java:/comp/env");
DataSource ds = (DataSource) envContext.lookup("datasource/ds");
assertEquals(dsString, ds.toString());
}
说明:
lookup("java:/comp/env")
进入配置的命名空间lookup("datasource/ds")
对应jndi/datasource.properties
中的ds
对象- Simple-JNDI 会自动根据
.type
创建对应实例
💡 踩坑提醒:路径拼写、斜杠方向、文件位置一定要对,否则
lookup
会抛NameNotFoundException
。
5. 总结
本文介绍了两种在 Spring 中测试 JNDI 数据源的方法:
方式 | 是否推荐 | 适用场景 |
---|---|---|
SimpleNamingContextBuilder |
❌ 不推荐(已废弃) | 老项目兼容 |
Simple-JNDI | ✅ 强烈推荐 | 所有新项目 |
📌 核心要点:
- 单元测试中应避免依赖真实数据库 ✅
- 使用 mock 数据源提升测试速度和稳定性
- 推荐使用 Simple-JNDI + 属性文件 的方式,配置清晰、易于维护
- 注意 JNDI 路径、命名空间和文件位置的匹配
所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-persistence-simple