Spring Mock- يوفر MVC طرق اختبار ممتازة لواجهات برمجة تطبيقات Spring Boot REST. يسمح لنا Mock-MVC باختبار معالجة طلبات Spring-MVC دون بدء تشغيل خادم حقيقي.
لقد استخدمت اختبارات Mock-MVC في مشاريع مختلفة وفي تجربتي غالبًا ما تكون طويلة جدًا. هذا ليس بالضرورة أمرًا سيئًا. ومع ذلك ، يتطلب هذا غالبًا نسخ / لصق أجزاء التعليمات البرمجية المماثلة في فئات الاختبار. في هذا المنشور ، سننظر في عدة طرق لتحسين اختبارات Spring Mock-MVC.
قرر ما الذي ستختبره باستخدام Mock-MVC
السؤال الأول الذي نحتاج إلى طرحه هو ما نريد اختباره باستخدام Mock-MVC. فيما يلي بعض الأمثلة على حالات الاختبار:
اختبار طبقة الويب فقط ومحاكاة جميع تبعيات وحدة التحكم.
اختبار طبقة الويب بمنطق المجال ومحاكاة تبعيات الطرف الثالث مثل قواعد البيانات أو قوائم انتظار الرسائل.
اختبار المسار الكامل من طبقة الويب إلى قاعدة البيانات عن طريق استبدال تبعيات الطرف الثالث ببدائل مضمنة إن أمكن (على سبيل المثال H2 أو Kafka المضمنة )
كل هذه السيناريوهات لها مزاياها وعيوبها. ومع ذلك ، أعتقد أن هناك قاعدتين بسيطتين يجب اتباعهما:
اختبر قدر الإمكان في اختبارات JUnit القياسية (بدون الربيع). يعمل هذا على تحسين أداء الاختبار بشكل كبير ويسهل كتابة الاختبارات.
حدد النص (النصوص) التي تريد اختبارها مع Spring وكن متسقًا في التبعيات التي تحاكيها. هذا يجعل الاختبارات أسهل في الفهم ويمكن أن يسرعها. عند تشغيل العديد من تكوينات الاختبار المختلفة ، غالبًا ما يتعين على Spring إعادة تهيئة سياق التطبيق ، مما يؤدي إلى إبطاء الاختبارات.
عند تحقيق أقصى استفادة من اختبارات JUnit القياسية ، غالبًا ما يعمل السيناريو الأخير المذكور أعلاه. بعد أن اختبرنا كل المنطق من خلال اختبارات الوحدة السريعة ، يمكننا استخدام العديد من اختبارات Mock-MVC للتأكد من أن جميع القطع تعمل معًا ، من وحدة التحكم إلى قاعدة البيانات.
تبسيط تكوين الاختبار باستخدام التعليقات التوضيحية المخصصة
يسمح لنا الربيع بدمج عدة تعليقات توضيحية لفصل الربيع في تعليق توضيحي واحد مخصص.
, @MockMvcTest:
@SpringBootTest
@TestPropertySource(locations = "classpath:test.properties")
@AutoConfigureMockMvc(secure = false)
@Retention(RetentionPolicy.RUNTIME)
public @interface MockMvcTest {}:
@MockMvcTest
public class MyTest {
...
}, . Spring .
Mock-MVC
Mock-MVC , :
mockMvc.perform(put("/products/42")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content("{\"name\": \"Cool Gadget\", \"description\": \"Looks cool\"}")
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk());PUT JSON /products/42.
, - JSON Java. , , , , Java, .
, JSON. , . Java Text JDK 13/14 . - , .
JSON . :
mvc.perform(put("/products/42")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content("""
{
"name": "Cool Gadget",
"description": "Looks cool"
}
""")
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk()); .
, -, , JSON , JSON.
:
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(put("/products/42")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(objectToJson(product))
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk());product JSON objectToJson(..). . , .
, . JSON REST-API, , PUT. :
public static MockHttpServletRequestBuilder putJson(String uri, Object body) {
try {
String json = new ObjectMapper().writeValueAsString(body);
return put(uri)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(json);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}body JSON Jackson ObjectMapper . PUT Accept Content-Type .
:
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(putJson("/products/42", product)
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk()), . putJson(..) MockHttpServletRequestBuilder. , , (, ).
- , Spring Mock-MVC. putJson(..). PUT , , -.
RequestPostProcessor . , RequestPostProcessor . .
:
public static RequestPostProcessor authentication() {
return request -> {
request.addHeader("Authorization", getBasicAuthHeader("John", "secr3t"));
return request;
};
} authentication() RequestPostProcessor, . RequestPostProcessor with(..):
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(putJson("/products/42", product).with(authentication()))
.andExpect(status().isOk()). , , . , putJson(url, data).with(authentication()) .
, .
:
mvc.perform(get("/products/42"))
.andExpect(status().isOk())
.andExpect(header().string("Cache-Control", "no-cache"))
.andExpect(jsonPath("$.name").value("Cool Gadget"))
.andExpect(jsonPath("$.description").value("Looks cool"));HTTP, , Cache-Control no-cache, JSON-Path .
Cache-Control , , , . :
public ResultMatcher noCacheHeader() {
return header().string("Cache-Control", "no-cache");
}, noCacheHeader() andExpect(..):
mvc.perform(get("/products/42"))
.andExpect(status().isOk())
.andExpect(noCacheHeader())
.andExpect(jsonPath("$.name").value("Cool Gadget"))
.andExpect(jsonPath("$.description").value("Looks cool"));.
, product(..), JSON Product:
public static ResultMatcher product(String prefix, Product product) {
return ResultMatcher.matchAll(
jsonPath(prefix + ".name").value(product.getName()),
jsonPath(prefix + ".description").value(product.getDescription())
);
}:
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(get("/products/42"))
.andExpect(status().isOk())
.andExpect(noCacheHeader())
.andExpect(product("$", product));, prefix . , , JSON .
, . prefix . :
Product product0 = ..
Product product1 = ..
mvc.perform(get("/products"))
.andExpect(status().isOk())
.andExpect(product("$[0]", product0))
.andExpect(product("$[1]", product1));ResultMatcher . .
Spring Mock-MVC. Mock-MVC, , . ( Spring Mock-MVC).
Spring Mock-MVC. RequestPostProcessor . ResultMatcher .
يمكنك العثور على أمثلة التعليمات البرمجية على جيثب .