首页 > 解决方案 > 为单元测试创​​建模拟 - 改造

问题描述

有命令行应用程序使用公共 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;
    }
}

标签: javaapimockingretrofit

解决方案


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 。


推荐阅读