#java #api #mocking #retrofit
#java #API #издевательство #модернизация
Вопрос:
Существует приложение командной строки для отображения прогноза на завтра с использованием общедоступного API
Пример вывода может быть следующим:
Tomorrow (2019/05/01) in city XYZ:
Clear
Temp: 26.5 °C
Wind: 7.6 mph
Humidity: 61%
Вопрос: Как вы создадите тестовый пример, чтобы тесты не касались реального сервиса и работали без Интернета.
Я попытался создать тест junit для того же, и он работал нормально до тех пор, пока я не использую api напрямую.
Может кто-нибудь, пожалуйста, помогите, как я могу создать макет для моего модульного тестирования.
App.java
import api.ForecastServiceImpl;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.io.IOException;
import java.time.LocalDate;
public class App {
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Pass city name as an argument");
System.exit(1);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.metaweather.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
LocalDate tomorrow = LocalDate.now().plusDays(1);
ForecastServiceImpl service = new ForecastServiceImpl(retrofit);
System.out.println(service.getForecast(args[0], tomorrow));
}
}
ForecastServiceImpl.java
package api;
import model.City;
import model.Forecast;
import retrofit2.Call;
import retrofit2.Retrofit;
import util.PathDate;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
public class ForecastServiceImpl {
private Retrofit retrofit;
public ForecastServiceImpl(Retrofit retrofit) {
this.retrofit = retrofit;
}
public String getForecast(String cityName, LocalDate date) throws IOException {
PathDate pathDate = new PathDate(date);
ForecastService service = retrofit.create(ForecastService.class);
Call<List<City>> findCityCall = service.findCityByName(cityName.toLowerCase());
City city = Objects.requireNonNull(findCityCall.execute().body())
.stream()
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Can't find city id for %s", cityName)));
Call<List<Forecast>> forecastCall = service.getForecast(city.getWoeid(), pathDate);
Forecast forecast = Objects.requireNonNull(forecastCall.execute().body())
.stream()
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Can't get forecast for %s", cityName)));
return String.format("Weather on (%s) in %s:n%s", pathDate, city.getTitle(), forecast);
}
}
ForecastService.java
package api;
import model.City;
import model.Forecast;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
import util.PathDate;
import java.util.List;
public interface ForecastService {
@GET("/api/location/{city_id}/{date}/")
Call<List<Forecast>> getForecast(@Path("city_id") Long cityId, @Path("date") PathDate date);
@GET("/api/location/search/")
Call<List<City>> findCityByName(@Query("query") String city);
}
City.java
package model;
public class City {
private String title;
private Long woeid;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Long getWoeid() {
return woeid;
}
public void setWoeid(Long woeid) {
this.woeid = woeid;
}
}
Forecast.java
package model;
import com.google.gson.annotations.SerializedName;
public class Forecast {
private Long id;
@SerializedName("weather_state_name")
private String weatherState;
@SerializedName("wind_speed")
private Double windSpeed;
@SerializedName("the_temp")
private Double temperature;
private Integer humidity;
public Long getId() {
return id;
}
public Forecast setId(Long id) {
this.id = id;
return this;
}
public String getWeatherState() {
return weatherState;
}
public Forecast setWeatherState(String weatherState) {
this.weatherState = weatherState;
return this;
}
public Double getWindSpeed() {
return windSpeed;
}
public Forecast setWindSpeed(Double windSpeed) {
this.windSpeed = windSpeed;
return this;
}
public Double getTemperature() {
return temperature;
}
public Forecast setTemperature(Double temperature) {
this.temperature = temperature;
return this;
}
public Integer getHumidity() {
return humidity;
}
public Forecast setHumidity(Integer humidity) {
this.humidity = humidity;
return this;
}
@Override
public String toString() {
return String.format("%snTemp: %.1f °CnWind: %.1f mphnHumidity: %d%%",
weatherState, temperature, windSpeed, humidity);
}
}
PathDate.java
package util;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class PathDate {
private final LocalDate date;
public PathDate(LocalDate date) {
this.date = date;
}
@Override public String toString() {
return date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
}
}
Utils.java
package util;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Utils {
public static byte[] readResourceFileToBytes(String filename) {
byte[] fileBytes = new byte[0];
try {
Path path = Paths.get(Utils.class.getClassLoader().getResource(filename).toURI());
fileBytes = Files.readAllBytes(path);
} catch (URISyntaxException|IOException|NullPointerException e) {
e.printStackTrace();
}
return fileBytes;
}
}
Комментарии:
1. Вы хотите правильно написать тест для
getForecast
функции?2. Да, правильно для getForecast
3. @DeepakPatankar не могли бы вы помочь мне с примером тестового примера?
Ответ №1:
service.getForecast(city.getWoeid(), pathDate);
Возвращает нам Call<List<Forecast>>
объект. И когда мы вызываем execute
этот объект, тогда выполняется фактический вызов API. Поскольку мы не хотим выполнять фактический вызов API, мы можем попробовать издеваться над Call<List<Forecast>>
объектом.
Мы можем издеваться над Call
классом, например
Call<List<Forecast>> mockedListForeCast = mock(Call.class);
Приведенное выше утверждение создает макет объекта Call<List<Forecast>>
. Мы можем использовать when
для определения того, что должно происходить при вызове метода для макетируемого объекта.
// here I am returning the singleton list, you can return a list of forecast
when(mockedListForeCast.execute()).thenReturn(Response.success(Collections.singletonList()));
В приведенной выше строке говорится, что возвращает пустой список прогноза, когда функция execute была вызвана для модифицированного объекта.
Таким образом, мы имитируем ответ API, и нам не нужно выполнять фактический вызов API.
Редактировать:
Вы также можете издеваться над своим модифицированным API, используя модифицированный макет.
Комментарии:
1. Привет, @user1873274, вот как я написал модульные тесты в своем коде. Не могли бы вы проверить, работает ли это и для вас? Возможно, вам потребуется изменить некоторую часть строк в приведенном выше ответе в соответствии с вашим случаем.
2. Спасибо @Deepak, но один вопрос, пожалуйста, объясните это немного подробнее, поскольку я новичок в этом. Должен ли я создать макет метода, в котором я должен использовать класс call для макетирования, а затем определять макет вывода?
3. Конечно, добавит объяснение
4. Привет, @user1873274, я обновил свой ответ. Пожалуйста, дайте мне знать, если это поможет.
5. я понял концепцию, и это было полезно. Нужно ли мне создавать макет метода в самом тестовом классе? или это должно быть в отдельном классе? Извините, что задаю слишком простые вопросы.