#java #unit-testing #mockito #httpclient #junit5
#java #модульное тестирование #mockito #httpclient #junit5
Вопрос:
Я столкнулся с проблемой, я не знаю, как написать модульный тест статического метода void.
У меня есть класс HttpHelper, который прямо сейчас использует Apache HttpClient. Код, подобный приведенному ниже.
public class HttpHelper {
private static CloseableHttpClient httpClient;
public static void init() {
httpClient = HttpClients.custom().setSSLContext(getDummySSL()).build();
}
public static void closeHttpClient() throws IOException {
httpClient.close();
}
private static SSLContext getDummySSL() {
...omit
}
private static void send() {
HttpGet httpGet = new HttpGet("https://someUrl.com");
try(CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
responseString = EntityUtils.toString(httpResponse.getEntity());
// do something
} else {
throw new Exception();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Итак, в моем основном я буду вызывать HttpHelper.init()
для инициализации HttpClient. Каждый раз, когда я хочу отправить запрос, я буду звонить HttpHelper.send()
. Потому что я не хочу каждый раз создавать новый HttpClient. В конце я вызову HttpHelper.close()
, чтобы закрыть HttpClient.
Мне интересно, как протестировать эти методы void. Моя концепция состоит в том, чтобы создать его CloseableHttpClient
в моем тесте, а затем вызвать HttpHelper.init()
для создания фактического. затем сравните мой ожидаемый и фактический один и тот же. Я прав?
Из-за переменной и методы объявляются как статические. Немного сложно писать модульные тесты. Во многих сообщениях говорится, что статические методы являются плохой практикой. Однако в моем примере я не знаю, как избежать объявления их как статических и сохранить один CloseableHttpClient
экземпляр.
Спасибо!
Комментарии:
1. Боль при модульном тестировании обычно указывает на запах кода. Возможно, перейти
httpClient
наsend()
(sohttpClient
можно легко подделать) или изменить на нестатический и ввестиhttpClient
(sohttpClient
можно легко подделать).2. Здравствуйте, спасибо за ответ. Но если я это сделаю.
httpClient
может быть, невозможно сохранить один экземпляр?
Ответ №1:
Гарантия одного экземпляра в основном решается с помощью одноэлементного шаблона. Общий трюк для модульного тестирования — создать конструктор с защищенной видимостью, в который вы можете поместить аргументы для тестирования. Класс, наконец, может выглядеть так.
public class HttpHelper {
private static HttpHelper INSTANCE = new HttpHelper();
public static HttpHelper getInstance() {
return INSTANCE;
}
private CloseableHttpClient httpClient;
private HttpHelper() {
SSLContext sslContext = getDummySSL();
this(HttpClients.custom().setSSLContext(sslContext).build(), sslContext);
}
protected HttpHelper(CloseableHttpClient httpClient, SSLContext sslContext) {
this.httpClient = httpClient;
}
public void closeHttpClient() throws IOException {
httpClient.close();
}
private static SSLContext getDummySSL() {
...
}
private void send() {
...
}
}
Я бы также переименовал getDummySSL
в createDummySSL
, но это детали.
Ответ №2:
Иметь такой статичный класс плохо, потому что это очень сложно проверить. Я понимаю, почему вы хотели бы этого, но у вас могли бы быть все те же преимущества, подобные этому:
public class HttpHelper {
private static HttpHelper DEFAULT_INSTANCE = null;
private CloseableHttpClient httpClient;
public HttpHelper(CloseableHttpClient httpClient) {
this.httpClient = httpClient;
}
public static void getDeafultInstance() { // this should probably be synchronised for thread safety
if (DEFAULT_INSTANCE == null) {
DEFAULT_INSTANCE = httpClient = HttpClients.custom().setSSLContext(getDummySSL()).build();
}
return DEAFULT_INSTANCE;
}
private static SSLContext getDummySSL() {
...omit
}
public void closeHttpClient() throws IOException {
httpClient.close();
}
private void send() {
HttpGet httpGet = new HttpGet("https://someUrl.com");
try(CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
responseString = EntityUtils.toString(httpResponse.getEntity());
// do something
} else {
throw new Exception();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Тогда вы могли бы модульно протестировать его следующим образом:
public class HttpHelperTest {
@Test
public testSendsRequestToSomeUrl() {
CloseableHttpClient httpClientMock = mock();
when(httpClient.execute(any())).thenReturn(..http_response_where_stauts_code_is_ok..)
HttpHelper httpHelper = new HttpHelper(httpClientMock)
httpHelper.send()
verify(httpClient).execute(new HttpGet("https://someUrl.com"))
}
}
и использовать его в реальном коде, подобном этому:
HttpHelper.getDeafultInstance().send()
PS
Если у вас есть какая-то доступная среда внедрения зависимостей, вы можете вообще избавиться от статических методов.