問題緣起
單元測試默認情況下使用嵌入式數據庫,例如H2。如果要切換爲MySQL,直接移除H2驅動,在application.properties(yml)配置相應的連接信息,都不起作用。那該如何切換配置呢?
單元測試數據庫
在SpringBoot的單元測試中,默認使用嵌入數據庫,例如H2,HSQLDB等.默認情況下無需指定具體的嵌入數據庫類型,只需要在pom.xml文件中加入相應的數據庫驅動即可,示例如下:
<dependencies> <!-- 略去其餘依賴 ---> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> </dependencies>
這裏Scope指定其作用的範圍,test表示其可以在測試中生效,即可單元測試程序會使用H2數據庫。
使用MySQL作爲測試數據庫
如果不想使用H2之類的嵌入數據庫,而是希望在實際運行和單元測試的數據庫保持一致,該如何處理呢?
首先了解一下AutoConfigureTestDataBase.java的代碼定義:
** * Annotation that can be applied to a test class to configure * a test database to use * instead of any application defined or auto-configured {@link DataSource}. */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @ImportAutoConfiguration @PropertyMapping("spring.test.database") public @interface AutoConfigureTestDatabase { @PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE) Replace replace() default Replace.ANY; EmbeddedDatabaseConnection connection() default EmbeddedDatabaseConnection.NONE; enum Replace { ANY, AUTO_CONFIGURED, NONE } }
從類上的說明信息來看,其主要用來配置測試程序連接的數據庫,這個註解的設置將覆蓋自動配置的數據庫信息。這裏需要注意一下replace屬性默認爲ANY,沒有做數據庫約束。
connection:默認讀取classpath包中掃描到的數據庫驅動包信息,其默認使用嵌入式數據庫。
定義抽象BaseJPA的基類:
@RunWith(SpringRunner.class) @DataJpaTest @EnableJpaRepositories(basePackages = "org.demo.data.dao") @EntityScan(basePackages="org.demo.data.dao") @TestPropertySource(value= {"classpath:application-unittest.properties"}) @ContextConfiguration(classes= {AuditorConfig.class}) @Getter @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public abstract class BaseJPA { @Autowired private TestEntityManager testEntityManager; }
這裏定義了基於SpringDataJPA的測試基類,封裝了常用的配置信息:
- RunWith:指定測試程序運行的環境
- DataJpaTest 用於指定當前測試程序用於測試DAO層,其中默認啓用數據操作的相關內容,詳細功能可以參照其類定義
- EnableJpaRepositories 指定Repository的目錄
- EntityScan 指定SpringDataJPA實體類的目錄位置
- TestPropertySource 指定系統自定義的配置文件,用以替代默認的application.properties(yml)
- ContextConfiguration 指定當前應用使用的配置類,設置相應的配置信息
- Getter 爲Lombok包的註解,用以生成Getter方法
- AutoConfigureTestDatabase 用於指定測試程序依賴的數據庫,這裏使用None,不再從pom.xml文件中加載嵌入式數據庫驅動。而是直接從系統配置文件application.properties中讀取相應的數據庫連接信息。
修改POM文件中的依賴
在使用MySQL作爲單元測試的數據庫,需要調整pom文件中MySQL的驅動程序包依賴:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
這裏需要將其scope表示其只在系統運行之時,會被使用到。移除scope設置之後,將使用默認值,compile。其將作用與表示爲當前依賴參與項目的編譯、測試和運行階段,屬於強依賴。例如:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
如果不能正確設置scope,在啓動過程中,會報出如下錯誤:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Failed to replace DataSource with an embedded database for tests. If you want an embedded database please put a supported one on the classpath or tune the replace attribute of @AutoConfigureTestDatabase. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1771) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1255) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760) ... 52 more Caused by: java.lang.IllegalStateException: Failed to replace DataSource with an embedded database for tests. If you want an embedded database please put a supported one on the classpath or tune the replace attribute of @AutoConfigureTestDatabase. at org.springframework.util.Assert.state(Assert.java:73) at org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration$EmbeddedDataSourceFactory.getEmbeddedDatabase(TestDatabaseAutoConfiguration.java:185) at org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration$EmbeddedDataSourceFactoryBean.afterPropertiesSet(TestDatabaseAutoConfiguration.java:151) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1830) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1767) ... 63 more
從上述錯誤信息可知,默認情況下,會從classpath路徑中搜索使用嵌入式數據庫的驅動,這裏報錯的原因是由於沒有在classpath中找到有效的數據庫驅動。
測試代碼
UserRepository是基於JpaRepository擴展而來的接口,其中基於方法名定義了若干查詢,針對其中方法的單元測試:
@Slf4j public class AnnotatedQueryUserTest extends BaseJPA { @Autowired private UserRepository userRepository; @Test @Sql(value="classpath:sqls/dao/user_annotated_query.sql") public void testCustomResult() { List<UserCount> userCountList = this.userRepository.listGroupedUser(0L); log.info("userCount List:{}", userCountList.size()); Assert.assertThat(userCountList, hasSize(3)); List<UserEntity> userEntities = this.userRepository.findCustomByNameAndSex("Robin Li", Gender.MALE); log.info("User Entity count:{}", userEntities.size()); Assert.assertThat(userEntities, hasSize(1)); } }
總結
在本文中重點需要講述的是如何自定義單元測試中的數據庫類型,默認情況是使用嵌入式數據庫。例如從嵌入式數據庫H2切換到MySQL數據庫,其中的關鍵點是AutoConfigureTestDatabase以及其中的replace屬性。本文中使用了SpringDataJPA作爲數據庫中間層。