【问题标题】:How to test a mocked JNDI datasource with Spring?如何使用 Spring 测试模拟的 JNDI 数据源?
【发布时间】:2011-08-21 21:42:52
【问题描述】:

我是 Spring 的新手,想知道如何创建使用模拟数据源的 JUnit 测试以及如何使用 JNDI 上下文?目前,我的应用程序使用来自 tomcat 的 JNDI 上下文来检索连接,并通过该连接从数据库中检索数据。所以我想我需要模拟 JNDI 调用和数据检索。关于解决这个问题的最佳方法的任何好的指示都会很棒!非常感谢!

【问题讨论】:

    标签: spring testing junit mocking datasource


    【解决方案1】:

    您可以通过扩展 Spring 的 AbstractDataSource 创建自己的模拟数据源。

    import java.sql.Connection;
    import java.sql.SQLException;
    
    import org.springframework.jdbc.datasource.AbstractDataSource;
    
    /**
     * Mock implementation of DataSource suitable for use in testing.
     * 
     *
     */
    public class MockDataSource extends AbstractDataSource {
        private Connection connection;
    
        /**
         * Sets the connection returned by javax.sql.DataSource#getConnection()
         * and javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
         * 
         * @param connection
         */
        public void setConnection(Connection connection) {
            this.connection = connection;
        }
    
        /*
         * (non-Javadoc)
         * @see javax.sql.DataSource#getConnection()
         */
        public Connection getConnection()
                throws SQLException {
            return connection;
        }
    
        /*
         * (non-Javadoc)
         * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
         */
        public Connection getConnection(String username, String password)
                throws SQLException {
            return connection;
        }
    }
    

    我会将连接的 JNDI 查找与其余代码分开。将 DataSource 注入您的数据访问对象 (DAO) 并使用 MockDataSource 来测试 DAO。

    【讨论】:

    • 如果我注入数据源,这不会消除 JNDI 查找的需要吗?
    • 可以的。在 Spring 中有多种方法可以获取 DataSource。一旦你有了它,你可以注入。不过,Spring 可以从 JNDI 读取数据源。
    • 我编辑了您的答案以删除第一行的缩进。现在语法高亮工作。希望你不要介意。
    【解决方案2】:

    我通常在单独的文件中定义我的 JNDI 依赖项,例如 datasource-context.xml:

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/jee
            http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
    
        <jee:jndi-lookup id="dataSource" 
            jndi-name="java:comp/env/dataSource" 
            expected-type="javax.sql.DataSource" />
    
    </beans>
    

    这样我就可以在测试资源中创建另一个文件并定义适合我的测试数据源,例如datasource-testcontext.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    
        <bean id="dataSource"
            class="org.springframework.jdbc.datasource.DriverManagerDataSource"
            p:driverClassName="org.hsqldb.jdbcDriver"
            p:url="jdbc:hsqldb:hsql://localhost:9001"
            p:username="sa"
            p:password="" /> 
    
    </beans>
    

    然后在我的测试类中,我使用数据源的测试配置而不是依赖于 JNDI 的生产配置

    @ContextConfiguration({
        "classpath*:META-INF/spring/datasource-testcontext.xml",
        "classpath*:META-INF/spring/session-factory-context.xml"
    })
    public class MyTest {
    
    }
    

    如果数据源未在单独的文件中定义,您仍然可以轻松地存根 JNDI 调用返回的对象:

    【讨论】:

    • 我这样做了,但我仍然得到异常原因:javax.naming.NoInitialContextException:需要在环境或系统属性中指定类名,或作为小程序参数,或在应用程序资源中文件:java.naming.factory.initial
    • @fastcodejava 你到底做了什么?为 JNDI 相关配置使用单独的文件?在测试设置中创建了 JNDI 上下文?还是用SimpleNamingContextBuilder
    • 不幸的是,oracle 博客文章的链接似乎已失效。我找不到等效的替代品...
    【解决方案3】:

    您可以使用 SimpleNamingContextBuilder 使 jndi 数据源可用于您的测试:

        SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
        builder.bind("java:comp/env/jdbc/mydatasource", dataSource);
        builder.activate();
    

    https://fisheye.springsource.org/browse/spring-framework/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java?hb=true

    这并不完全是模拟数据源,但它确实通过 jndi 使数据源可用于您的测试。

    【讨论】:

    • 我这样做了,但我仍然得到异常原因:javax.naming.NoInitialContextException:需要在环境或系统属性中指定类名,或作为小程序参数,或在应用程序资源中文件:java.naming.factory.initial
    【解决方案4】:

    你总是可以创建一个 beans.test.xml 配置,你首先引用 beans.xml,然后覆盖数据源配置:

    src/main/resources/beans.xml

    <!-- Database configuration -->
    <import resource="beans.datasource.jndi.xml" />
    

    src/test/resources/beans.test.xml

    <import resource="beans.xml" />
    <import resource="beans.datasource.test.xml" />
    

    JUnit 测试类:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:/beans.test.xml" })
    public class ASRTests
    {
    ...
    }
    

    在你的 jndi bean 中,声明引用

    <jee:jndi-lookup expected-type="javax.sql.DataSource" id="mysqlDataSource" jndi-name="jdbc/mysql"/>
    

    在您的测试 bean 中,声明数据源

    <bean id="mysqlDataSource" ...>
    ...
    </bean>
    

    请记住将测试数据源 bean 移动到测试文件夹中。

    【讨论】:

      【解决方案5】:

      Spring 的 org.springframework.jndi.JndiObjectFactoryBean 最适合 JNDI 查找。根据其文档,它还允许为基于弹簧的测试用例注入默认值。

      参考下面的spring配置(命名为spring-test-db-config.xml)

      <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource">
          <property name="URL" value="jdbc:oracle:thin:@localhost:1521:XE"/>
          <property name="user" value="UNITTEST"/>
          <property name="password" value="UNITTEST"/>
      </bean>
      
      <bean id="dataSourceFromJndi" class="org.springframework.jndi.JndiObjectFactoryBean">
          <!-- Any junk value will suffice as that is always gonna throw NamingException -->
          <property name="jndiName" value="jdbc/Ds"/>
          <property name="defaultObject" ref="dataSource"/>
      </bean>
      

      添加其他配置文件定义的bean参考dataSourceFromJndibean

      <!-- START OF SERVICES -->
      <bean class="com.sample.Service" >
          <property name="dataSource" ref="dataSourceFromJndi" />
      </bean>
      

      这种方法的优点是您可以保留 2 个不同的数据库配置文件 - 一个用于生产,另一个用于单元测试。只需导入正确的。测试配置将包含一个默认对象。

      【讨论】:

        【解决方案6】:

        Java 配置.....

        Junit 测试用例

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(classes = {DatabaseConfigStub.class}, loader= AnnotationConfigContextLoader.class)
        public class DatabaseConfigTest  {
        
        @Autowired
        private DataSource datasource;
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        
        @Before
        public void setUp() throws Exception {
        
        }
        
        @After
        public void tearDown() throws Exception {
        }
        
        @Test
        public void testDataSource() {
            assertNotNull(datasource);
            assertNotNull(jdbcTemplate);
        }
        
        }
        

        DatabaseConfigStub

        public class DatabaseConfigStub {
        
        private static final Logger log = Logger.getLogger(DatabaseConfigStub.class);
        
                private static final String DS_NAME = "jdbc/DS_NAME";
        
        @Bean
        DataSource dataSource() {
            JndiObjectFactoryBean jndiObjectBean = EasyMock.createMock(JndiObjectFactoryBean.class);
            jndiObjectBean.setJndiName(DS_NAME);
            jndiObjectBean.setResourceRef(true);
            jndiObjectBean.setProxyInterfaces(DataSource.class);
        
            EasyMock.expect( (DataSource)jndiObjectBean.getObject()).andReturn(new DataSource() {
        
                    public <T> T unwrap(Class<T> iface) throws SQLException {
                        // TODO Auto-generated method stub
                        return null;
                    }
        
                    public boolean isWrapperFor(Class<?> iface) throws SQLException {
                        // TODO Auto-generated method stub
                        return false;
                    }
        
                    public void setLoginTimeout(int seconds) throws SQLException {
                        // TODO Auto-generated method stub
        
                    }
        
                    public void setLogWriter(PrintWriter out) throws SQLException {
                        // TODO Auto-generated method stub
        
                    }
        
                    public int getLoginTimeout() throws SQLException {
                        // TODO Auto-generated method stub
                        return 0;
                    }
        
                    public PrintWriter getLogWriter() throws SQLException {
                        // TODO Auto-generated method stub
                        return null;
                    }
        
                    public Connection getConnection(String username, String password)
                            throws SQLException {
                        // TODO Auto-generated method stub
                        return null;
                    }
        
                    public Connection getConnection() throws SQLException {
                        // TODO Auto-generated method stub
                        return null;
                    }
                }
            );
            EasyMock.replay(jndiObjectBean);
        
            return (DataSource) jndiObjectBean.getObject();
        }
        
        @Bean
        JdbcTemplate jdbcTemplate(){
            return new JdbcTemplate( dataSource());
        }
        

        }

        【讨论】:

          【解决方案7】:

          您也可以使用 Simple-JNDI。它是一个内存中的 JNDI 实现,用于在 J2EE 容器之外使用 JNDI 上下文。它允许您在生产和测试中使用相同的 bean 定义文件。假设这是您在生产中的 bean 定义:

          <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
              <property name="jndiName" value="java:comp/env/jdbc/DataSource"/>
          </bean>
          <bean id="dao" class="my.Dao">
              <property name="dataSource" ref="dataSource" />
          </bean>
          

          像这样创建一个属性文件

          type=javax.sql.DataSource
          driverClassName=org.gjt.mm.mysql.Driver
          url=jdbc:mysql://localhost/testdb
          username=user_name
          password=password
          

          将 Simple-JNDI 和一个带有少量配置的 jndi.properties 文件放在你的类路径中。然后像往常一样访问您的数据源。

          More about Simple-JNDI is found here.

          【讨论】:

            【解决方案8】:

            我最近遇到了为我的 JUnit 测试用例模拟 JNDI DB 资源的问题。我处理创建了一个单独的 DBStub 类,其中包含模拟的 javax.sql.DataSource 并将其分配给 JNDI 命名上下文构建器 SimpleNamingContextBuilder 的 Spring 简单实现,

            public class DBStub {
            
            @Mock
            DataSource dataSource;
            
            public DBStub() {
                try {
                    MockitoAnnotations.initMocks(this);
                    SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
                    builder.bind("java:comp/env/jdbc/DataSource", dataSource);
                } catch (NamingException e) {
                    fail();
                }
            
                } 
            }
            

            将此类扩展到实际的 JUnit 测试类将解决问题,

            @RunWith(SpringJUnit4ClassRunner.class)
            @ContextConfiguration(locations = { "classpath:application-context.xml" })
            public class PricingOperationTest extends DBStub {
            
                @Autowired
                private JdbcTemplate template;
            
                @Test
                public void testDataSource() {
                    assertNotNull(template.getDataSource());
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2020-01-19
              • 1970-01-01
              • 2012-08-16
              • 1970-01-01
              • 2010-11-07
              • 2020-11-07
              • 2014-04-01
              • 2011-01-01
              • 1970-01-01
              相关资源
              最近更新 更多