簡體   English   中英

為單元測試創​​建模擬 - 改造

[英]Creating mocks for unit test - retrofit

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

預測服務.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"));
    }
}

實用程序

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()));

上面一行表示當在模擬對象上調用 execute 函數時返回一個空的預測列表。

這樣我們就可以模擬 API 響應,而不必進行實際的 API 調用。

編輯

您還可以使用Retrofit Mock模擬您的改造 API。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM