我一直在做一个演示 Spring Boot 项目,它提供了一个 REST API 来对一些实体类执行 CRUD 操作。在开发此 REST API 期间,我还创建了单元和集成测试以确保流程按预期工作。在集成测试阶段,我认为最好创建一个单独的数据库来测试在测试配置文件上运行,而不是使用相同的开发数据库。但是,尝试设置集成测试以使用测试数据库并对测试数据库执行 CRUD 操作对我来说是有问题的。 更具体地说,这是导致问题的属性文件和集成测试类:
--- src/main/resources/application.properties ---
spring.datasource.url=jdbc:postgresql://localhost:5432/employee-management-system
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:data-dev.sql
--- src/test/resources/application-test.properties ---
spring.datasource.url=jdbc:h2:mem:employee-management-system-test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.sql=DEBUG
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema-test.sql
--- EmployeeRestControllerIntegrationTest.java ---
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Sql(scripts = {"classpath:data-test.sql"})
class EmployeeRestControllerIntegrationTest {
@Autowired
private TestRestTemplate template;
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private ObjectMapper objectMapper;
@Spy
private ModelMapper modelMapper;
private EmployeeDto employeeDto1;
private EmployeeDto employeeDto2;
private List<EmployeeDto> employeeDtos;
@BeforeEach
void setUp() {
employeeDto1 = modelMapper.map(getMockedEmployee1(), EmployeeDto.class);
employeeDto2 = modelMapper.map(getMockedEmployee2(), EmployeeDto.class);
employeeDtos = modelMapper.map(getMockedEmployees(), new TypeToken<List<EmployeeDto>>() {}.getType());
}
@AfterEach
void tearDown() {
employeeRepository.deleteAll();
}
@Test
void getAllEmployees_shouldReturnListOfEmployees() throws Exception {
ResponseEntity<String> response = template.getForEntity("/api/employees", String.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
List<EmployeeDto> result = objectMapper.readValue(response.getBody(), new TypeReference<>() {});
Collections.reverse(employeeDtos);
assertThat(result).isEqualTo(employeeDtos);
}
@Test
void getEmployeeById_withValidId_shouldReturnEmployeeWithGivenId() {
ResponseEntity<EmployeeDto> response = template.getForEntity("/api/employees/1", EmployeeDto.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
assertThat(response.getBody()).isEqualTo(employeeDto1);
}
@Test
void getEmployeeById_withInvalidId_shouldThrowException() {
Long id = 999L;
ResponseEntity<String> response = template.getForEntity("/api/employees/999", String.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
assertThat(response.getBody()).isEqualTo("Resource not found: " + String.format(EMPLOYEE_NOT_FOUND, id));
}
@Test
void saveEmployee_shouldAddEmployeeToList() {
ResponseEntity<EmployeeDto> response = template.postForEntity("/api/employees", employeeDto1, EmployeeDto.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
assertThat(response.getBody()).isEqualTo(employeeDto1);
}
@Test
void updateEmployeeById_withValidId_shouldUpdateEmployeeWithGivenId() {
Long id = 1L;
EmployeeDto employeeDto = employeeDto2;
employeeDto.setId(id);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(APPLICATION_JSON);
ResponseEntity<EmployeeDto> response = template.exchange("/api/employees/1", HttpMethod.PUT, new HttpEntity<>(employeeDto2, headers), EmployeeDto.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
assertThat(response.getBody()).isEqualTo(employeeDto);
}
@Test
void updateEmployeeById_withInvalidId_shouldThrowException() {
Long id = 999L;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(APPLICATION_JSON);
ResponseEntity<String> response = template.exchange("/api/employees/999", HttpMethod.PUT, new HttpEntity<>(employeeDto2, headers), String.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
assertThat(response.getBody()).isEqualTo("Resource not found: " + String.format(EMPLOYEE_NOT_FOUND, id));
}
@Test
void deleteEmployeeById_withValidId_shouldRemoveEmployeeWithGivenIdFromList() {
ResponseEntity<Void> response = template.exchange("/api/employees/1", HttpMethod.DELETE, new HttpEntity<>(null), Void.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
ResponseEntity<Void> getResponse = template.exchange("/api/employees/1", HttpMethod.GET, null, Void.class);
assertNotNull(getResponse);
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
ResponseEntity<Void> getAllResponse = template.exchange("/api/employee", HttpMethod.GET, null, Void.class);
assertNotNull(getAllResponse);
}
@Test
void deleteEmployeeById_withInvalidId_shouldThrowException() {
Long id = 999L;
ResponseEntity<String> response = template.exchange("/api/employees/999", HttpMethod.DELETE, new HttpEntity<>(null), String.class);
assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
assertThat(response.getBody()).isEqualTo("Resource not found: " + String.format(EMPLOYEE_NOT_FOUND, id));
}
}
事实上,当我运行测试时:
之后,应用程序使用以下堆栈跟踪抛出此异常:
org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #8 of class path resource [data-test.sql]: INSERT INTO employees_experiences(employee_id, experience_id) VALUES (1, 1), (1, 2), (2, 3), (2, 4), (3, 1), (3, 2), (4, 3), (4, 4), (5, 1), (5, 2), (6, 3), (6, 4), (7, 1), (7, 2), (8, 3), (8, 4), (9, 1), (9, 2), (10, 3), (10, 4), (11, 1), (11, 2), (12, 3), (12, 4), (13, 1), (13, 2), (14, 3), (14, 4), (15, 1), (15, 2), (16, 3), (16, 4), (17, 1), (17, 2), (18, 3), (18, 4), (19, 1), (19, 2), (20, 3), (20, 4)com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FK9KRGPGEGMHONO5TCMUS7LBLJY: PUBLIC.EMPLOYEES_EXPERIENCES FOREIGN KEY(EMPLOYEE_ID) REFERENCES PUBLIC.EMPLOYEES(ID) (1)"; SQL statement:
INSERT INTO employees_experiences(employee_id, experience_id) VALUES (1, 1), (1, 2), (2, 3), (2, 4), (3, 1), (3, 2), (4, 3), (4, 4), (5, 1), (5, 2), (6, 3), (6, 4), (7, 1), (7, 2), (8, 3), (8, 4), (9, 1), (9, 2), (10, 3), (10, 4), (11, 1), (11, 2), (12, 3), (12, 4), (13, 1), (13, 2), (14, 3), (14, 4), (15, 1), (15, 2), (16, 3), (16, 4), (17, 1), (17, 2), (18, 3), (18, 4), (19, 1), (19, 2), (20, 3), (20, 4)
我知道这个问题与表 employees_experiences 中的外键约束违规有关(因为 Employee 和 Experience 实体具有多对多关系而创建的映射表)。 即便如此,我不知道应该做些什么来解决这个问题并使测试通过。任何关于如何解决我应该考虑的问题或文章/视频的建议或建议将不胜感激。另外,请告诉我是否应该提供更多项目代码。