특정 그룹에 포함되는 사용자들에게 이용권을 일괄적으로 지급하려고 함

Tasklet execute 메소드의 return 타입

@Configuration
@RequiredArgsConstructor
public class AddPassesJobConfig {
private final AddPassesTasklet addPassesTasklet;
private final PlatformTransactionManager transactionManager;
private final JobRepository jobRepository;
@Bean
public Job addPassesJob(){
return new JobBuilder("addPassesJob",jobRepository)
.start(addPassesStep())
.build();
}
@Bean
public Step addPassesStep(){
return new StepBuilder("addPassesStep",jobRepository)
.tasklet(addPassesTasklet,transactionManager)
.build();
}
}
@Component
@RequiredArgsConstructor
@Slf4j
public class AddPassesTasklet implements Tasklet {
private final PassRepository passRepository;
private final BulkPassRepository bulkPassRepository;
private final UserGroupMappingRepository userGroupMappingRepository;
private final UserRepository userRepository;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
// 이용권 시작 일시 1일 전 user group 내 사용자에게 이용권을 추가해줌
final LocalDateTime startedAt = LocalDateTime.now().minusDays(1);
final List<BulkPassEntity> bulkPassEntities = bulkPassRepository.findByStatusAndStartedAtGreaterThan(BulkPassStatus.READY,startedAt);
int count = 0;
// 대량 이용권 정보를 돌면서 user group에 속한 userId를 조회하고 해당 userId로 이용권 추가
for(BulkPassEntity bulkPassEntity : bulkPassEntities){
final List<String> userIds = userGroupMappingRepository.findByUserGroupId(bulkPassEntity.getUserGroupId())
.stream()
.map(UserGroupMappingEntity::getUserId)
.collect(Collectors.toList());
count += addPasses(bulkPassEntity,userIds);
bulkPassEntity.addPassStatus();
}
log.info("AddPassesTasklet - execute: 이용권 {}건 추가 완료, startedAt={}",count,startedAt);
return RepeatStatus.FINISHED;
}
private int addPasses(BulkPassEntity bulkPassEntity,List<String> userIds){
List<UserEntity> users = userRepository.findAllById(userIds);
Map<String, UserEntity> userMap = users.stream()
.collect(Collectors.toMap(UserEntity::getUserId, Function.identity()));
List<PassEntity> passEntities = userIds.stream()
.map(userMap::get)
.filter(Objects::nonNull)
.map(
user -> PassEntity.of(
bulkPassEntity.getPackaze(),
user,
PassStatus.READY,
bulkPassEntity.getCount(),
bulkPassEntity.getStartedAt(),
bulkPassEntity.getEndedAt(),
PassType.GENERAL
))
.collect(Collectors.toList());
return passRepository.saveAll(passEntities).size();
}
}
@Slf4j
@ExtendWith(MockitoExtension.class)
public class AddPassesTaskletTest {
@Mock
private StepContribution stepContribution;
@Mock
private ChunkContext chunkContext;
@Mock
private PassRepository passRepository;
@Mock
private PackageRepository packageRepository;
@Mock
private BulkPassRepository bulkPassRepository;
@Mock
private UserRepository userRepository;
@Mock
private UserGroupMappingRepository userGroupMappingRepository;
//@InjectMocks 클래스의 인스턴스를 생성하고 @Mock 으로 생성된 객체를 주입
@InjectMocks
private AddPassesTasklet addPassesTasklet;
@Test
public void test_execute() throws Exception {
final String userGroupId = "GROUP";
final String userId = "A1000000";
final Integer packageSeq = 1;
final Integer count = 10;
final LocalDateTime now = LocalDateTime.now();
PackageEntity packageEntity = PackageEntity.of(
1,
"name",
10
);
UserEntity userEntity = UserEntity.of("A1000000");
final BulkPassEntity bulkPassEntity = BulkPassEntity.of(
1,
packageEntity,
userGroupId,
BulkPassStatus.READY,
count,
now,
now.plusDays(60)
);
final UserGroupMappingEntity userGroupMapping = UserGroupMappingEntity.of(
userGroupId,
userId
);
when(bulkPassRepository.findByStatusAndStartedAtGreaterThan(eq(BulkPassStatus.READY),any()))
.thenReturn(List.of(bulkPassEntity));
when(userGroupMappingRepository.findByUserGroupId(eq("GROUP")))
.thenReturn(List.of(userGroupMapping));
when(userRepository.findAllById(List.of(userId)))
.thenReturn(List.of(userEntity));
when(passRepository.saveAll(any())).thenAnswer(invocation -> invocation.getArgument(0));
RepeatStatus repeatStatus = addPassesTasklet.execute(stepContribution,chunkContext);
Assertions.assertEquals(RepeatStatus.FINISHED,repeatStatus);
ArgumentCaptor<List> passEntitiesCaptor = ArgumentCaptor.forClass(List.class);
verify(passRepository,times(1)).saveAll(passEntitiesCaptor.capture());
final List<PassEntity> passEntities = passEntitiesCaptor.getValue();
assertEquals(1, passEntities.size());
final PassEntity passEntity = passEntities.get(0);
assertEquals(packageSeq, passEntity.getPackaze().getPackageSeq());
assertEquals(userId, passEntity.getUser().getUserId());
assertEquals(PassStatus.READY, passEntity.getStatus());
assertEquals(count, passEntity.getRemainingCount();
}
}
- `@ExtendWith(MockitoExtension.class)` → 단위 테스트에 공통적으로 사용할 확장 기능을 선언, 개발자가 동작을 직접 제어할 수 있는 가짜(Mock) 객체를 지원하는 테스트 프레임워크
- `@InjectMocks` → `@Mock` 또는 `@Spy`로 생성된 가짜 객체를 자동으로 주입시켜주는 객체
- `StepContribution` → 아직 커밋되지 않은 현재 트랜잭션에 대한 정보
- `ChunkContext` → 실행 시점의 잡 상태 제공
- `ArgumentCaptor` → 메소드에 들어가는 인자값 검증
- `verify(passRepository,times(1)).saveAll(passEntitiesCaptor.capture())` → 1회만 실행되는지를 검증