使用 Unitils 对 Postgres 数据库运行单元测试时出现 NullPointerException

Posted

技术标签:

【中文标题】使用 Unitils 对 Postgres 数据库运行单元测试时出现 NullPointerException【英文标题】:NullPointerException while running unit test against Postgres database with Unitils 【发布时间】:2017-10-13 14:39:03 【问题描述】:

我有下表(迁移):

databaseChangeLog:
  - changeSet:
      id: 1
      author: me
      changes:
        - createTable:
            tableName: person
            columns:
              - column:
                  name: id
                  type: int
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: first_name
                  type: varchar(255)
                  constraints:
                    nullable: false
              - column:
                  name: last_name
                  type: varchar(255)
                  constraints:
                    nullable: false
  - changeSet:
      id: 2
      author: me
      changes:
        - insert:
            tableName: person
            columns:
              - column:
                  name: first_name
                  value: First
              - column:
                  name: last_name
                  value: Last

还有以下 DAO:

public interface HelloDao 
    @SqlQuery(
            "SELECT * FROM person"
    )
    @Mapper(PersonMapper.class)
    Person getPerson();

使用以下实体+映射器:

public class Person 
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) 
        this.firstName = firstName;
        this.lastName = lastName;
    

    public String getFirstName() 
        return firstName;
    

    public void setFirstName(String firstName) 
        this.firstName = firstName;
    

    public String getLastName() 
        return lastName;
    

    public void setLastName(String lastName) 
        this.lastName = lastName;
    

-

public class PersonMapper implements ResultSetMapper<Person> 
    public Person map(int index, ResultSet r, StatementContext ctx) throws SQLException 
        return new Person(r.getString("first_name"), r.getString("last_name"));
    

我正在使用 junit 和 unitils 为这个 Dao 编写单元测试。我的unitils.properties 文件设置如下:

database.driverClassName=org.postgresql.Driver
database.url=jdbc:postgresql://localhost:5432/springbootdb
database.dialect=postgresql
database.userName=<username>
database.password=<password>
database.schemaNames=public

为了清楚起见,这是我的application.properties 文件:

spring.thymeleaf.cache=false
spring.main.show-banner=false
logging.level.jdbc=OFF
logging.level.jdbc.sqltiming=DEBUG
logging.level.jdbc.resultsettable=DEBUG

spring.datasource.url= jdbc:postgresql://localhost:5432/springbootdb
spring.datasource.username=<username> 
spring.datasource.password=<password>
spring.jpa.hibernate.ddl-auto=create-drop

这是我的测试:

@Transactional(TransactionMode.ROLLBACK)
@RunWith(UnitilsBlockJUnit4ClassRunner.class)
public class HelloDaoTest 
    @TestDataSource
    DataSource dataSource;

    private DBI dbi;
    private HelloDao helloDao;

    @Before
    public void setUp() throws Exception 
        dbi = new DBI(dataSource);
        helloDao = dbi.onDemand(HelloDao.class);
    

    @Test
    @DataSet("HelloDaoTest.testSet.xml")
    public void helloDao() throws Exception 
        Person p = helloDao.getPerson();
        assertEquals("First", p.getFirstName());
    


还有我的固定装置 (HelloDaoTest.testSet.xml)

<?xml version="1.0" encoding="UTF-8" ?>
    <dataset>
        <person
            first_name="First"
            last_name="Last"
        />
    </dataset>

和 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.experiment.springboot</groupId>
    <artifactId>testspringbootproj</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Spring Boot Blank Project (from https://github.com/making/spring-boot-blank)</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>com.experiment.springboot.App</start-class>
        <java.version>1.8</java.version>
        <lombok.version>1.14.8</lombok.version>
        <log4jdbc.log4j2.version>1.16</log4jdbc.log4j2.version>
        <rest.assured.version>2.3.3</rest.assured.version>
        <jdbi.version>2.78</jdbi.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>$lombok.version</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>$rest.assured.version</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.bgee.log4jdbc-log4j2</groupId>
            <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
            <version>$log4jdbc.log4j2.version</version>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.4-1206-jdbc42</version>
        </dependency>
        <dependency>
            <groupId>org.jdbi</groupId>
            <artifactId>jdbi</artifactId>
            <version>$jdbi.version</version>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.unitils</groupId>
            <artifactId>unitils-dbunit</artifactId>
            <version>3.4.6</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework</groupId>
                        <artifactId>springloaded</artifactId>
                        <version>$spring-loaded.version</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.liquibase</groupId>
                <artifactId>liquibase-maven-plugin</artifactId>
                <version>3.4.1</version>
                <configuration>
                    <propertyFile>src/main/resources/liquibase.properties</propertyFile>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

当我运行此测试时,我收到以下错误:

java.lang.NullPointerException
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:820)
    at org.unitils.database.transaction.impl.DefaultUnitilsTransactionManager.rollback(DefaultUnitilsTransactionManager.java:157)
    at org.unitils.database.DatabaseModule.rollbackTransaction(DatabaseModule.java:425)
    at org.unitils.database.DatabaseModule.endTransactionForTestMethod(DatabaseModule.java:390)
    at org.unitils.database.DatabaseModule$DatabaseTestListener.afterTestTearDown(DatabaseModule.java:486)
    at org.unitils.core.Unitils$UnitilsTestListener.afterTestTearDown(Unitils.java:315)
    at org.unitils.core.junit.AfterTestTearDownStatement.evaluate(AfterTestTearDownStatement.java:48)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.unitils.core.junit.BeforeTestClassStatement.evaluate(BeforeTestClassStatement.java:41)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

看起来测试代码甚至没有到达。我做错了什么?

【问题讨论】:

不是任何这些技术的专家,但是如果您查看堆栈跟踪,看起来好像测试期望在完整的 Spring 上下文中存在某种事务管理器,但事实并非如此t 设置好(没有 spring junit runner)。 请看第二行测试@RunWith(UnitilsBlockJUnit4ClassRunner.class) 那么,您是否按照unitils.sourceforge.net/tutorial-spring.html 中的描述设置了 Spring 应用程序上下文? 是的,我这样做了@TassosBassoukos 【参考方案1】:

您的 pom.xml 中确实有两个数据库实现,但没有指定范围。 Spring 无法猜测哪个是正确的,因此是一个例外。

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    [...]
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>9.4-1206-jdbc42</version>
    </dependency>

这很可能会进一步帮助您:Configure specific in memory database for testing purpose in Spring

【讨论】:

【参考方案2】:

因为

Rollback transaction after @Test

它没有使用 Unitils,但似乎确实是您正在寻找的行为。 Unitils 似乎最后一次更新是在 2011 年(我怀疑这就是您的问题所在),因此可能值得将其丢弃。

【讨论】:

以上是关于使用 Unitils 对 Postgres 数据库运行单元测试时出现 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

Unitils 与 Spring TestContext 框架

单元测试工具 unitils

如何在 Postgres 中对数组列使用 BETWEEN 条件?

Postgres - 连接三个表并在查询中对数据使用聚合函数

如何对 Postgres 9.0 数据库进行逆向工程

关于单元测试之王