Skip to content

テスト方法

Spring Bootアプリケーションの包括的なテスト戦略について詳しく解説します。

テストは、以下の3つの層に分けて実装します:

/\
/ \ E2Eテスト(少数)
/____\
/ \ 統合テスト(中程度)
/________\
/ \ ユニットテスト(多数)
/____________\
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService;
@Test
@DisplayName("ユーザーIDでユーザーを取得できる")
void testFindById_Success() {
// Given
Long userId = 1L;
User user = createTestUser(userId, "Test User", "test@example.com");
when(userRepository.findById(userId)).thenReturn(Optional.of(user));
// When
UserDTO result = userService.findById(userId);
// Then
assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo(userId);
assertThat(result.getName()).isEqualTo("Test User");
verify(userRepository).findById(userId);
verifyNoInteractions(emailService);
}
@Test
@DisplayName("存在しないユーザーIDで例外がスローされる")
void testFindById_NotFound() {
// Given
Long userId = 999L;
when(userRepository.findById(userId)).thenReturn(Optional.empty());
// When & Then
assertThatThrownBy(() -> userService.findById(userId))
.isInstanceOf(ResourceNotFoundException.class)
.hasMessageContaining("User")
.hasMessageContaining("999");
}
@Test
@DisplayName("新しいユーザーを作成できる")
void testCreateUser_Success() {
// Given
UserCreateRequest request = new UserCreateRequest();
request.setName("New User");
request.setEmail("new@example.com");
request.setPassword("password123");
User savedUser = createTestUser(1L, "New User", "new@example.com");
when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
when(userRepository.save(any(User.class))).thenReturn(savedUser);
doNothing().when(emailService).sendWelcomeEmail(anyString());
// When
UserDTO result = userService.create(request);
// Then
assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo(1L);
assertThat(result.getName()).isEqualTo("New User");
verify(userRepository).existsByEmail("new@example.com");
verify(userRepository).save(any(User.class));
verify(emailService).sendWelcomeEmail("new@example.com");
}
@Test
@DisplayName("重複したメールアドレスでユーザー作成時に例外がスローされる")
void testCreateUser_DuplicateEmail() {
// Given
UserCreateRequest request = new UserCreateRequest();
request.setName("New User");
request.setEmail("existing@example.com");
request.setPassword("password123");
when(userRepository.existsByEmail("existing@example.com")).thenReturn(true);
// When & Then
assertThatThrownBy(() -> userService.create(request))
.isInstanceOf(DuplicateResourceException.class)
.hasMessageContaining("User")
.hasMessageContaining("existing@example.com");
verify(userRepository, never()).save(any(User.class));
verify(emailService, never()).sendWelcomeEmail(anyString());
}
private User createTestUser(Long id, String name, String email) {
User user = new User();
user.setId(id);
user.setName(name);
user.setEmail(email);
return user;
}
}
@SpringBootTest
@Transactional
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("ユーザーの作成と取得の統合テスト")
void testCreateAndFindUser() {
// Given
UserCreateRequest request = new UserCreateRequest();
request.setName("Integration Test User");
request.setEmail("integration@example.com");
request.setPassword("password123");
// When
UserDTO createdUser = userService.create(request);
// Then
assertThat(createdUser).isNotNull();
assertThat(createdUser.getId()).isNotNull();
assertThat(createdUser.getName()).isEqualTo("Integration Test User");
assertThat(createdUser.getEmail()).isEqualTo("integration@example.com");
// データベースから取得して確認
Optional<User> savedUser = userRepository.findById(createdUser.getId());
assertThat(savedUser).isPresent();
assertThat(savedUser.get().getName()).isEqualTo("Integration Test User");
}
@Test
@DisplayName("トランザクションのロールバックテスト")
void testTransactionRollback() {
// Given
UserCreateRequest request = new UserCreateRequest();
request.setName("Rollback Test User");
request.setEmail("rollback@example.com");
request.setPassword("password123");
// When
try {
userService.createWithError(request);
} catch (Exception e) {
// エラーが発生
}
// Then: トランザクションがロールバックされているため、データは保存されていない
Optional<User> user = userRepository.findByEmail("rollback@example.com");
assertThat(user).isEmpty();
}
}
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Test
@DisplayName("ユーザー一覧を取得できる")
void testGetUsers() throws Exception {
// Given
List<UserDTO> users = Arrays.asList(
createUserDTO(1L, "User 1", "user1@example.com"),
createUserDTO(2L, "User 2", "user2@example.com")
);
when(userService.findAll()).thenReturn(users);
// When & Then
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$[0].id").value(1L))
.andExpect(jsonPath("$[0].name").value("User 1"))
.andExpect(jsonPath("$[1].id").value(2L))
.andExpect(jsonPath("$[1].name").value("User 2"));
}
@Test
@DisplayName("ユーザー詳細を取得できる")
void testGetUser() throws Exception {
// Given
Long userId = 1L;
UserDTO userDTO = createUserDTO(userId, "Test User", "test@example.com");
when(userService.findById(userId)).thenReturn(userDTO);
// When & Then
mockMvc.perform(get("/api/users/{id}", userId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(userId))
.andExpect(jsonPath("$.name").value("Test User"))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
@DisplayName("存在しないユーザーIDで404エラーが返される")
void testGetUser_NotFound() throws Exception {
// Given
Long userId = 999L;
when(userService.findById(userId))
.thenThrow(new ResourceNotFoundException("User", userId));
// When & Then
mockMvc.perform(get("/api/users/{id}", userId))
.andExpect(status().isNotFound());
}
@Test
@DisplayName("新しいユーザーを作成できる")
void testCreateUser() throws Exception {
// Given
UserCreateRequest request = new UserCreateRequest();
request.setName("New User");
request.setEmail("new@example.com");
request.setPassword("password123");
UserDTO createdUser = createUserDTO(1L, "New User", "new@example.com");
when(userService.create(any(UserCreateRequest.class))).thenReturn(createdUser);
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.name").value("New User"))
.andExpect(jsonPath("$.email").value("new@example.com"));
}
@Test
@DisplayName("バリデーションエラーで400エラーが返される")
void testCreateUser_ValidationError() throws Exception {
// Given
UserCreateRequest request = new UserCreateRequest();
// 必須フィールドを設定しない
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
}
private UserDTO createUserDTO(Long id, String name, String email) {
UserDTO dto = new UserDTO();
dto.setId(id);
dto.setName(name);
dto.setEmail(email);
return dto;
}
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager entityManager;
@Test
@DisplayName("メールアドレスでユーザーを検索できる")
void testFindByEmail() {
// Given
User user = new User();
user.setName("Test User");
user.setEmail("test@example.com");
user.setPassword("password");
entityManager.persistAndFlush(user);
// When
Optional<User> found = userRepository.findByEmail("test@example.com");
// Then
assertThat(found).isPresent();
assertThat(found.get().getEmail()).isEqualTo("test@example.com");
assertThat(found.get().getName()).isEqualTo("Test User");
}
@Test
@DisplayName("存在しないメールアドレスで空のOptionalが返される")
void testFindByEmail_NotFound() {
// When
Optional<User> found = userRepository.findByEmail("notfound@example.com");
// Then
assertThat(found).isEmpty();
}
@Test
@DisplayName("メールアドレスの存在確認ができる")
void testExistsByEmail() {
// Given
User user = new User();
user.setName("Test User");
user.setEmail("test@example.com");
user.setPassword("password");
entityManager.persistAndFlush(user);
// When
boolean exists = userRepository.existsByEmail("test@example.com");
boolean notExists = userRepository.existsByEmail("notfound@example.com");
// Then
assertThat(exists).isTrue();
assertThat(notExists).isFalse();
}
}

各テストは独立して実行できるようにします。

@SpringBootTest
@Transactional
class IndependentTest {
// 各テストメソッドは独立して実行される
// データベースの状態は各テスト後にロールバックされる
}
@Test
void testWithTestData() {
// @BeforeEachでテストデータを準備
// または、@Sqlアノテーションを使用
}
@Test
void testWithClearAssertions() {
// Given
// When
UserDTO result = userService.findById(1L);
// Then
assertThat(result)
.isNotNull()
.extracting(UserDTO::getId, UserDTO::getName)
.containsExactly(1L, "Test User");
}

Spring Bootでのテストは、以下の層に分けて実装します:

  • ユニットテスト: 単一のクラスやメソッドをテスト
  • 統合テスト: 複数のコンポーネントを組み合わせてテスト
  • Web層テスト: コントローラーのエンドポイントをテスト
  • リポジトリ層テスト: データベースアクセスをテスト

適切なテスト戦略により、コードの品質と信頼性を向上させることができます。