모르지 않다는 것은 아는것과 다르다.

Junit

SpringBatch Test

채마스 2022. 4. 2. 11:16

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
    • @JobScope 컨텍스트를 생성해준다.

 

테스트 하고자 하는 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)
    • Job 의 상태를 검증한다.
  • MockBean 으로 Stubbing 했던 로직이 정상적으로 실행됐는지 검증한다.
    • verify(fakeSendService, times(1)).send(eq(email), anyString());
    • verify(fakeSendService, never()).send(eq(anotherEmail), anyString());

 

 

REFERENCES

  • 황지연님의 스프링 배치

'Junit' 카테고리의 다른 글

파라미터를 사용해서 테스트하기  (0) 2022.02.27
테스트 더블  (0) 2022.02.27
WebMvc Test  (0) 2022.02.27
단위테스트 vs 통합테스트  (0) 2022.02.27
Service 단위테스트  (0) 2022.02.27