转自:https://gerrydevstory.com/2014/04/11/unit-testing-using-mysql-in-memory-database-on-spring/
Unit Testing Using In-Memory MySQL Database On Spring
Well the title lied, there’s no such thing as in-memory MySQL database (or at least I won’t be using it for this article). Instead I will use H2 in-memory database setup to run in “MySQL mode”
1 2 3 4 | < bean id = "dataSource" class = "org.apache.commons.dbcp.BasicDataSource" > < property name = "driverClassName" value = "org.h2.Driver" /> < property name = "url" value = "jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" /> </ bean > |
(thanks to joensson for )
If your app just uses plain jdbc then adding above datasource to your test context would be sufficient, but if you use JPA/Hibernate the cost of table setup, scanning etc could be quite significant.
To overcome this you can split the test context using @ContextHierarchy annotation.
In the example below I have unit tests for two DAOs: AccountDAOTest and CustomerDAOTest:
1 2 3 4 5 6 7 | @ContextHierarchy ({ @ContextConfiguration ( "/test-root-context.xml" ), @ContextConfiguration ( "AccountDAOTest-context.xml" ) }) @RunWith (SpringJUnit4ClassRunner. class ) public class AccountDAOTest { } |
1 2 3 4 5 6 7 | @ContextHierarchy ({ @ContextConfiguration ( "/test-root-context.xml" ), @ContextConfiguration ( "CustomerDAOTest-context.xml" ) }) @RunWith (SpringJUnit4ClassRunner. class ) public class CustomerDAOTest { } |
By doing this Spring test-root-context.xml will be setup once and reused accross all unit tests. Only put components common to all tests in test-root-context.xml. In my case I put the following:
- DataSource
- EntityManagerFactory
- TransactionManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!-- JDBC Data Source --> < bean id = "dataSource" class = "org.apache.commons.dbcp.BasicDataSource" > < property name = "driverClassName" value = "org.h2.Driver" /> < property name = "url" value = "jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" /> </ bean > <!-- EntityManagerFactory --> < bean class = "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id = "entityManagerFactory" > < property name = "persistenceUnitName" value = "persistenceUnit" /> < property name = "dataSource" ref = "dataSource" /> </ bean > <!-- Transaction Manager --> < bean class = "org.springframework.orm.jpa.JpaTransactionManager" id = "transactionManager" > < property name = "entityManagerFactory" ref = "entityManagerFactory" /> </ bean > |
All test specific components go into their respective context.
Don’t forget to add <tx:annotation-driven/> if your DAO uses it. This can’t be placed on test-root-context.xml because I don’t scan all my DAOs there.
And lastly — ofcourse — you need to make sure your pom.xml has dependency to spring-test, junit and h2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | < dependency > < groupId >com.h2database</ groupId > < artifactId >h2</ artifactId > < version >1.3.176</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-test</ artifactId > < version >${org.springframework-version}</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >junit</ groupId > < artifactId >junit</ artifactId > < version >4.7</ version > < scope >test</ scope > </ dependency > |
使用java代码进行bean的配置:
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)@EnableConfigurationProperties(value = {JpaProperties.class,DataSourceProperties.class})public class MfgMasterInMemoryDBConfig { // <!-- JDBC Data Source -->// <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">// <property name="driverClassName" value="org.h2.Driver"/>// <property name="url" value="jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" />// </bean>// // // <!-- EntityManagerFactory -->// <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">// <property name="persistenceUnitName" value="persistenceUnit" />// <property name="dataSource" ref="dataSource" />// </bean>// // <!-- Transaction Manager -->// <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">// <property name="entityManagerFactory" ref="entityManagerFactory" />// </bean> @Autowired private JpaProperties jpaProperties; @Bean public JpaVendorAdapter jpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } @Bean public EntityManagerFactoryBuilder entityManagerFactoryBuilder() { return new EntityManagerFactoryBuilder(jpaVendorAdapter(), jpaProperties.getProperties(), null); }@Bean
public EntityManager entityManger(EntityManagerFactory factory) { return factory.createEntityManager(); } @Bean public DataSource dataSource(DataSourceProperties baseDataSourceProperties){ DataSource dataSource = DataSourceBuilder .create(baseDataSourceProperties.getClassLoader()) .driverClassName("org.h2.Driver") .url("jdbc:h2:mem:testdb;MODE=MySQL") .username("sa") .password("") .build();; return dataSource; } @Bean JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){ JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(); jpaTransactionManager.setEntityManagerFactory(entityManagerFactory); return jpaTransactionManager; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, EntityManagerFactoryBuilder builder) { Map<String, Object> hibernateProps = new LinkedHashMap<>(); hibernateProps.putAll(jpaProperties.getHibernateProperties(dataSource));return builder
.dataSource(dataSource) .packages(User.class.getPackage().getName()) .properties(hibernateProps).jta(false) .build(); }}
Test类的主要注解:
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes={MfgMasterTestConfiguration.class,MfgMasterInMemoryDBConfig.class})//@SpringApplicationConfiguration(classes = MfgMasterDataApplication.class)public class MfgMasterDataApplicationTests { @Autowired public DataSource dataSource; @Before public void prepareUser(){ try { dataSource.getConnection().createStatement().executeUpdate("INSERT INTO user (id, email, password, created_by, creation_date, enabled, update_date, updated_by, version, firstname, lastname, language) " + "VALUES (-1, 'system@mfg.com', 'system', -1, '2015-03-02 17:36:38', 0, NULL, NULL, 0, 'system', 'system', 'US')"); } catch (SQLException e) { e.printStackTrace(); } } @Test public void contextLoads() { System.out.println("Done"); }}