Как написать модульный тест статического метода void

#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() (so httpClient можно легко подделать) или изменить на нестатический и ввести httpClient (so httpClient можно легко подделать).

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

Если у вас есть какая-то доступная среда внедрения зависимостей, вы можете вообще избавиться от статических методов.