Spring Batch Job
- 아래와 같이 Job 을 구현했다고 했을때, 아래의 코드를 테스트해보려고 한다.
- 아래의 Job 은 AptNotificationRepository 에서 Apt 의 조건에 따라 메일로 공지를 보내는 Job 이다.
@Configuration
@RequiredArgsConstructor
@Slf4j
public class AptNotificationJobConfig {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job aptNotificationJob(Step aptNotificationStep) {
return jobBuilderFactory.get("aptNotificationJob")
.incrementer(new RunIdIncrementer())
.validator(new DealDateParameterValidator())
.start(aptNotificationStep)
.build();
}
@JobScope
@Bean
public Step aptNotificationStep(RepositoryItemReader<AptNotification> aptNotificationRepositoryItemReader,
ItemProcessor<AptNotification, NotificationDto> aptNotificationProcessor,
ItemWriter<NotificationDto> aptNotificationWriter
) {
return stepBuilderFactory.get("aptNotificationStep")
.<AptNotification, NotificationDto>chunk(10)
.reader(aptNotificationRepositoryItemReader)
.processor(aptNotificationProcessor)
.writer(aptNotificationWriter)
.build();
}
@StepScope
@Bean
public RepositoryItemReader<AptNotification> aptNotificationRepositoryItemReader(AptNotificationRepository aptNotificationRepository) {
return new RepositoryItemReaderBuilder<AptNotification>()
.name("aptNotificationRepositoryItemReader")
.repository(aptNotificationRepository)
.methodName("findByEnabledIsTrue")
.pageSize(10)
.arguments(Arrays.asList())
.sorts(Collections.singletonMap("aptNotificationId", Sort.Direction.DESC))
.build();
}
@StepScope
@Bean
public ItemProcessor<AptNotification, NotificationDto> aptNotificationProcessor(
@Value("#{jobParameters['dealDate']}") String dealDate,
AptDealService aptDealService,
LawdRepository lawdRepository
) {
return aptNotification -> {
List<AptDto> aptDtoList = aptDealService.findByGuLawdCdAndDealDate(aptNotification.getGuLawdCd(), LocalDate.parse(dealDate));
if (aptDtoList.isEmpty()) {
return null;
}
String guName = lawdRepository.findByLawdCd(aptNotification.getGuLawdCd() + "00000")
.orElseThrow().getLawdDong();
return NotificationDto.builder()
.email(aptNotification.getEmail())
.guName(guName)
.count(aptDtoList.size())
.aptDeals(aptDtoList)
.build();
};
}
@StepScope
@Bean
public ItemWriter<NotificationDto> aptNotificationWriter(EmailSendService emailSendService) {
return items -> items.forEach(item -> emailSendService.send(item.getEmail(), item.toMessage()));
}
}
테스트 코드
Batch Test Config
- 아래와 같이 BatchTest Config 파일을 구성한다.
@Configuration
@EnableBatchProcessing
@EnableAutoConfiguration
public class BatchTestConfig {
}
Test 코드 작성
@SpringBatchTest
@SpringBootTest
@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
@ContextConfiguration(classes = {AptNotificationJobConfig.class, BatchTestConfig.class})
public class AptNotificationJobConfigTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private AptNotificationRepository aptNotificationRepository;
@MockBean
private AptDealService aptDealService;
@MockBean
private LawdRepository lawdRepository;
@MockBean
private EmailSendService emailSendService;
@AfterEach
public void tearDown() {
aptNotificationRepository.deleteAll();
}
@Test
public void success() throws Exception {
// given
LocalDate dealDate = LocalDate.now().minusDays(1);
String guLawdCd = "11110";
String email = "abc@gmail.com";
String anotherEmail = "efg@gmail.com";
givenAptNotification(guLawdCd, email, true);
givenAptNotification(guLawdCd, anotherEmail, false);
givenLawdCd(guLawdCd);
givenAptDeal(guLawdCd, dealDate);
// when
JobExecution jobExecution = jobLauncherTestUtils.launchJob(
new JobParameters(Maps.newHashMap("dealDate", new JobParameter(dealDate.toString())))
);
// then
assertEquals(jobExecution.getExitStatus(), ExitStatus.COMPLETED);
verify(emailSendService, times(1)).send(eq(email), anyString());
verify(emailSendService, never()).send(eq(anotherEmail), anyString());
}
private void givenAptNotification(String guLawdCd, String email, boolean enabled) {
AptNotification notification = new AptNotification();
notification.setEmail(email);
notification.setGuLawdCd(guLawdCd);
notification.setEnabled(enabled);
notification.setCreatedAt(LocalDateTime.now());
notification.setUpdatedAt(LocalDateTime.now());
aptNotificationRepository.save(notification);
}
private void givenLawdCd(String guLawdCd) {
String lawdCd = guLawdCd + "00000";
Lawd lawd = new Lawd();
lawd.setLawdCd(lawdCd);
lawd.setLawdDong("경기도 성남시 분당구");
lawd.setExist(true);
lawd.setCreatedAt(LocalDateTime.now());
lawd.setUpdatedAt(LocalDateTime.now());
when(lawdRepository.findByLawdCd(lawdCd))
.thenReturn(Optional.of(lawd));
}
private void givenAptDeal(String guLawdCd, LocalDate dealDate) {
when(aptDealService.findByGuLawdCdAndDealDate(guLawdCd, dealDate))
.thenReturn(Arrays.asList(
new AptDto("IT아파트", 2000000000L),
new AptDto("탄천아파트", 1500000000L)
));
}
}
@SpringBatchTest 를 설정해주면 아래와 같은 빈을 자동으로 주입해준다.
- JobLauncherTestUtils
- launcherJob()/launcherStep() 같은 스프링 배치 테스트에 필요한 유틸성 메서드를 지원받는다.
- JobRepositoryTestUtils
- JobRepository 를 사용해서 jobExecution 을 생성 및 삭제 기능 메소드를 지원한다.
- StepScopeTestExecutionListener
- @StepScope 컨텍스트를 생성해준다.
- 해당 컨텍스트를 통해 JobParameter 등을 단위 테스트에서 DI 가능하다.
- JobScopeTestExecutionLister
테스트 하고자 하는 Job 에서 사용되는 아래와 같은 Component 들을 MockBean 으로 설정해 준다.
- AptNotificationRepository
- AptDealService
- LawdRepository
- FakeSendService
아래의 메소드에서 Stubbing 처리한다.
- givenLawdCd(String guLawdCd)
- givenAptDeal(String guLawdCd, LocalDate dealDate)
JobLauncherTestUtils 를 통해서 Job 을 실행한다.
- Program arguments 로 설정했던, JobParameters 에 Map 형태로 JobParameter 를 넘겨줘서 Job 을 실행시킬 수 있다.
검증
- assertEquals(jobExecution.getExitStatus(), ExitStatus.COMPLETED)
- MockBean 으로 Stubbing 했던 로직이 정상적으로 실행됐는지 검증한다.
- verify(fakeSendService, times(1)).send(eq(email), anyString());
- verify(fakeSendService, never()).send(eq(anotherEmail), anyString());
REFERENCES