如何在 Mockito 单元测试期间生成 H2 嵌入式数据库?

Posted

技术标签:

【中文标题】如何在 Mockito 单元测试期间生成 H2 嵌入式数据库?【英文标题】:How to generate H2 embedded database during Mockito unit test? 【发布时间】:2019-10-10 22:22:01 【问题描述】:

我目前正在开发一个 springboot API,已决定我和我的同事将在开发过程中使用 H2 嵌入式数据库。

数据库在启动应用程序时会正常生成,但我无法在单元测试期间使其工作:它们都失败说org.h2.jdbc.JdbcSQLException: Table '[...]' not found

大部分代码我基本上没有写,如果您需要更多,请告诉我:

application.properties初始化H2数据库的文件:

datasources.member-request.url=jdbc:h2:mem:db;IGNORECASE=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS DMDEOWNER\\;SET SCHEMA DMDEOWNER\\;runscript from 'classpath:h2_init_script.sql'
datasources.member-request.username=sa
datasources.member-request.password=
datasources.member-request.driver-class-name=org.h2.Driver
datasources.member-request.initialize=true

正如你在上面看到的,我们在 H2 初始化时执行了一个名为 h2_init_script.sql 的 SQL 脚本,在这个文件中我们有一些这样的 SQL 语句(例如,我将向你展示 T_THEME 表)

/* CREATE statements */
CREATE TABLE DMDEOWNER.T_THEME (
    F_NUM_THEME NUMBER(14) NOT NULL,
    F_REF_THEME VARCHAR2(30) NOT NULL,
    F_LIB_THEME VARCHAR2(50) NOT NULL,
    F_POS_THEME NUMBER(4) NOT NULL,
    F_BOOL_ACTIF NUMBER(1) NOT NULL,
    F_UTILISATEUR_MAJ VARCHAR2(40) NOT NULL,
    F_DT_CREATION DATE NOT NULL,
    F_DT_MAJ DATE,
    PRIMARY KEY (F_NUM_THEME)
);

/* INSERT statements */
/* ... */

这是我的测试类(及其父类):

public class ThemeRepositoryTest extends AbstractRepositoryTest 

  private final static int NB_ACTIVE_ENTITIES = 2;
  private final static int NB_INACTIVE_ENTITIES = 1;

  @Autowired
  private ThemeRepository repository;

  @Autowired
  private ThemeMapper mapper;


  // -------------------------------------------------------------------------
  // CREATE
  // -------------------------------------------------------------------------
  @Before
  public void init() 
    for (int i = INTEGER_ZERO; i < NB_ACTIVE_ENTITIES; i++) 
      insertTheme(true);
    
    for (int i = INTEGER_ZERO; i < NB_INACTIVE_ENTITIES; i++) 
      insertTheme(false);
    
    entityManager.flush();
  

  // -------------------------------------------------------------------------
  // READ
  // -------------------------------------------------------------------------
  @Test
  public void findAll() 
    List<ThemeEntity> list = repository.findAll();
    assertEquals(NB_ACTIVE_ENTITIES + NB_INACTIVE_ENTITIES, list.size());
  


@RunWith(SpringRunner.class)
@DataJpaTest
public abstract class AbstractRepositoryTest 

  @Autowired
  protected TestEntityManager entityManager;

  protected ThemeEntity insertTheme(boolean active) 
    ThemeEntity entity =  newThemeEntity(3L, "NAME", active, "USER");
    return entityManager.merge(entity);
  

这是我的错误发生的地方,在我调用init() 函数时insertTheme()

findAll(io.lacipav.repository.ThemeRepositoryTest)  Time elapsed: 0.227 s  <<< ERROR!
javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not prepare statement
    at io.lacipav.repository.ThemeRepositoryTest.init(ThemeRepositoryTest.java:38)
Caused by: org.hibernate.exception.SQLGrammarException: could not prepare statement
    at io.lacipav.repository.ThemeRepositoryTest.init(ThemeRepositoryTest.java:38)
Caused by: org.h2.jdbc.JdbcSQLException: 
Table "T_THEME" not found; SQL statement:
select themeentit0_.f_num_theme as f_num_th1_11_0_, themeentit0_.f_bool_actif as f_bool_a2_11_0_, themeentit0_.f_dt_creation as f_dt_cre3_11_0_, themeentit0_.f_dt_maj as f_dt_maj4_11_0_, themeentit0_.f_lib_theme as f_lib_th5_11_0_, themeentit0_.f_pos_theme as f_pos_th6_11_0_, themeentit0_.f_ref_theme as f_ref_th7_11_0_, themeentit0_.f_utilisateur_maj as f_utilis8_11_0_ from t_theme themeentit0_ where themeentit0_.f_num_theme=? [42102-197]
    at io.lacipav.repository.ThemeRepositoryTest.init(ThemeRepositoryTest.java:38)

由于错误告诉我它没有找到我的表,我猜这是因为我在构建我的 .war 文件时我的数据库没有运行,如果是这样,我怎样才能更快地初始化它?

编辑: 根据要求,这是 T_THEME 的 Entity 类:

@Entity
@Getter
@Setter
@Table(name = "T_THEME")
public class ThemeEntity 

  /** Primary key of the table. */
  @Id
  @NotNull
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "theme_generator")
  @SequenceGenerator(name = "theme_generator", sequenceName = "SEQ_NUM_THEME", allocationSize = 1)
  @Column(name = "f_num_theme")
  private Long idTechnique;

  /** Theme functional identifier */
  @NotNull
  @Column(name = "f_ref_theme")
  private String reference;

  /** Theme simple short length label */
  @NotNull
  @Column(name = "f_lib_theme")
  private String name;

  //...

注意:生成 SQL 序列“SEQ_NUM_THEME”并关联到 SQL 脚本中的 F_NUM_THEME。

编辑 2:

正如 Lesiak 所指出的,我的错误可能来自我的 DataSource 被注释 @DataJpaTest 替换。通过仔细查看日志,我发现了一些有趣的行。

[INFO ] 2019-05-24 16:22:20.341 [main] org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration$EmbeddedDataSourceBeanFactoryPostProcessor.process(TestDatabaseAutoConfiguration.java:106) : Replacing 'dataSource' DataSource bean with embedded version
[INFO ] 2019-05-24 16:22:20.346 [main] org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:824) : Overriding bean definition for bean 'dataSource' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]] with [Root bean: class [org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration$EmbeddedDataSourceFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
[INFO ] 2019-05-24 16:22:21.050 [main] org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory.initDatabase(EmbeddedDatabaseFactory.java:189) : Starting embedded database: url='jdbc:h2:mem:4ef27020-c472-4be9-a9af-73f5e0175846;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'

我们可以在最后一行看到我的 url 连接 jdbc:h2:mem:db 已被 jdbc:h2:mem:4ef27020-c472-4be9-a9af-73f5e0175846 替换。这可能是我收到一些 Table not found 错误的原因。

【问题讨论】:

请告诉我们ThemeEntity(只需@Entity注解) spring.jpa.properties.hibernate.default_schema=DMDEOWNER 有帮助吗? 哦,是的,实际上它仍然无法工作,但错误消息现在显示org.h2.jdbc.JdbcSQLException: Schema "DMDEOWNER" not found; 【参考方案1】:

我最终尝试了很多东西,以至于我不太确定它为什么现在可以工作......

在the documentation 中,据说默认情况下它会尝试找到schema.sql 来创建数据库和data.sql 来填充它。我正在使用我自己的 .sql 文件来完成这两项工作,所以我按照建议将它分开,它开始工作了!

所以现在我的 application.properties 如下:

# Datasource connection settings to H2 in-memory database
# To init db with h2_init_script.sql add the following to the url : INIT=CREATE SCHEMA IF NOT EXISTS DMDEOWNER\\;SET SCHEMA DMDEOWNER\\;runscript from 'classpath:h2_init_script.sql'
datasources.member-request.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;IGNORECASE=TRUE;
datasources.member-request.username=sa
datasources.member-request.password=
datasources.member-request.driver-class-name=org.h2.Driver
datasources.member-request.initialize=true

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.h2.console.settings.web-allow-others=true

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

由于我不再在 application.properties 中创建架构,因此我将其添加到 schema.sql 文件中:

CREATE SCHEMA IF NOT EXISTS DMDEOWNER;
SET SCHEMA DMDEOWNER;

最后,我在 Entity 类中的序列声明中添加了模式的名称前缀,因为我有一些 SEQUENCE NOT FOUND 错误:

@Entity
@Getter
@Setter
@Table(name = "T_THEME")
public class ThemeEntity 

  /** Primary key of the table. */
  @Id
  @NotNull
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "theme_generator")
  @SequenceGenerator(name = "theme_generator", sequenceName = "DMDEOWNER.SEQ_NUM_THEME", allocationSize = 1)
  @Column(name = "f_num_theme")
  private Long idTechnique;

  //...

【讨论】:

它正在工作,因为您根本没有使用schema.sql。 docs.spring.io/spring-boot/docs/current/reference/html/…:In a JPA-based app, you can choose to let Hibernate create the schema or use schema.sql, but you cannot do both. Make sure to disable spring.jpa.hibernate.ddl-auto if you use schema.sql. 嗯,这很奇怪:当我用 spring.jpa.hibernate.ddl-auto=none 替换 spring.jpa.hibernate.ddl-auto=update 时,它会带回 Table "[...]" not found 错误...我要倒退并一点一点地删除东西,看看到底是什么需要而不是,我会相应地编辑答案 我的猜测是你在 schema.sql 中创建了带有 schema 的表,而 hibernate ddl 在默认 schema 中创建了它。【参考方案2】:

问题在于您正在使用的@DataJpaTest。 见Documentation of @DataJpaTest

默认情况下,使用 @DataJpaTest 注释的测试将使用嵌入式内存数据库(替换任何显式或通常自动配置的数据源)。 @AutoConfigureTestDatabase 注解可用于覆盖这些设置。

你会在日志中找到类似的东西

EmbeddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version

要修复,请使用:

spring.test.database.replace=none
spring.jpa.properties.hibernate.default_schema=DMDEOWNER 

【讨论】:

我再次编辑了这个问题,你对 Replacing 'dataSource' ... 的看法是对的,但我仍然遇到错误,提示找不到架构 DMDEOWNER 奇怪,当我使用spring.test.database.replace=none 时,这条线消失了。运行测试时尝试在EmbeddedDataSourceBeanFactoryPostProcessor.process 中设置断点

以上是关于如何在 Mockito 单元测试期间生成 H2 嵌入式数据库?的主要内容,如果未能解决你的问题,请参考以下文章

H2单元测试与业务数据隔离之内嵌模式

单元测试如何使用 Mockito 模拟存储库

JUnit + Mockito 单元测试

如何使用 Mockito 在单元测试中调用 AppCompatActivity onCreate

Mockito单元测试

Mock单元测试