JAVASpring

dbunit 在spring boot项目中 出现AmbiguousTableNameException

当我们在Java项目中,使用dbunit做UT测试时,有可能会发生下面AmbiguousTableNameException的错误。


UT测试类代码:

@SpringBootTest(classes = ServiceTestApplication.class)
@ActiveProfiles("test")
@TestExecutionListeners({ 
	DependencyInjectionTestExecutionListener.class,
	DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
	MockitoTestExecutionListener.class,
	DbUnitTestExecutionListener.class })
@ComponentScan(value = "com.xxxx")
@AutoConfigureMockMvc
public class TestControllerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	@DatabaseSetup(value = { "classpath:com/test_102N.xls" })
	@DatabaseTearDown(value = { "classpath:databaseTearDown/DatabaseTearDown.xls" })
	void test_102N() throws Exception {

		// ヘッダーの情報の設定
		org.springframework.http.HttpHeaders headers = new HttpHeaders();
		headers.add("x-user-id", "123");
		headers.add("x-system-id", "456");

		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
		params.put("testKbn", Collections.singletonList("1"));

		// リクエストを実行する
		MvcResult mvcResult = mockMvc.perform(get("/test").queryParams(params).headers(headers)).andReturn();

		String actualContent = mvcResult.getResponse().getContentAsString(Charset.forName("UTF-8"));
		// リターンステータスが正しいかどうかを判断する
		int status = mvcResult.getResponse().getStatus();
		assertEquals(HttpStatus.OK.value(), status);
	}

}

application-test.yaml

注意:这里用jdbc:postgresql://localhost:5432/postgres?currentSchema=ut_db 指定连接的Schema不起作用

spring.datasource:
  url: jdbc:postgresql://localhost:5432/postgres
  username: dummy
  password: dummyvalue
  driver-class-name: org.postgresql.Driver

spring.main.allow-circular-references: true

logging:
  level:
    com.jtb.nucleus: debug

异常信息:

分析原因


当一个实例下存在多个数据库,而且这个数据库中存在相同名称的表,Dbunit会报一个不能区分是哪个表的错误。

在数据库中存在两个同名的数据表,同名->不区分大小写的同名,比如user和USER也是同名的,特别的是这两个同名数据表可能是存在本地中两个不同的Schema中。

在没有指定Schema的情况下,DbUnit会默认扫描整个实例。

解决方法


  • 重命名你当前项目的数据表,使其不与数据库中其他数据表重名,如user命名为t_user。
  • 删除其他数据库中的重名数据表,使你当前数据表命名唯一。
  • 假如在连接数据库时未指定到哪个具体的schema,那么就指定schema。

指定schema的例子


创建db连接Configuration类(注意TestConfiguration注解,因为该配置只想在Test类有效)

package com.xxx.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.springtestdbunit.bean.DatabaseConfigBean;
import com.github.springtestdbunit.bean.DatabaseDataSourceConnectionFactoryBean;


//@Configuration
@TestConfiguration
public class DbUnitConfig {

    @Bean("dbUnitConnection")
    DatabaseDataSourceConnectionFactoryBean getTestConnection(DataSource dataSource) {
        DatabaseDataSourceConnectionFactoryBean bean = new DatabaseDataSourceConnectionFactoryBean();
        bean.setDataSource(dataSource);
        DatabaseConfigBean databaseConfigBean = new DatabaseConfigBean();
        databaseConfigBean.setAllowEmptyFields(true);
        bean.setDatabaseConfig(databaseConfigBean);
        bean.setSchema("ut_db");

        return bean;
    }
}

在测试类通过DbUnitConfiguration指定数据源

@SpringBootTest(classes = ServiceTestApplication.class)
@ActiveProfiles("test")
@TestExecutionListeners({ 
	DependencyInjectionTestExecutionListener.class,
	DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
	MockitoTestExecutionListener.class,
	DbUnitTestExecutionListener.class })
@ComponentScan(value = "com.xxxx")
@AutoConfigureMockMvc
@DbUnitConfiguration(dataSetLoader = XlsDataSetLoader.class, databaseConnection = { "dbUnitConnection" })

public class TestControllerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	@DatabaseSetup(value = { "classpath:com/test_102N.xls" })
	@DatabaseTearDown(value = { "classpath:databaseTearDown/DatabaseTearDown.xls" })
	void test_102N() throws Exception {

		// ヘッダーの情報の設定
		org.springframework.http.HttpHeaders headers = new HttpHeaders();
		headers.add("x-user-id", "123");
		headers.add("x-system-id", "456");

		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
		params.put("testKbn", Collections.singletonList("1"));

		// リクエストを実行する
		MvcResult mvcResult = mockMvc.perform(get("/test").queryParams(params).headers(headers)).andReturn();

		String actualContent = mvcResult.getResponse().getContentAsString(Charset.forName("UTF-8"));
		// リターンステータスが正しいかどうかを判断する
		int status = mvcResult.getResponse().getStatus();
		assertEquals(HttpStatus.OK.value(), status);
	}

}

补充


另外,DbUnitConfig作为TestConfiguration,会在测试类启动时被扫描加载,会创建 @Bean(“dbUnitConnection”),但是这个Bean需要创建一个DataSource dataSource连接,但是假如我们的测试工程不需要连接数据库,而没有设置数据库的配置,启动测试类时会导致出下面的错误。

Description:

Parameter 0 of method getTestConnection in DbUnitConfig required a bean of type 'javax.sql.DataSource' that could not be found.


Action:

Consider defining a bean of type 'javax.sql.DataSource' in your configuration.

解决方法是我们可以在测试类加上,来mock一个DataSource

@MockBean
DataSource dataSource;

完整代码

@SpringBootTest(classes = ServiceTestApplication.class)
@ActiveProfiles("test")
@TestExecutionListeners({ 
	DependencyInjectionTestExecutionListener.class,
	DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
	MockitoTestExecutionListener.class,
	DbUnitTestExecutionListener.class })
@ComponentScan(value = "com.xxxx")
@AutoConfigureMockMvc
@DbUnitConfiguration(dataSetLoader = XlsDataSetLoader.class, databaseConnection = { "dbUnitConnection" })

public class TestControllerTest {

	// 这部分很重要
 @MockBean
	DataSource dataSource;

	@Autowired
	private MockMvc mockMvc;

	@Test
	@DatabaseSetup(value = { "classpath:com/test_102N.xls" })
	@DatabaseTearDown(value = { "classpath:databaseTearDown/DatabaseTearDown.xls" })
	void test_102N() throws Exception {

		// ヘッダーの情報の設定
		org.springframework.http.HttpHeaders headers = new HttpHeaders();
		headers.add("x-user-id", "123");
		headers.add("x-system-id", "456");

		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
		params.put("testKbn", Collections.singletonList("1"));

		// リクエストを実行する
		MvcResult mvcResult = mockMvc.perform(get("/test").queryParams(params).headers(headers)).andReturn();

		String actualContent = mvcResult.getResponse().getContentAsString(Charset.forName("UTF-8"));
		// リターンステータスが正しいかどうかを判断する
		int status = mvcResult.getResponse().getStatus();
		assertEquals(HttpStatus.OK.value(), status);
	}

}