Интеграционно тестване през пролетта

1. Общ преглед

Интеграционното тестване играе важна роля в цикъла за разработка на приложения, като проверява цялостното поведение на системата.

В тази статия ще видим как можем да използваме тестовата рамка Spring MVC, за да напишем и стартираме тестове за интеграция, които тестват контролерите, без изрично да стартира контейнер Servlet.

2. Подготовка

Следните зависимости на Maven са необходими за провеждане на тестове за интеграция, както е описано в тази статия. На първо място най-новите зависимости от теста JUnit и Spring:

 junit junit 4.12 test   org.springframework spring-test 4.3.2.RELEASE test  

За ефективно утвърждаване на резултатите ще използваме също Hamcrest и JSON path:

 org.hamcrest hamcrest-library 1.3 test   com.jayway.jsonpath json-path 2.2.0 test 

3. Конфигурация на пробен MVC тест

Нека сега представим как да конфигурираме и стартираме тестовете с пролетта.

3.1. Активирайте Spring in Tests

Първо, всеки пролетен тест ще се стартира с помощта на @RunWith (SpringJUnit4ClassRunner.class) ; бегачът е по същество входната точка, за да започнете да използвате Spring Test framework.

Също така се нуждаем от анотациите @ContextConfiguration, за да заредим конфигурацията на контекста и да заредим контекста, който ще използва тестът .

Нека погледнем:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { ApplicationConfig.class }) @WebAppConfiguration public class GreetControllerIntegrationTest { .... }

Забележете как в @ContextConfiguration предоставихме конфигурационния клас ApplicationConfig.class, който зарежда конфигурацията, от която се нуждаем за този конкретен тест.

Тук използвахме Java конфигурационен клас, за да зададем конфигурацията на контекста; по подобен начин можем да използваме XML-базирана конфигурация:

@ContextConfiguration(locations={""})

И накрая - тестът е коментиран и с @ WebAppConfiguration - което ще зареди контекста на уеб приложението.

По подразбиране той търси основното уеб приложение по пътя по подразбиране src / main / webapp ; местоположението може да бъде заменено чрез предаване на атрибута стойност като:

@WebAppConfiguration(value = "")

3.2. В WebApplicationContext обекта

WebApplicationContext ( wac ) предоставя конфигурация на уеб приложение. Той зарежда всички зърна на приложения и контролери в контекста.

Сега ще можем да свържем контекста на уеб приложението директно в теста:

@Autowired private WebApplicationContext wac;

3.3. Подигравателен уеб контекстен боб

MockMvc осигурява поддръжка за пролетно тестване на MVC. Той капсулира всички компоненти на уеб приложения и ги прави достъпни за тестване.

Нека да видим как да го използваме:

private MockMvc mockMvc; @Before public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); }

Ние трябва да се инициализира mockMvc обект в @Before анотиран метод, така че ние не трябва да го инициализира във всеки тест.

3.4. Проверете тестовата конфигурация

За нашия урок тук, нека всъщност проверим дали зареждаме правилно обекта WebApplicationContext ( wac ). Също така ще проверим дали е прикачен правилният servletContext :

@Test public void givenWac_whenServletContext_thenItProvidesGreetController() { ServletContext servletContext = wac.getServletContext(); Assert.assertNotNull(servletContext); Assert.assertTrue(servletContext instanceof MockServletContext); Assert.assertNotNull(wac.getBean("greetController")); }

Забележете, че проверяваме също така, че ние GreenController.java боб съществува в уеб контекста - което гарантира, че пролетните зърна са заредени правилно.

На този етап настройката на теста за интеграция е завършена. Нека да видим как можем да тестваме методи на ресурси, използвайки обекта MockMvc .

4. Писане на тестове за интеграция

В този раздел ще разгледаме основните операции, достъпни чрез тестовата рамка.

Ще покажем как да изпращаме заявки с променливи и параметри на пътя. Освен това ще последваме няколкото примера, които показват как да се твърди, че правилното име на изгледа е разрешено или че тялото на отговора е както се очаква.

Следните фрагменти използват статични импорти от класове M ockMvcRequestBuilders или MockMvcResultMatchers .

4.1. Проверете името на изгледа

Нека извикаме / homePage крайната точка от нашия тест като :

//localhost:8080/spring-mvc-test/

или

//localhost:8080/spring-mvc-test/homePage

Кодов фрагмент:

@Test public void givenHomePageURI_whenMockMVC_thenReturnsIndexJSPViewName() { this.mockMvc.perform(get("/homePage")).andDo(print()) .andExpect(view().name("index")); }

Нека разбием това:

  • perform() method will call a get request method which returns the ResultActions. Using this result we can have assertion expectations on response like content, HTTP status, header, etc
  • andDo(print()) will print the request and response. This is helpful to get a detailed view in case of error
  • andExpect()will expect the provided argument. In our case we are expecting “index” to be returned via MockMvcResultMatchers.view()

4.2. Verify Response Body

We will invoke /greet endpoint from our test as:

//localhost:8080/spring-mvc-test/greet

Expected Output:

{ "id": 1, "message": "Hello World!!!" }

Code Snippet:

@Test public void givenGreetURI_whenMockMVC_thenVerifyResponse() { MvcResult mvcResult = this.mockMvc.perform(get("/greet")) .andDo(print()).andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Hello World!!!")) .andReturn(); Assert.assertEquals("application/json;charset=UTF-8", mvcResult.getResponse().getContentType()); }

Let's see exactly what's going on:

  • andExpect(MockMvcResultMatchers.status().isOk())will verify that response HTTP status is Ok i.e. 200. This ensures that the request was successfully executed
  • andExpect(MockMvcResultMatchers.jsonPath(“$.message”).value(“Hello World!!!”)) will verify that response content matches with the argument “Hello World!!!“. Here we used jsonPath which extracts response content and provide the requested value
  • andReturn()will return the MvcResult object which is used when we have to verify something which is not achievable by the library. You can see we have added assertEquals to match the content type of response that is extracted from the MvcResult object

4.3. Send GET Request With Path Variable

We will invoke /greetWithPathVariable/{name} endpoint from our test as:

//localhost:8080/spring-mvc-test/greetWithPathVariable/John

Expected Output:

{ "id": 1, "message": "Hello World John!!!" }

Code Snippet:

@Test public void givenGreetURIWithPathVariable_whenMockMVC_thenResponseOK() { this.mockMvc .perform(get("/greetWithPathVariable/{name}", "John")) .andDo(print()).andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World John!!!")); }

MockMvcRequestBuilders.get(“/greetWithPathVariable/{name}”, “John”) will send request as “/greetWithPathVariable/John“.

This becomes easier with respect to readability and knowing what are the parameters which are dynamically set in the URL. Note that we can pass as many path parameters as needed.

4.4. Send GET Request With Query Parameters

We'll invoke /greetWithQueryVariable?name={name} endpoint from our test as:

//localhost:8080/spring-mvc-test /greetWithQueryVariable?name=John%20Doe

Expected Output:

{ "id": 1, "message": "Hello World John Doe!!!" }

Code Snippet:

@Test public void givenGreetURIWithQueryParameter_whenMockMVC_thenResponseOK() { this.mockMvc.perform(get("/greetWithQueryVariable") .param("name", "John Doe")).andDo(print()).andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World John Doe!!!")); }

param(“name”, “John Doe”) will append the query parameter in the GET request. It is similar to “ /greetWithQueryVariable?name=John%20Doe“.

The query parameter can also be implemented using the URI template style:

this.mockMvc.perform( get("/greetWithQueryVariable?name={name}", "John Doe"));

4.5. Send POST Request

We will invoke /greetWithPost endpoint from our test as:

//localhost:8080/spring-mvc-test/greetWithPost

Expected Output:

{ "id": 1, "message": "Hello World!!!" }

Code Snippet:

@Test public void givenGreetURIWithPost_whenMockMVC_thenVerifyResponse() { this.mockMvc.perform(post("/greetWithPost")).andDo(print()) .andExpect(status().isOk()).andExpect(content() .contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World!!!")); }

MockMvcRequestBuilders.post(“/greetWithPost”) will send the post request. Path variables and Query Parameters can be set in a similar way we looked earlier, whereas Form Data can be set via param() method only similar to Query Parameter as:

//localhost:8080/spring-mvc-test/greetWithPostAndFormData

Form Data:

id=1;name=John%20Doe

Expected Output:

{ "id": 1, "message": "Hello World John Doe!!!" }

Code Snippet:

@Test public void givenGreetURIWithPostAndFormData_whenMockMVC_thenResponseOK() { this.mockMvc.perform(post("/greetWithPostAndFormData").param("id", "1") .param("name", "John Doe")).andDo(print()).andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World John Doe!!!")) .andExpect(jsonPath("$.id").value(1)); }

In the above code snippet, we've added two parameters id as “1” and name as “John Doe”.

5. MockMvc Limitations

MockMvc provides an elegant and easy to use API to call web endpoints and inspect and assert their response at the same time. Despite all its benefits, it has a few limitations.

First of all, it does use a subclass of the DispatcherServlet to handle test requests. To be more specific, the TestDispatcherServlet is responsible for calling controllers and performing all of the familiar Spring magic.

The MockMvc class wraps this TestDispatcherServlet internally. So, every time we send a request using the perform() method, MockMvc will use the underlying TestDispatcherServlet directly. Therefore, there are no real network connections made, and consequently, we won't test the whole network stack while using MockMvc.

Also,because Spring prepares a fake web application context to mock the HTTP requests and responses, it may not support all features of a full-blown Spring application.

For example, this mock setup does not support HTTP redirections. This may not seem that significant at first. However, Spring Boot handles some errors by redirecting the current request to the /error endpoint. So if we're using the MockMvc, we may not be able to test some API failures.

As an alternative to MockMvc, we can set up a more real application contextand then use RestTemplate or even Rest Assured to test our application.

For instance, this is easy using Spring Boot:

@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) public class GreetControllerRealIntegrationTest { @LocalServerPort private int port; @Before public void setUp() { RestAssured.port = port; } @Test public void givenGreetURI_whenSendingReq_thenVerifyResponse() { given().get("/greet") .then() .statusCode(200); } }

This way, every test will make a real HTTP request to the application that listens on a random TCP port.

6. Conclusion

In this tutorial, we implemented a few simple Spring enabled integration tests.

We also looked at the WebApplicationContext and MockMVC object creation which played an important role in calling the endpoints of the application.

Looking further we covered how we can send GET and POST requests with variations of parameter passing and how to verify the HTTP response status, header, and content.

As a closing remark, we did also evaluate some limitations of the MockMvc. Knowing those limitations can guide us to make an informed decision about how we're going to implement our tests.

И накрая, изпълнението на всички тези примери и кодови фрагменти е достъпно в GitHub .