Sử dụng Data-source cấu hình Tomcat JNDI trong Unit Test

Khi phát triển các ứng dụng chạy trên các Web Application Server (WAS) như Tomcat, chúng ta thường định nghĩa và đăng ký Database kết nối với ứng dụng dưới dạng một DataSource trong file context.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<Context antiJARLocking="true" antiResourceLocking="true" reloadable="true">
    ...
    <Resource name="todoDataSource" auth="Container" type="javax.sql.DataSource"
              maxTotal="100" maxIdle="30" maxWaitMillis="10000"
              username="test" password="test" driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/test"/>
    ...
</Context>
Việc định nghĩa DataSource theo cách này mang lại lợi ích là có thể sử dụng Connection Pool do WAS cung cấp. Đồng thời, thông qua JNDI, các đối tượng cần tương tác với Database (như DAO) có thể dễ dàng lấy ra DataSource và Connection để sử dụng.
Tuy nhiên, trong trường hợp bạn không sử dụng các framework như Spring-JDBC hay iBatis mà tự viết mã JDBC thuần (JDBC coding) và cần kiểm thử (test) class đó, làm thế nào để lấy được DataSource đã khai báo trong WAS? Liệu có cần phải khởi chạy cả WAS lên để thực hiện Unit Test không?
Câu trả lời là Không. Mặc dù DataSource được khai báo trong WAS, nhưng chúng ta vẫn có thể lấy nó thông qua JNDI. JNDI là một giao diện tiêu chuẩn (standard interface) và các WAS như Tomcat cung cấp các bản thực thi (implementation) tuân thủ tiêu chuẩn đó. Nói cách khác, chúng ta chỉ cần kết nối DataSource thông qua JNDI giống như cách mà Tomcat vẫn làm.
Giả sử chúng ta có một class tên là JndiDataSourceManager chuyên lấy DataSource thông qua JNDI và cần kiểm tra xem nó có hoạt động tốt hay không. File context.xml hiện đang nằm tại đường dẫn src/main/webapp/META-INF/.
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class JndiDataSourceManager {
    private String defaultDataSourceJndiName = "java:comp/env/todoDataSource";

    public DataSource getDataSource(String jndiName) {
        try {
            return (DataSource) new InitialContext().lookup(jndiName);
        } catch (NamingException e) {
            throw new RuntimeException("Fail to get a data-source with jndi: " + jndiName);
        }
    }

    public DataSource getDefaultDataSource() {
        return getDataSource(defaultDataSourceJndiName);
    }
}
Để kiểm thử JndiDataSourceManager trong tình huống này, bạn cần các thư viện hỗ trợ. Hãy thêm các dependency sau vào file pom.xml:
<dependency>
    <groupId>com.github.h-thurow</groupId>
    <artifactId>TomcatJNDI</artifactId>
    <version>1.0.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>7.0.37</version>
    <scope>test</scope>
</dependency>
Sau đó, tạo class JndiDataSourceManagerTest để kiểm thử và viết code như sau:
import hthurow.tomcatjndi.TomcatJNDI;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.File;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;

public class JndiDataSourceManagerTest {
    private JndiDataSourceManager jndiDataSourceManager;
    private TomcatJNDI tomcatJNDI;

    @Before
    public void setUp() {
        // Khởi tạo thực thể TomcatJNDI và chỉ định file context.xml của WAS
        tomcatJNDI = new TomcatJNDI();
        tomcatJNDI.processContextXml(new File("src/main/webapp/META-INF/context.xml"));
        tomcatJNDI.start();
        jndiDataSourceManager = new JndiDataSourceManager();
    }

    @After
    public void tearDown() {
        // Bắt buộc phải gọi tearDown để dọn dẹp tài nguyên
        tomcatJNDI.tearDown();
    }

    @Test
    public void test_getDataSourceUsingJndi() {
        DataSource dataSource = jndiDataSourceManager.getDataSource("java:comp/env/todoDataSource");
        assertThat("data source from jndi", dataSource, is(notNullValue()));
    }

    @Test
    public void test_defaultDataSource() {
        DataSource dataSource = jndiDataSourceManager.getDefaultDataSource();
        assertThat("data source from jndi", dataSource, is(notNullValue()));
    }

    @Test(expected = RuntimeException.class)
    public void test_getDataSource_with_not_defined_name() {
        jndiDataSourceManager.getDataSource("not-defined-jndi-name");
    }
}
Đầu tiên, trong phương thức @Before (setUp), chúng ta tạo một instance của TomcatJNDI và chỉ định file context.xml mà WAS sẽ sử dụng, sau đó gọi start(). Lưu ý quan trọng là trong phương thức @After (tearDown), bạn phải gọi hàm tearDown() của instance TomcatJNDI.
Khi chạy các đoạn mã kiểm thử trên, bạn sẽ thấy các test case lấy DataSource đều thành công.
Bằng cách này, chúng ta có thể kiểm tra xem DataSource đã được khai báo đúng trong context của WAS hay chưa và có lấy ra được hay không mà không cần khởi chạy WAS. Thay vì phải thực hiện các bài kiểm thử End-to-End (E2E) phức tạp như bật server rồi gọi trang web hoặc API, bạn có thể xác nhận mọi thứ nhanh chóng chỉ thông qua Unit Test.
Nguồn bài viết ryukato.github.io