導入
コードを書くたびに、ブラウザやPostmanで手動でAPIを叩いて確認するのは時間がかかりますし、確認漏れも起こります。自動テストを書けば、変更のたびに一瞬で「壊れていないか」を確認できます。
説明
Springのテストは、大きく2つのレベルに分かれます。
① 単体テスト ― Springを一切起動せず、素のJavaオブジェクトとしてテストします。
class UserServiceTest {
@Test
void 既に登録済みのメールなら例外を投げる() {
UserRepository fakeRepo = mock(UserRepository.class); // 偽物のRepository
when(fakeRepo.findByEmail("a@example.com"))
.thenReturn(Optional.of(new User("既存太郎", "a@example.com")));
UserService service = new UserService(fakeRepo, ...); // コンストラクタインジェクションだからこそ、newで組み立てられる
assertThrows(DuplicateEmailException.class,
() -> service.register(new CreateUserRequest("新規太郎", "a@example.com", "pass")));
}
}
ここでレッスン4の「コンストラクタインジエクション」が効いてきます。UserService を new で直接組み立てられるからこそ、Springを起動せずに一瞬でテストが実行できるのです。Repository は本物ではなく モック(偽物) に差し替え、「Repositoryがこう返したら、Serviceはこう振る舞う」だけに集中してテストします。
② 統合テスト ― @SpringBootTest を使い、実際にApplicationContextを起動してテストします。
@SpringBootTest
@AutoConfigureMockMvc
class UserApiIntegrationTest {
@Autowired
private MockMvc mockMvc; // 実際のHTTPリクエストに近い形でControllerを叩ける
@Test
void ユーザー登録に成功する() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"山田太郎\",\"email\":\"yamada@example.com\",\"password\":\"pass1234\"}"))
.andExpect(status().isOk());
}
}
graph TD Unit["単体テスト<br/>Springを起動しない<br/>速い・対象が狭い"] --> Layer["どこをテストしたいか"] Integ["統合テスト(@SpringBootTest)<br/>Springを起動する<br/>遅い・対象が広い(本物に近い)"] --> Layer
@SpringBootTest はアプリ全体を起動するため実行に時間がかかります。そこで、「Web層だけ」「データアクセス層だけ」を軽量に起動する スライステスト(@WebMvcTest, @DataJpaTest など)も用意されています。テストしたい範囲に応じて、単体テスト・スライステスト・統合テストを使い分けるのが実務の基本方針です。
やってみよう
「Serviceの業務ロジックが正しいか」を確かめたいときと、「ControllerのURLマッピングやJSON変換まで含めて正しく動くか」を確かめたいときとで、単体テストと統合テストのどちらが向いているか考えてみましょう。