java - 为单元测试创建模拟 - 改造
问题描述
有命令行应用程序使用公共 API 显示明天的预测
示例输出可能如下:
Tomorrow (2019/05/01) in city XYZ:
Clear
Temp: 26.5 °C
Wind: 7.6 mph
Humidity: 61%
问题:您将如何创建一个测试用例,以使测试不接触实际服务并在没有 Internet 的情况下工作。
我尝试为它创建junit测试并且它工作正常,直到我直接使用api。
有人可以帮助我如何为我的单元测试创建一个模拟。
应用程序.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);
}
城市.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;
}
}
预测.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("%s\nTemp: %.1f °C\nWind: %.1f mph\nHumidity: %d%%",
weatherState, temperature, windSpeed, humidity);
}
}
路径日期.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"));
}
}
实用程序.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;
}
}
解决方案
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()));
上面这行表示在模拟对象上调用执行函数时返回一个空的预测列表。
这样我们就可以模拟 API 响应,而不必进行实际的 API 调用。
编辑:
你也可以使用Retrofit Mock来模拟你的改造 API 。
推荐阅读
- swift - Apple 开发者 SwiftUI 教程的预览问题 - 与 UIKit 的框架集成接口
- r - 在具有不同条数的ggplot堆叠条形图中保持恒定条形宽度
- xslt-1.0 - 如何在页面 xsl fo 上将行保持在一起?
- perl - 如何在 perl 中运行多个测试脚本,并从另一个脚本中提供参数?
- android - 收入猫购买
- python - 使用 input() 创建一个程序以在 python 中绘制 excel 数据以选择文件
- mysql - MySQL查询返回错误的行数?
- python - 如何将字符串转换为日期格式
- r - 用其他列中匹配类别的同一数据表中的值替换 NA
- active-directory - Active Directory LDAP 上的 baseDN 问题,找不到对象