From pg-backend-skills
IROVEN 백엔드 테스트 작성 표준. Use when writing test code, choosing test strategies, generating test data, or verifying test coverage.
How this skill is triggered — by the user, by Claude, or both
Slash command
/pg-backend-skills:test-writingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
IROVEN 백엔드 팀의 테스트 작성 표준을 정의합니다.
IROVEN 백엔드 팀의 테스트 작성 표준을 정의합니다.
@MockitoBean 사용, @MockBean deprecated)MockMvcTester (AssertJ 스타일) 또는 RestTestClient 권장@ActiveProfiles("test")
application-test.yml:
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
flyway:
enabled: false
핵심: H2 인메모리 DB + Flyway 비활성화 + create-drop
API 엔드포인트? → @WebMvcTest (Controller 테스트)
Repository/QueryDSL? → @DataJpaTest (Repository 테스트)
Service 비즈니스 로직? → Mockito 단위 테스트 (기본 선택)
여러 서비스 통합? → @SpringBootTest (최소화)
도메인 로직 (POJO)? → 순수 JUnit 테스트
기본은 Mockito 단위 테스트, 꼭 필요할 때만 통합 테스트
통합 테스트가 필요한 경우:
위 조건 모두 해당 안 되면 → Mockito 단위 테스트
모든 테스트는 반드시 Given-When-Then 구조:
@Test
@DisplayName("파일 업로드 - 성공")
void uploadFile_ValidFile_Success() {
// given - 테스트 준비
MultipartFile file = new MockMultipartFile("file", "test.jpg", "image/jpeg", "content".getBytes());
when(storageService.upload(any(), any())).thenReturn("s3://bucket/test.jpg");
// when - 실행
FileInfo result = fileService.uploadFile(file, "uploads");
// then - 검증
assertThat(result).isNotNull();
assertThat(result.getFileName()).isEqualTo("test.jpg");
verify(storageService, times(1)).upload(any(), any());
}
class FileServiceTest // Service 단위 테스트
class FileControllerTest // Controller 테스트
class FileInfoRepositoryTest // Repository 테스트
class FileServiceIntegrationTest // 통합 테스트
class FileInfoTest // 도메인/Entity 테스트
// 패턴: {메서드명}_{시나리오}_{예상결과}
void uploadFile_ValidFile_Success()
void uploadFile_EmptyFile_ThrowsException()
void getFileInfo_FileNotFound_ThrowsFileNotFoundException()
@ExtendWith(MockitoExtension.class)
@DisplayName("FileService 단위 테스트")
class FileServiceTest {
@Mock
private FileInfoRepository fileInfoRepository;
@Mock
private StorageService storageService;
@InjectMocks
private FileService fileService;
@Test
@DisplayName("파일 정보 조회 - 성공")
void getFileInfo_ValidId_Success() {
// given
Long fileId = 1L;
FileInfo fileInfo = createFileInfo(fileId, "test.jpg");
when(fileInfoRepository.findById(fileId)).thenReturn(Optional.of(fileInfo));
when(storageService.getFileUrl(any())).thenReturn("https://s3.amazonaws.com/test.jpg");
// when
FileInfoResponse response = fileService.getFileInfo(fileId);
// then
assertThat(response).isNotNull();
assertThat(response.fileName()).isEqualTo("test.jpg");
verify(fileInfoRepository, times(1)).findById(fileId);
}
@Test
@DisplayName("파일 정보 조회 - 파일 없음 예외")
void getFileInfo_FileNotFound_ThrowsException() {
// given
Long fileId = 999L;
when(fileInfoRepository.findById(fileId)).thenReturn(Optional.empty());
// when & then
assertThatThrownBy(() -> fileService.getFileInfo(fileId))
.isInstanceOf(FileNotFoundException.class)
.hasMessageContaining("파일을 찾을 수 없습니다");
}
}
@ActiveProfiles("test")
@WebMvcTest(FileController.class)
@DisplayName("FileController 테스트")
class FileControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private JsonMapper jsonMapper; // Jackson 3: ObjectMapper → JsonMapper
@MockitoBean // Spring Boot 4: @MockBean → @MockitoBean
private FileService fileService;
@Test
@DisplayName("파일 업로드 API - 성공")
void uploadFile_ValidRequest_Returns200() throws Exception {
// given
MockMultipartFile file = new MockMultipartFile(
"file", "test.jpg", "image/jpeg", "content".getBytes());
when(fileService.uploadFileAndGetId(any(), any())).thenReturn(1L);
// when & then
mockMvc.perform(multipart("/file")
.file(file)
.param("folder", "uploads"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").value(1));
}
@Test
@DisplayName("파일 정보 조회 API - 404")
void getFileInfo_NotFound_Returns404() throws Exception {
// given
when(fileService.getFileInfo(999L))
.thenThrow(new FileNotFoundException("파일을 찾을 수 없습니다."));
// when & then
mockMvc.perform(get("/file/{fileId}", 999L))
.andExpect(status().isNotFound());
}
}
@ActiveProfiles("test")
@DataJpaTest
@DisplayName("FileInfoRepository 테스트")
class FileInfoRepositoryTest {
@Autowired
private FileInfoRepository fileInfoRepository;
@Test
@DisplayName("파일 저장 및 조회 - 성공")
void save_AndFindById_Success() {
// given
FileInfo fileInfo = FileInfo.of("test.jpg", "uuid_test.jpg", "jpg", 1024L, "uploads/uuid_test.jpg");
// when
FileInfo saved = fileInfoRepository.save(fileInfo);
Optional<FileInfo> found = fileInfoRepository.findById(saved.getId());
// then
assertThat(found).isPresent();
assertThat(found.get().getFileName()).isEqualTo("test.jpg");
assertThat(found.get().getFileSize()).isEqualTo(1024L);
}
}
// 단일 값
assertThat(response).isNotNull();
assertThat(response.fileName()).isEqualTo("test.jpg");
assertThat(response.fileSize()).isGreaterThan(0L);
// 컬렉션
assertThat(files).hasSize(3);
assertThat(files).extracting(FileInfo::getFileName)
.containsExactlyInAnyOrder("a.jpg", "b.jpg", "c.jpg");
// Boolean
assertThat(result.isPresent()).isTrue();
// 예외
assertThatThrownBy(() -> fileService.getFileInfo(999L))
.isInstanceOf(FileNotFoundException.class)
.hasMessageContaining("파일을 찾을 수 없습니다");
// Optional
assertThat(result).isPresent();
assertThat(result).isEmpty();
// 반환값 설정
when(repository.findById(1L)).thenReturn(Optional.of(entity));
when(storageService.upload(any(), any())).thenReturn("path");
// void 메서드
doNothing().when(storageService).delete(any());
// 예외 발생
when(repository.findById(999L)).thenThrow(new FileNotFoundException("not found"));
// 호출 검증
verify(repository, times(1)).save(any());
verify(storageService, never()).delete(any());
// 인자 검증
verify(repository).save(argThat(fileInfo ->
fileInfo.getFileName().equals("test.jpg")));
공통
@ActiveProfiles("test") 적용되었는가?@DisplayName으로 테스트 의도가 명확한가?{메서드}_{시나리오}_{결과} 패턴인가?Service 단위 테스트
@ExtendWith(MockitoExtension.class) 사용하는가?@Mock + @InjectMocks 패턴인가?verify()로 호출 검증하는가?Controller 테스트
@WebMvcTest(TargetController.class) 명시했는가?@MockitoBean인가? (@MockBean은 deprecated)Repository 테스트
@DataJpaTest 사용하는가?통합 테스트 (사용 시)
@MockitoBean인가?@SpringBootTest 사용 시 @AutoConfigureMockMvc 명시했는가? (Boot 4에서 필수)npx claudepluginhub seonhyeokjun/pg-ai-skills --plugin pg-backend-skillsGenerates JUnit 5 unit tests with Mockito and Testcontainers integration tests for Java services, repositories, controllers, and utilities. Auto-detects Maven/Gradle/Spring Boot setup from build files.
Test Java applications - JUnit 5, Mockito, integration testing, TDD patterns
Provides testing patterns for Micronaut/Kotlin backends: integration tests with Retrofit clients, repository tests, MockK rules, test naming, and coverage requirements. Use when writing tests, setting up infrastructure, or improving coverage.