Статьи

Модульное тестирование HTTP-вызовов с LocalTestServer

Есть моменты, когда вы тестируете код, который выполняет HTTP-вызовы на удаленный сервер. Для этого вы можете использовать библиотеку, такую ​​как Apache’sHttpClient или Spring RestTemplate.

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

Рассмотрим следующий упрощенный пример.

ExampleHttpCall

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class ExampleHttpCall {
 
    private String serviceUrl;
 
    public ExampleHttpCall(String url) {
        serviceUrl = url;
    }
 
    public String doGet() {
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(serviceUrl, String.class);
        String response = responseEntity.getBody();
        return response;
    }
 
}

Как бы вы пошли о написании модульного теста для ExampleHttpCall?

Конечно, вы можете изменить класс таким образом, чтобы экземпляр класса RestTemplate был внедрен в класс:

ExampleHttpCall альтернативная версия

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Component
public class ExampleHttpCallAlternateVersion {
 
    @Resource
    private RestTemplate restTemplate;
 
    private String serviceUrl;
 
    public ExampleHttpCallAlternateVersion(String url) {
        serviceUrl = url;
    }
 
    public String doGet() {
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(serviceUrl, String.class);
        String response = responseEntity.getBody();
        return response;
    }
 
}

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

Для этого простого примера использование макета, вероятно, является подходящим способом. Но это не всегда так. Если это так, другой возможный подход использует использование локального тестового сервера. Как это происходит, проект Apache HttpClient предоставляет LocalTestServer в своем артефакте тестов. Если вы используете Maven, вы можете включить его, добавив следующую зависимость:

1
2
3
4
5
6
7
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.3.6</version>
    <classifier>tests</classifier>
    <scope>test</scope>
</dependency>

Теперь вы можете настроить сервер в своем модульном тесте:

LocalTestServer настроен

01
02
03
04
05
06
07
08
09
10
11
private LocalTestServer server = new LocalTestServer(null, null);
 
@Before
public void setUp() throws Exception {
    server.start();
}
 
@After
public void tearDown() throws Exception {
    server.stop();
}

Конечно, только запуск и остановка сервера не слишком далеко зашли. Итак, есть еще один ингредиент, который вам понадобится. Вы захотите зарегистрировать один или несколько обработчиков, которые реализуют интерфейс org.apache.http.protocol.HttpRequestHandler , например:

зарегистрировать ваш обработчик

1
server.register("/foo/*", myHttpRequestHandler);

Интерфейс HttpRequestHanlder позволит вам реализовать метод void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException;

Этот метод даст вам полный контроль над HTTP-ответом.

Таким образом, для нашего исходного примера минимальный модульный тест может выглядеть примерно так:

Базовый юнит тест

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ExampleHttpCallTest {
 
 
    private ExampleHttpCall exampleHttpCall;
 
    private LocalTestServer server = new LocalTestServer(null, null);
 
    private HttpRequestHandler myHttpRequestHandler = new HttpRequestHandler() {
 
        @Override
        public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
            response.setEntity(new StringEntity("foobar"));
        }
 
    };
 
    @Before
    public void setUp() throws Exception {
        server.start();
        server.register("/foo/*", myHttpRequestHandler);
 
        String serverUrl = "http:/" + server.getServiceAddress();
        exampleHttpCall = new ExampleHttpCall(serverUrl +"/foo/bar");
    }
 
    @After
    public void tearDown() throws Exception {
        server.stop();
    }
 
    @Test
    public void test() {
        String result = exampleHttpCall.doGet();
        assertEquals("foobar", result);
    }
 
}

Это все, что нужно, чтобы начать. С этого момента вы можете уточнить, добавив тестовые случаи для каждого возможного сценария.