1. 在应用的test目录(app/src/test/)下新建 resources/api-response目录,然后将各个接口的json文件放入在该目录文件下
eg: app/src/test/resources/api-response/popular-movies.json
2. 添加测试依赖库
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
testImplementation "android.arch.core:core-testing:1.1.1"
testImplementation "com.squareup.okhttp3:mockwebserver:3.8.1"
3. 编写测试用例
@RunWith(JUnit4.class)
public class SexServiceTest {
@Rule
public InstantTaskExecutorRule taskExecutorRule = new InstantTaskExecutorRule();
private GirlService service;
private MockWebServer mockWebServer;
@Before
public void createService() throws IOException {
mockWebServer = new MockWebServer();
service = new Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.client(getHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(new LiveDataCallAdapterFactory())
.build()
.create(GirlService.class);
}
@Test
public void testGetPopularMovies() throws IOException, InterruptedException {
enqueueResponse("popular-movies.json");
ServerResponse<IndexEntity> response = LiveDataTestUtil.getValue(service.getIndex("123456")).body;
RecordedRequest request = mockWebServer.takeRequest();
assertThat(request.getPath(), is("/index"));
System.out.println(request.getHeaders().toString());
System.out.println(request.getRequestUrl().toString());
assertNotNull(response.data);
List<IndexEntity> lists = response.data.getLists();
assertTrue(lists.size() > 0);
}
@After
public void stopService() throws Exception {
mockWebServer.shutdown();
}
private void enqueueResponse(String fileName) throws IOException {
enqueueResponse(fileName, Collections.emptyMap());
}
private void enqueueResponse(String fileName, Map<String, String> headers) throws IOException {
InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("api-response/" + fileName);
BufferedSource source = Okio.buffer(Okio.source(inputStream));
MockResponse mockResponse = new MockResponse();
for (Map.Entry<String, String> header : headers.entrySet()) {
mockResponse.addHeader(header.getKey(), header.getValue());
}
mockWebServer.enqueue(mockResponse
.setBody(source.readString(StandardCharsets.UTF_8)));
}
private OkHttpClient getHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
}
public class LiveDataTestUtil {
public static <T> T getValue(LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
@Override
public void onChanged(@Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
public interface GirlService {
@Headers({
"Content-Type: application/json; charset=utf-8",
"Accept: application/json"
})
@POST("index")
LiveData<ApiResponse<ServerResponse<IndexEntity>>> getIndex(@Header("token") String token);
}
public class LiveDataCallAdapterFactory extends CallAdapter.Factory {
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != LiveData.class) {
return null;
}
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawObservableType = getRawType(observableType);
if (rawObservableType != ApiResponse.class) {
throw new IllegalArgumentException("type must be a resource");
}
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalArgumentException("resource must be parameterized");
}
Type bodyType = getParameterUpperBound(0, (ParameterizedType) observableType);
return new LiveDataCallAdapter<>(bodyType);
}
}
服务器返回响应的泛型类
/**
* @param <T>
*/
public class ApiResponse<T> {
private static final Pattern LINK_PATTERN = Pattern
.compile("<([^>]*)>[\\s]*;[\\s]*rel=\"([a-zA-Z0-9]+)\"");
private static final Pattern PAGE_PATTERN = Pattern.compile("\\bpage=(\\d+)");
private static final String NEXT_LINK = "next";
public final int code;
@Nullable
public final T body;
@Nullable
public final String errorMessage;
@NonNull
public final Map<String, String> links;
public ApiResponse(Throwable error) {
code = 500;
body = null;
errorMessage = error.getMessage();
links = Collections.emptyMap();
}
public ApiResponse(Response<T> response) {
code = response.code();
if (response.isSuccessful()) {
body = response.body();
errorMessage = null;
} else {
String message = null;
if (response.errorBody() != null) {
try {
message = response.errorBody().string();
} catch (IOException ignored) {
Timber.e(ignored, "error while parsing response");
}
}
if (message == null || message.trim().length() == 0) {
message = response.message();
}
errorMessage = message;
body = null;
}
String linkHeader = response.headers().get("link");
if (linkHeader == null) {
links = Collections.emptyMap();
} else {
links = new ArrayMap<>();
Matcher matcher = LINK_PATTERN.matcher(linkHeader);
while (matcher.find()) {
int count = matcher.groupCount();
if (count == 2) {
links.put(matcher.group(2), matcher.group(1));
}
}
}
}
public boolean isSuccessful() {
return code >= 200 && code < 300;
}
public Integer getNextPage() {
String next = links.get(NEXT_LINK);
if (next == null) {
return null;
}
Matcher matcher = PAGE_PATTERN.matcher(next);
if (!matcher.find() || matcher.groupCount() != 1) {
return null;
}
try {
return Integer.parseInt(matcher.group(1));
} catch (NumberFormatException ex) {
Timber.w("cannot parse next page from %s", next);
return null;
}
}
}