このシリーズの最初の記事では、Spring Boot、Cloudinary、Docker、PostgreSQL を使用した堅牢なイメージ アップローダーの構築について説明しました。プロジェクトのセットアップから、画像と情報を保存するエンドポイントへのリクエストまで、すべてをカバーしました。この記事をまだ読んでいない場合は、これから作業するアプリケーションの強固な基盤を得るために、そこから読み始めることを強くお勧めします。
今度は、アプリケーションの信頼性と長期にわたる整合性の維持を確認します。これは、ソフトウェア開発の重要な側面、テストにつながります。この記事では、画像アップローダー API の単体テストの作成に焦点を当てます。依存関係を模擬する方法を検討し、サービスのさまざまな部分をカバーするテストを作成します。
単体テストは、バグを早期に発見するのに役立つだけでなく、コードが保守可能でスケーラブルであることを保証します。この記事を終えるまでに、画像アップローダー API の包括的なテスト スイートが完成し、アプリケーションが期待どおりに動作することを確信できるようになります。
単体テストの世界に飛び込んで、画像アップローダー API を完璧なものにしてみましょう!
私は VSCode と Java 用拡張パックを使用しています。これで、テストを作成する準備が整いました。
別の IDE を使用している場合は、JUnit5 ドキュメントのすべての IDE のサポートを参照してください。
BookService クラスを右クリックし、「テストに移動」をクリックして、メニューからテストを生成するメソッドを選択します。
以下のような同様のファイルが生成されます:
import org.junit.jupiter.api.Test; public class BookServiceTest { @Test void testAddBook() { } }
この記事では、テスト (配置 - 実行 - アサート) の AAA パターン を使用することに注意してください。
@ExtendWith(MockitoExtension.class) public class BookServiceTest { @Mock private BookRepository bookRepository; @Mock private Cloudinary cloudinary; @Mock private MultipartFile multipartFile; @Mock private Uploader uploader; @Captor private ArgumentCaptor<Book> bookArgumentCaptor; @InjectMocks private BookService bookService; }
@ExtendWith(MockitoExtension.class) public class BookServiceTest { @Mock private BookRepository bookRepository; @Mock private Cloudinary cloudinary; @Mock private MultipartFile multipartFile; @Mock private Uploader uploader; @Captor private ArgumentCaptor<Book> bookArgumentCaptor; @InjectMocks private BookService bookService; @Nested class AddBook { @Test void shouldCreateANewBook() throws Exception { // Arrange Map<String, Object> uploadResult = Map.of("url", "http://example.com/image.jpg"); when(cloudinary.uploader()).thenReturn(uploader); when(uploader.upload(any(File.class), anyMap())).thenReturn(uploadResult); Book book = new Book(); book.setName("Test Book"); book.setImgUrl(uploadResult.get("url").toString()); when(bookRepository.save(any(Book.class))).thenReturn(book); when(multipartFile.getOriginalFilename()).thenReturn("test.jpg"); when(multipartFile.getBytes()).thenReturn("test content".getBytes()); // Act Book result = bookService.addBook("Test Book", multipartFile); // Assert assertNotNull(result); assertEquals("Test Book", result.getName()); assertEquals("http://example.com/image.jpg", result.getImgUrl()); } @Test void shouldCallRepositorySave() throws Exception { // Arrange Map<String, Object> uploadResult = Map.of("url", "http://example.com/image.jpg"); when(cloudinary.uploader()).thenReturn(uploader); when(uploader.upload(any(File.class), anyMap())).thenReturn(uploadResult); Book book = new Book(); book.setName("Test Book"); book.setImgUrl(uploadResult.get("url").toString()); when(bookRepository.save(any(Book.class))).thenReturn(book); when(multipartFile.getOriginalFilename()).thenReturn("test.jpg"); when(multipartFile.getBytes()).thenReturn("test content".getBytes()); // Act bookService.addBook("Test Book", multipartFile); // Assert verify(bookRepository, times(1)).save(bookArgumentCaptor.capture()); Book capturedBook = bookArgumentCaptor.getValue(); assertEquals("Test Book", capturedBook.getName()); assertEquals("http://example.com/image.jpg", capturedBook.getImgUrl()); } @Test void shouldFailTheUpload() throws Exception { // Arrange when(multipartFile.getOriginalFilename()).thenReturn("test.jpg"); when(multipartFile.getBytes()).thenReturn("test content".getBytes()); when(cloudinary.uploader()).thenReturn(uploader); when(uploader.upload(any(File.class), anyMap())).thenThrow(IOException.class); // Act & Assert ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { bookService.addBook("Test Book", multipartFile); }); assertEquals(HttpStatus.BAD_GATEWAY, exception.getStatusCode()); assertEquals("Failed to upload the file.", exception.getReason()); } } }
package cloudinary.upload.imageUpload.controllers; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.server.ResponseStatusException; import cloudinary.upload.imageUpload.configs.GlobalExceptionHandler; import cloudinary.upload.imageUpload.entities.Book; import cloudinary.upload.imageUpload.services.BookService; @ExtendWith(MockitoExtension.class) public class BookControllerTest { @Mock private BookService bookService; @InjectMocks private BookController bookController; private MockMvc mockMvc; @Nested class AddBook { @Test void shouldReturnSuccess() throws Exception { // Arrange MockMultipartFile image = new MockMultipartFile("imgUrl", "test.jpg", MediaType.IMAGE_JPEG_VALUE, "test content".getBytes()); Book book = new Book(); book.setName("Test Book"); book.setImgUrl("http://example.com/image.jpg"); when(bookService.addBook(any(String.class), any(MockMultipartFile.class))).thenReturn(book); mockMvc = MockMvcBuilders.standaloneSetup(bookController).build(); // Act & Assert mockMvc.perform(multipart("/addBook") .file(image) .param("name", "Test Book")) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("Test Book")) .andExpect(jsonPath("$.imgUrl").value("http://example.com/image.jpg")); } @Test void shouldFailToUploadImage() throws Exception { // Arrange MockMultipartFile image = new MockMultipartFile("imgUrl", "test.jpg", MediaType.IMAGE_JPEG_VALUE, "test content".getBytes()); when(bookService.addBook(any(String.class), any(MockMultipartFile.class))) .thenThrow(new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to upload the file.")); mockMvc = MockMvcBuilders.standaloneSetup(bookController).setControllerAdvice(new GlobalExceptionHandler()) .build(); // Act & Assert mockMvc.perform(multipart("/addBook") .file(image) .param("name", "Test Book")) .andExpect(status().isInternalServerError()) .andExpect(result -> result.getResponse().equals("Failed to upload the file.")); } @Test void shouldFailWithMissingNameParameter() throws Exception { // Arrange MockMultipartFile image = new MockMultipartFile("imgUrl", "test.jpg", MediaType.IMAGE_JPEG_VALUE, "test content".getBytes()); mockMvc = MockMvcBuilders.standaloneSetup(bookController).build(); // Act & Assert mockMvc.perform(multipart("/addBook") .file(image)) .andExpect(status().isBadRequest()); } @Test void shouldFailWithMissingImageParameter() throws Exception { // Arrange mockMvc = MockMvcBuilders.standaloneSetup(bookController).build(); // Act & Assert mockMvc.perform(multipart("/addBook") .param("name", "Test Book")) .andExpect(status().isBadRequest()); } } }
これらは、アプリのテストを開始するためのいくつかの簡単なテスト ケースです。繰り返しを避けるためにいくつかのファクトリーを追加することで、これらのテストをリファクタリングできることを覚えておいてください。
お読みいただきありがとうございます。
JUnit5 - ドキュメント
Mockito - ドキュメント
以上がJUnitnd Mockito を使用したイメージ アップローダー API の単体テストの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。