Spring 测试因@Scheduled Task:JdbcSQLSyntaxErrorException表“而失败;用户“U账户”创建“U事件”;找不到 总结&第一个问题
我正在尝试测试我的用户注册机制。通过我的REST API创建新用户帐户时,数据库中会存储一个Spring 测试因@Scheduled Task:JdbcSQLSyntaxErrorException表“而失败;用户“U账户”创建“U事件”;找不到 总结&第一个问题,spring,hibernate,spring-boot,spring-data-jpa,spring-test,Spring,Hibernate,Spring Boot,Spring Data Jpa,Spring Test,我正在尝试测试我的用户注册机制。通过我的REST API创建新用户帐户时,数据库中会存储一个UserAccountCreatedEvent。计划任务每5秒检查一次数据库,查看是否有新的UserAccountCreatedEvents,如果有,则向注册用户发送电子邮件。在运行测试时,我遇到了一个问题,即找不到UserAccountCreatedEvent的表(请参见下面的异常)。我曾经在服务方法中以阻塞方式发送电子邮件,但最近我切换到了这种异步方法。我所有的测试对于阻塞方法都非常有效,对于异步方法
UserAccountCreatedEvent
。计划任务每5秒检查一次数据库,查看是否有新的UserAccountCreatedEvent
s,如果有,则向注册用户发送电子邮件。在运行测试时,我遇到了一个问题,即找不到UserAccountCreatedEvent
的表(请参见下面的异常)。我曾经在服务方法中以阻塞方式发送电子邮件,但最近我切换到了这种异步方法。我所有的测试对于阻塞方法都非常有效,对于异步方法,我唯一改变的就是在测试中包含waitibility
2019-04-23 11:24:51.605 ERROR 7968 --- [taskScheduler-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [select useraccoun0_.id as id1_0_, useraccoun0_.completed_at as complete2_0_, useraccoun0_.created_at as created_3_0_, useraccoun0_.in_process_since as in_proce4_0_, useraccoun0_.status as status5_0_, useraccoun0_.user_id as user_id1_35_ from user_account_created_event useraccoun0_ where useraccoun0_.status=? order by useraccoun0_.created_at asc limit ?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException:
Table "USER_ACCOUNT_CREATED_EVENT" not found; SQL statement:
select useraccoun0_.id as id1_0_, useraccoun0_.completed_at as complete2_0_, useraccoun0_.created_at as created_3_0_, useraccoun0_.in_process_since as in_proce4_0_, useraccoun0_.status as status5_0_, useraccoun0_.user_id as user_id1_35_ from user_account_created_event useraccoun0_ where useraccoun0_.status=? order by useraccoun0_.created_at asc limit ? [42102-199]
第二个问题 似乎这还不够,在调试模式下运行测试时,测试的行为完全不同。当我在方法中设置断点时,该方法调用的断点被注释为
@Scheduled
,它会被调用多次,尽管@Scheduled
配置了5000ms的fixedDelayString
(固定延迟)。多亏了日志记录,我甚至可以看到发送了几封邮件。不过,我的测试SMTP服务器(GreenMail
)没有收到任何电子邮件。这怎么可能呢?我特意将事务隔离设置为isolation.SERIALIZABLE
,这样就不可能(就我所理解的事务隔离而言)两个计划的方法从数据库访问同一事件
第三个问题 最重要的是,当我重新运行失败的测试时,它们是有效的。但是,控制台上存在不同的异常(请参见下文)。但是,应用程序启动,测试成功完成。根据我是否运行所有测试、是否只运行类、是否只运行方法以及是否重新运行失败的测试,会有不同的测试结果。我不明白这种不确定的行为怎么可能发生
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: Failed to scan classpath for unlisted entity classes
Caused by: java.nio.channels.ClosedByInterruptException: null
我的代码 测试类(
UserRegistrationTest
)
计划方法(RegistrationTask
)调用的代码
UserAccountCreatedEvent
application.yml
我不熟悉Spring的日程安排,因此非常感谢您的帮助
@ActiveProfiles("test")
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public class UserRegistrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private Routes routes;
@Autowired
private TestConfig testConfig;
@Resource(name = "validCustomerDTO")
private CustomerDTO validCustomerDTO;
@Resource(name = "validVendorDTO")
private VendorRegistrationDTO validVendorRegistrationDTO;
@Value("${schedule.sendRegistrationConfirmationEmailTaskDelay}")
private Short registrationConfirmationEmailSenderTaskDelay;
private GreenMail smtpServer;
// Setup & tear down
@Before
public void setUp() {
smtpServer = testConfig.getMailServer();
smtpServer.start();
}
@After
public void tearDown() {
smtpServer.stop();
}
// Tests
@Test
public void testCreateCustomerAccount() throws Exception {
mockMvc.perform(
post(routes.getCustomerPath())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(validCustomerDTO)))
.andExpect(status().isCreated());
// When run normally, I get a timeout from the next line
await().atMost(registrationConfirmationEmailSenderTaskDelay + 10000, MILLISECONDS).until(smtpServerReceivedOneEmail());
// Verify correct registration confirmation email was sent
MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
// other checks
// ...
}
@Test
public void testCreateVendorAccount() throws Exception {
mockMvc.perform(
post(routes.getVendorPath())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(validVendorRegistrationDTO)))
.andExpect(status().isCreated());
// When run normally, I get a timeout from the next line
await().atMost(registrationConfirmationEmailSenderTaskDelay + 10000, MILLISECONDS).until(smtpServerReceivedOneEmail());
// Verify correct registration confirmation email was sent
MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
// other checks
// ...
}
// Helper methods
private Callable<Boolean> smtpServerReceivedOneEmail() {
return () -> smtpServer.getReceivedMessages().length == 1;
}
// Test configuration
@TestConfiguration
static class TestConfig {
private static final int PORT = 3025;
private static final String HOST = "localhost";
private static final String PROTOCOL = "smtp";
GreenMail getMailServer() {
return new GreenMail(new ServerSetup(PORT, HOST, PROTOCOL));
}
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(HOST);
javaMailSender.setPort(PORT);
javaMailSender.setProtocol(PROTOCOL);
javaMailSender.setDefaultEncoding("UTF-8");
return javaMailSender;
}
}
@Component
public class BusinessTaskScheduler {
private final RegistrationTask registrationTask;
@Autowired
public BusinessTaskScheduler(RegistrationTask registrationTask) {
this.registrationTask = registrationTask;
}
@Scheduled(fixedDelayString = "${schedule.sendRegistrationConfirmationEmailTaskDelay}")
public void sendRegistrationConfirmationEmail() {
registrationTask.sendRegistrationConfirmationEmail();
}
}
@Component
@Transactional(isolation = Isolation.SERIALIZABLE)
public class RegistrationTask {
private final EmailHelper emailHelper;
private final EventService eventService;
private final UserRegistrationService userRegistrationService;
@Autowired
public RegistrationTask(EmailHelper emailHelper, EventService eventService, UserRegistrationService userRegistrationService) {
this.emailHelper = emailHelper;
this.eventService = eventService;
this.userRegistrationService = userRegistrationService;
}
public void sendRegistrationConfirmationEmail() {
Optional<UserAccountCreatedEvent> optionalEvent = eventService.getOldestUncompletedUserAccountCreatedEvent();
if (optionalEvent.isPresent()) {
UserAccountCreatedEvent event = optionalEvent.get();
User user = event.getUser();
RegistrationVerificationToken token = userRegistrationService.createRegistrationVerificationTokenForUser(user);
emailHelper.sendRegistrationConfirmationEmail(token);
eventService.completeEvent(event);
}
}
}
@Service
@Transactional(isolation = Isolation.SERIALIZABLE)
public class EventServiceImpl implements EventService {
private final ApplicationEventDAO applicationEventDAO;
private final UserAccountCreatedEventDAO userAccountCreatedEventDAO;
@Autowired
public EventServiceImpl(ApplicationEventDAO applicationEventDAO, UserAccountCreatedEventDAO userAccountCreatedEventDAO) {
this.applicationEventDAO = applicationEventDAO;
this.userAccountCreatedEventDAO = userAccountCreatedEventDAO;
}
@Override
public void completeEvent(ApplicationEvent event) {
if (!event.getStatus().equals(COMPLETED) && Objects.isNull(event.getCompletedAt())) {
event.setStatus(COMPLETED);
event.setCompletedAt(LocalDateTime.now());
applicationEventDAO.save(event);
}
}
@Override
public Optional<UserAccountCreatedEvent> getOldestUncompletedUserAccountCreatedEvent() {
Optional<UserAccountCreatedEvent> optionalEvent = userAccountCreatedEventDAO.findFirstByStatusOrderByCreatedAtAsc(NEW);
if (optionalEvent.isPresent()) {
UserAccountCreatedEvent event = optionalEvent.get();
setEventInProcess(event);
return Optional.of(userAccountCreatedEventDAO.save(event));
}
return Optional.empty();
}
@Override
public void publishEvent(ApplicationEvent event) {
applicationEventDAO.save(event);
}
// Helper methods
private void setEventInProcess(ApplicationEvent event) {
event.setStatus(Status.IN_PROCESS);
event.setInProcessSince(LocalDateTime.now());
}
}
schedule:
sendRegistrationConfirmationEmailTaskDelay: 5000 # delay between tasks in milliseconds