Статьи

Интеграционное тестирование на REST URL с Spring Boot

Мы создаем приложение Spring Boot с интерфейсом REST, и в какой-то момент мы хотели протестировать наш интерфейс REST и, если возможно, интегрировать это тестирование с нашими обычными модульными тестами. Один из способов сделать это — @Autowire наши контроллеры REST и вызывать наши конечные точки, используя это. Однако это не даст полной выгоды, поскольку пропустит такие вещи, как десериализация JSON и глобальная обработка исключений. Таким образом, идеальной ситуацией для нас было бы запустить наше приложение при запуске модульного теста и снова закрыть его после последнего модульного теста.

Так получилось, что Spring Boot делает все это для нас с одной аннотацией: @IntegrationTest .

Вот пример реализации абстрактного класса, который вы можете использовать для своих юнит-тестов, который автоматически запустит приложение перед запуском ваших юнит-тестов, кэширует его и снова закроет в конце.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package demo;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
 
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
 
import com.fasterxml.jackson.databind.ObjectMapper;
 
@RunWith(SpringJUnit4ClassRunner.class)
// Your spring configuration class containing the @EnableAutoConfiguration
// annotation
@SpringApplicationConfiguration(classes = Application.class)
// Makes sure the application starts at a random free port, caches it throughout
// all unit tests, and closes it again at the end.
@IntegrationTest("server.port:0")
@WebAppConfiguration
public abstract class AbstractIntegrationTest {
 
    // Will contain the random free port number
    @Value("${local.server.port}")
    private int port;
 
    /**
     * Returns the base url for your rest interface
     *
     * @return
     */
    private String getBaseUrl() {
        return "http://localhost:" + port;
    }
 
    // Some convenience methods to help you interact with your rest interface
 
    /**
     * @param requestMappingUrl
     *            should be exactly the same as defined in your RequestMapping
     *            value attribute (including the parameters in {})
     *            RequestMapping(value = yourRestUrl)
     * @param serviceReturnTypeClass
     *            should be the the return type of the service
     * @param parametersInOrderOfAppearance
     *            should be the parameters of the requestMappingUrl ({}) in
     *            order of appearance
     * @return the result of the service, or null on error
     */
    protected <T> T getEntity(final String requestMappingUrl, final Class<T> serviceReturnTypeClass, final Object... parametersInOrderOfAppearance) {
        // Make a rest template do do the service call
        final TestRestTemplate restTemplate = new TestRestTemplate();
        // Add correct headers, none for this example
        final HttpEntity<String> requestEntity = new HttpEntity<String>(new HttpHeaders());
        try {
            // Do a call the the url
            final ResponseEntity<T> entity = restTemplate.exchange(getBaseUrl() + requestMappingUrl, HttpMethod.GET, requestEntity, serviceReturnTypeClass,
                    parametersInOrderOfAppearance);
            // Return result
            return entity.getBody();
        } catch (final Exception ex) {
            // Handle exceptions
        }
        return null;
    }
 
    /**
     * @param requestMappingUrl
     *            should be exactly the same as defined in your RequestMapping
     *            value attribute (including the parameters in {})
     *            RequestMapping(value = yourRestUrl)
     * @param serviceListReturnTypeClass
     *            should be the the generic type of the list the service
     *            returns, eg: List<serviceListReturnTypeClass>
     * @param parametersInOrderOfAppearance
     *            should be the parameters of the requestMappingUrl ({}) in
     *            order of appearance
     * @return the result of the service, or null on error
     */
    protected <T> List<T> getList(final String requestMappingUrl, final Class<T> serviceListReturnTypeClass, final Object... parametersInOrderOfAppearance) {
        final ObjectMapper mapper = new ObjectMapper();
        final TestRestTemplate restTemplate = new TestRestTemplate();
        final HttpEntity<String> requestEntity = new HttpEntity<String>(new HttpHeaders());
        try {
            // Retrieve list
            final ResponseEntity<List> entity = restTemplate.exchange(getBaseUrl() + requestMappingUrl, HttpMethod.GET, requestEntity, List.class, parametersInOrderOfAppearance);
            final List<Map<String, String>> entries = entity.getBody();
            final List<T> returnList = new ArrayList<T>();
            for (final Map<String, String> entry : entries) {
                // Fill return list with converted objects
                returnList.add(mapper.convertValue(entry, serviceListReturnTypeClass));
            }
            return returnList;
        } catch (final Exception ex) {
            // Handle exceptions
        }
        return null;
    }
 
    /**
     *
     * @param requestMappingUrl
     *            should be exactly the same as defined in your RequestMapping
     *            value attribute (including the parameters in {})
     *            RequestMapping(value = yourRestUrl)
     * @param serviceReturnTypeClass
     *            should be the the return type of the service
     * @param objectToPost
     *            Object that will be posted to the url
     * @return
     */
    protected <T> T postEntity(final String requestMappingUrl, final Class<T> serviceReturnTypeClass, final Object objectToPost) {
        final TestRestTemplate restTemplate = new TestRestTemplate();
        final ObjectMapper mapper = new ObjectMapper();
        try {
            final HttpEntity<String> requestEntity = new HttpEntity<String>(mapper.writeValueAsString(objectToPost));
            final ResponseEntity<T> entity = restTemplate.postForEntity(getBaseUrl() + requestMappingUrl, requestEntity, serviceReturnTypeClass);
            return entity.getBody();
        } catch (final Exception ex) {
            // Handle exceptions
        }
        return null;
    }
}