في المقالة الأولى من هذه السلسلة، قمنا ببناء أداة تحميل صور قوية باستخدام Spring Boot وCloudinary وDocker وPostgreSQL. لقد قمنا بتغطية كل شيء بدءًا من إعداد المشروع وحتى تقديم الطلبات وحتى نقطة النهاية التي تحفظ الصورة والمعلومات. إذا لم تكن قد قرأت هذه المقالة بعد، فإنني أوصي بشدة بالبدء من هناك للحصول على أساس متين للتطبيق الذي سنعمل معه.
الآن، حان الوقت للتأكد من أن تطبيقنا موثوق به ويحافظ على سلامته مع مرور الوقت. يقودنا هذا إلى جانب حاسم من تطوير البرمجيات: الاختبار. في هذه المقالة، سنركز على كتابة اختبارات الوحدة لواجهة برمجة التطبيقات الخاصة بتحميل الصور. سنستكشف كيفية الاستهزاء بالتبعيات، وكتابة الاختبارات التي تغطي أجزاء مختلفة من خدمتنا.
لا يساعد اختبار الوحدة في اكتشاف الأخطاء مبكرًا فحسب، بل يضمن أيضًا أن الكود الخاص بنا قابل للصيانة وقابل للتطوير. بحلول نهاية هذه المقالة، سيكون لديك مجموعة شاملة من الاختبارات لواجهة برمجة التطبيقات الخاصة بتحميل الصور، مما يمنحك الثقة في أن تطبيقك يعمل كما هو متوقع.
دعونا نتعمق في عالم اختبار الوحدات ونجعل واجهة برمجة تطبيقات تحميل الصور الخاصة بنا مقاومة للرصاص!
أنا أستخدم VSCode مع حزمة الامتداد لـ Java. نحن الآن جاهزون لكتابة اختباراتنا.
إذا كنت تستخدم IDE آخر، راجع الدعم الخاص بهم جميعًا هنا في وثائق JUnit5.
انقر بزر الماوس الأيمن على فئة BookService، وانقر على Go to Test، وحدد الطرق التي تريد إنشاء اختبارات لها من القائمة.
سيتم إنشاء ملف مشابه، مثل الملف أدناه:
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 ArgumentCaptorbookArgumentCaptor; @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 ArgumentCaptorbookArgumentCaptor; @InjectMocks private BookService bookService; @Nested class AddBook { @Test void shouldCreateANewBook() throws Exception { // Arrange Map 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 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 - المستندات
موكيتو - المستندات
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3