1. Overview
1.概述
Typically, when testing an application that uses JNDI, we may want to use a mocked datasource instead of a real one. This is a common practice when testing in order to make our unit tests simple and fully separated from any external context.
通常情况下,当测试一个使用JNDI的应用程序时,我们可能想使用一个模拟的数据源而不是一个真实的数据源。这是测试时的一种常见做法,目的是使我们的单元测试简单并与任何外部环境完全分离。
In this tutorial, we’ll showcase how to test a mock JNDI datasource using the Spring Framework and the Simple-JNDI library.
在本教程中,我们将展示如何使用Spring框架和Simple-JNDI库测试一个模拟的JNDI数据源。
Throughout this tutorial, we’re only going to focus on unit tests. But be sure to check out our article on how to create a Spring application using JPA with a JNDI datasource.
在本教程中,我们将只关注单元测试。但请务必查看我们的文章,了解如何使用带有JNDI数据源的JPA创建一个Spring应用程序。
2. Quick JNDI Recap
2.快速的JNDI回顾
In short, JNDI binds logical names to external resources like database connections. The main idea is that the application doesn’t have to know anything about the defined datasource except its JNDI name.
简而言之,JNDI 将逻辑名称与数据库连接等外部资源绑定。其主要思想是,除了其JNDI名称外,应用程序不必知道关于所定义的数据源的任何信息。
Simply put, all naming operations are relative to a context, so to use JNDI to access a naming service, we need to create an InitialContext object first. As the name implies the InitialContext class encapsulates the initial (root) context that provides the starting point for naming operations.
简单地说,所有的命名操作都是相对于上下文而言的,所以要使用JNDI来访问一个命名服务,我们需要先创建一个InitialContext对象。顾名思义,InitialContext类封装了初始(根)上下文,为命名操作提供起点。
In simple words, the root context acts as an entry point. Without it, JNDI can’t bind or lookup our resources.
简单地说,根上下文充当了一个入口点。没有它,JNDI就不能绑定或查询我们的资源。
3. How to Test a JNDI Datasource with Spring
3.如何用Spring测试一个JNDI数据源
Spring provides out-of-box integration with JNDI through SimpleNamingContextBuilder. This helper class offers a great way to mock a JNDI environment for testing purposes.
Spring通过SimpleNamingContextBuilder提供了与JNDI的开箱集成。这个辅助类为测试目的提供了一个模拟JNDI环境的好方法。
So, let’s see how we can use the SimpleNamingContextBuilder class to unit test a JNDI datasource.
因此,让我们看看如何使用SimpleNamingContextBuilder类来单元测试JNDI数据源。
First, we need to build an initial naming context for binding and retrieving the datasource object:
首先,我们需要建立一个初始命名环境,用于绑定和检索数据源对象。
@BeforeEach
public void init() throws Exception {
SimpleNamingContextBuilder.emptyActivatedContextBuilder();
this.initContext = new InitialContext();
}
We’ve created the root context using the emptyActivatedContextBuilder() method because it provides more flexibility over the constructor, as it creates a new builder or returns the existing one.
我们使用emptyActivatedContextBuilder()方法创建了根上下文,因为它提供了比构造函数更多的灵活性,因为它可以创建一个新的构建器或返回现有的构建器。
Now that we have a context, let’s implement a unit test to see how to store and retrieve a JDBC DataSource object using JNDI:
现在我们有了一个上下文,让我们实现一个单元测试,看看如何使用JNDI来存储和检索一个JDBC 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());
}
As we can see, we use the bind() method to map our JDBC DataSource object to the name java:comp/env/jdbc/datasource.
正如我们所看到的,我们使用 bind()方法将我们的JDBC DataSource 对象映射到java:comp/env/jdbc/datasource名称。
Then we use the lookup() method to retrieve a DataSource reference from our JNDI context using the exact logical name that we used previously to bind the JDBC DataSource object.
然后我们使用lookup()方法从我们的JNDI上下文中检索一个DataSource引用,使用的是我们之前用来绑定JDBCDataSource对象的确切逻辑名称。
Note that, JNDI will simply throw an exception in case the specified object is not found in the context.
请注意,如果在上下文中没有找到指定的对象,JNDI将简单地抛出一个异常。
It’s worth mentioning that the SimpleNamingContextBuilder class is deprecated since Spring 5.2 in favor of other solutions such as Simple-JNDI.
值得一提的是, SimpleNamingContextBuilder类从Spring 5.2开始被弃用,转而采用其他解决方案,如Simple-JNDI。
4. Mock and Test a JNDI Datasource Using Simple-JNDI
4.使用Simple-JNDI模拟和测试一个JNDI数据源
Simple-JNDI allows us to bind objects defined in property files to a mocked JNDI environment. It comes with great support for obtaining objects of type javax.sql.DataSource from JNDI outside Java EE containers.
Simple-JNDI允许我们将属性文件中定义的对象绑定到一个模拟的JNDI环境中。它对从Java EE容器外的JNDI中获取javax.sql.DataSource类型的对象有很大的支持。
So, let’s see how we can use it. First, we need to add the Simple-JNDI dependency to our pom.xml:
因此,让我们看看如何使用它。首先,我们需要将Simple-JNDI依赖性添加到我们的pom.xml。
<dependency>
<groupId>com.github.h-thurow</groupId>
<artifactId>simple-jndi</artifactId>
<version>0.23.0</version>
</dependency>
The latest version of Simple-JNDI library can be found on Maven Central.
Simple-JNDI库的最新版本可以在Maven Central上找到。
Next, we’re going to configure Simple-JNDI with all the details it needs to set up a JNDI context. To do so, we need to create a jndi.properties file which needs to be placed on the classpath:
接下来,我们要配置Simple-JNDI的所有细节,以建立一个JNDI上下文。为此,我们需要创建一个jndi.properties文件,需要放在classpath上。
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 specifies the context factory class that will be used to create the initial context.
java.naming.factory.initial 指定将用于创建初始上下文的上下文工厂类。
org.osjava.sj.jndi.shared=true means that all InitialContext objects will share the same memory.
org.osjava.sj.jndi.shared=true 意味着所有InitialContext对象将共享相同的内存。
As we can see, we used the org.osjava.sj.space property to define java:/comp/env as the starting point of all JNDI lookups.
正如我们所看到的,我们使用 org.osjava.sj.space属性来定义java:/comp/env作为所有JNDI查询的起点。
The basic idea behind using both org.osjava.sj.delimiter and jndi.syntax.separator properties is to avoid the ENC problem.
同时使用org.osjava.sj.delimiter和jndi.syntax.separator属性的基本思想是为了避免ENC问题。
org.osjava.sj.root property lets us define the path to where property files are stored. In our case, all the files will be located under the src/main/resources/jndi folder.
org.osjava.sj.rootproperty让我们定义属性文件的存储路径。在我们的例子中,所有的文件都将位于src/main/resources/jndi文件夹下。
So, let’s define a javax.sql.DataSource object inside our datasource.properties file:
因此,让我们在javax.sql.DataSource文件中定义一个datasource.properties对象。
ds.type=javax.sql.DataSource
ds.driver=org.h2.Driver
ds.url=jdbc:jdbc:h2:mem:testdb
ds.user=sa
ds.password=password
Now, let’s create an InitialContext object for our unit test:
现在,让我们为我们的单元测试创建一个InitialContext对象。
@BeforeEach
public void setup() throws Exception {
this.initContext = new InitialContext();
}
Finally, we’ll implement a unit test case to retrieve the DataSource object already defined in the datasource.properties file:
最后,我们将实现一个单元测试案例来检索已经在datasource.properties文件中定义的DataSource对象。
@Test
public void whenMockJndiDataSource_thenReturnJndiDataSource() throws Exception {
String dsString = "org.h2.Driver::::jdbc: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());
}
5. Conclusion
5.总结
In this tutorial, we explained how to tackle the challenge of testing JNDI outside J2EE containers. We looked at how to test a mock JNDI datasource using the Spring Framework and the Simple-JNDI library.
在本教程中,我们解释了如何应对在J2EE容器之外测试JNDI的挑战。我们研究了如何使用Spring框架和Simple-JNDI库来测试一个模拟的JNDI数据源。
As always, the code is available over on GitHub.
像往常一样,代码可在GitHub上获得。