Answers:
由于旧的机制(例如创建MockClient
类和从中实现类)Client
在Retrofit 2.0中不再起作用,因此在此我描述一种新的实现方式。您现在需要做的就是为OkHttpClient添加自定义拦截器,如下所示。FakeInterceptor
类仅覆盖intercept
method,如果应用程序处于DEBUG
模式下,则返回给定的JSON。
public final class RestClient {
private static IRestService mRestService = null;
public static IRestService getClient() {
if(mRestService == null) {
final OkHttpClient client = new OkHttpClient();
// ***YOUR CUSTOM INTERCEPTOR GOES HERE***
client.interceptors().add(new FakeInterceptor());
final Retrofit retrofit = new Retrofit.Builder()
// Using custom Jackson Converter to parse JSON
// Add dependencies:
// com.squareup.retrofit:converter-jackson:2.0.0-beta2
.addConverterFactory(JacksonConverterFactory.create())
// Endpoint
.baseUrl(IRestService.ENDPOINT)
.client(client)
.build();
mRestService = retrofit.create(IRestService.class);
}
return mRestService;
}
}
public interface IRestService {
String ENDPOINT = "http://www.vavian.com/";
@GET("/")
Call<Teacher> getTeacherById(@Query("id") final String id);
}
public class FakeInterceptor implements Interceptor {
// FAKE RESPONSES.
private final static String TEACHER_ID_1 = "{\"id\":1,\"age\":28,\"name\":\"Victor Apoyan\"}";
private final static String TEACHER_ID_2 = "{\"id\":1,\"age\":16,\"name\":\"Tovmas Apoyan\"}";
@Override
public Response intercept(Chain chain) throws IOException {
Response response = null;
if(BuildConfig.DEBUG) {
String responseString;
// Get Request URI.
final URI uri = chain.request().url().uri();
// Get Query String.
final String query = uri.getQuery();
// Parse the Query String.
final String[] parsedQuery = query.split("=");
if(parsedQuery[0].equalsIgnoreCase("id") && parsedQuery[1].equalsIgnoreCase("1")) {
responseString = TEACHER_ID_1;
}
else if(parsedQuery[0].equalsIgnoreCase("id") && parsedQuery[1].equalsIgnoreCase("2")){
responseString = TEACHER_ID_2;
}
else {
responseString = "";
}
response = new Response.Builder()
.code(200)
.message(responseString)
.request(chain.request())
.protocol(Protocol.HTTP_1_0)
.body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes()))
.addHeader("content-type", "application/json")
.build();
}
else {
response = chain.proceed(chain.request());
}
return response;
}
}
GitHub上项目的源代码
uri()
下chain.request().uri()
(由我解决了这个String url = chain.request().url().toString();
作为我的情况不同)。2-我要java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once
。我已将此添加到addNetworkInterceptor()
而不是addInterceptor()
。
我决定尝试如下方法1
public class MockClient implements Client {
@Override
public Response execute(Request request) throws IOException {
Uri uri = Uri.parse(request.getUrl());
Log.d("MOCK SERVER", "fetching uri: " + uri.toString());
String responseString = "";
if(uri.getPath().equals("/path/of/interest")) {
responseString = "JSON STRING HERE";
} else {
responseString = "OTHER JSON RESPONSE STRING";
}
return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", responseString.getBytes()));
}
}
并通过以下方式使用它:
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new MockClient());
它运行良好,无需连接真实服务器即可测试json字符串!
IllegalArgumentException url == null
与Retrofit 1.4.1一起使用。
builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
测试对象的JSON反序列化(大概使用TypeAdapters
?)似乎是一个单独的问题,需要单独的单元测试。
我个人使用版本2。它提供了类型安全,易于重构的代码,可以轻松地对其进行调试和更改。毕竟,如果您没有创建API的替代版本进行测试,那么将API声明为接口有什么好处!多态为赢。
另一种选择是使用Java Proxy
。实际上,这就是Retrofit(当前)实现其基础HTTP交互的方式。诚然,这将需要更多的工作,但会允许更多的动态模拟。
您还可以使用来自Squareup的Webservermock之类的东西!-> https://github.com/square/okhttp/tree/master/mockwebserver
我是Apiary.io的忠实拥护者,希望在迁移到真实服务器之前先模拟 API。
您还可以使用平面.json文件,并从文件系统中读取它们。
您还可以使用可公开访问的API,例如Twitter,Flickr等。
这是有关翻新的其他一些重要资源。
幻灯片:https://docs.google.com/presentation/d/12Eb8OPI0PDisCjWne9-0qlXvp_-R4HmqVCjigOIgwfY/edit#slide=id.p
视频:http : //www.youtube.com/watch?v= UtM06W51pPw& feature=g-user-u
示例项目:https://github.com/dustin-graham/ucad_twitter_retrofit_sample
Mockery(免责声明:我是作者)是专为这项任务而设计的。
Mockery是一个模拟/测试库,致力于通过对Retrofit的内置支持来验证网络层。它根据给定Api的规格自动生成JUnit测试。这个想法是不必手动编写任何测试;均未实现用于模拟服务器响应的接口。
首先,创建您的Retrofit接口。
public interface LifeKitServerService {
/**
* query event list from server,convert Retrofit's Call to RxJava's Observerable
*
* @return Observable<HttpResult<List<Event>>> event list from server,and it has been convert to Obseverable
*/
@GET("api/event")
Observable<HttpResult<List<Event>>> getEventList();
}
您的要求者如下:
public final class HomeDataRequester {
public static final String TAG = HomeDataRequester.class.getSimpleName();
public static final String SERVER_ADDRESS = BuildConfig.DATA_SERVER_ADDR + "/";
private LifeKitServerService mServerService;
private HomeDataRequester() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
//using okhttp3 interceptor fake response.
.addInterceptor(new MockHomeDataInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(SERVER_ADDRESS)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.build();
//using okhttp3 inteception to fake response.
mServerService = retrofit.create(LifeKitServerService.class);
//Second choice,use MockRetrofit to fake data.
//NetworkBehavior behavior = NetworkBehavior.create();
//MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit)
// .networkBehavior(behavior)
// .build();
//mServerService = new MockLifeKitServerService(
// mockRetrofit.create(LifeKitServerService.class));
}
public static HomeDataRequester getInstance() {
return InstanceHolder.sInstance;
}
public void getEventList(Subscriber<HttpResult<List<Event>>> subscriber) {
mServerService.getEventList()
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
如果使用第二种选择(使用Retrofit接口模拟服务器数据),则需要进行MockRetrofit,使用代码如下:
public final class MockLifeKitServerService implements LifeKitServerService {
public static final String TAG = MockLifeKitServerService.class.getSimpleName();
private BehaviorDelegate<LifeKitServerService> mDelegate;
private Gson mGson = new Gson();
public MockLifeKitServerService(BehaviorDelegate<LifeKitServerService> delegate) {
mDelegate = delegate;
}
@Override
public Observable<HttpResult<List<Event>>> getEventList() {
List<Event> eventList = MockDataGenerator.generateEventList();
HttpResult<List<Event>> httpResult = new HttpResult<>();
httpResult.setCode(200);
httpResult.setData(eventList);
LogUtil.json(TAG, mGson.toJson(httpResult));
String text = MockDataGenerator.getMockDataFromJsonFile("server/EventList.json");
if (TextUtils.isEmpty(text)) {
text = mGson.toJson(httpResult);
}
LogUtil.d(TAG, "Text:\n" + text);
text = mGson.toJson(httpResult);
return mDelegate.returningResponse(text).getEventList();
}
4.我的数据来自资产文件(Asset / server / EventList.json),文件内容为:
{
"code": 200,
"data": [
{
"uuid": "e4beb3c8-3468-11e6-a07d-005056a05722",
"title": "title",
"image": "http://image.jpg",
"goal": 1500000,
"current": 51233,
"hot": true,
"completed": false,
"createdAt": "2016-06-15T04:00:00.000Z"
}
]
}
5.如果使用okhttp3拦截器,则需要自定义拦截器,如下所示:
public final class MockHomeDataInterceptor implements Interceptor {
public static final String TAG = MockHomeDataInterceptor.class.getSimpleName();
@Override
public Response intercept(Chain chain) throws IOException {
Response response = null;
String path = chain.request().url().uri().getPath();
LogUtil.d(TAG, "intercept: path=" + path);
response = interceptRequestWhenDebug(chain, path);
if (null == response) {
LogUtil.i(TAG, "intercept: null == response");
response = chain.proceed(chain.request());
}
return response;
}
private Response interceptRequestWhenDebug(Chain chain, String path) {
Response response = null;
if (BuildConfig.DEBUG) {
Request request = chain.request();
if (path.equalsIgnoreCase("/api/event")) {
//get event list
response = getMockEventListResponse(request);
}
}
private Response getMockEventListResponse(Request request) {
Response response;
String data = MockDataGenerator.getMockDataFromJsonFile("server/EventList.json");
response = getHttpSuccessResponse(request, data);
return response;
}
private Response getHttpSuccessResponse(Request request, String dataJson) {
Response response;
if (TextUtils.isEmpty(dataJson)) {
LogUtil.w(TAG, "getHttpSuccessResponse: dataJson is empty!");
response = new Response.Builder()
.code(500)
.protocol(Protocol.HTTP_1_0)
.request(request)
//protocol&request be set,otherwise will be exception.
.build();
} else {
response = new Response.Builder()
.code(200)
.message(dataJson)
.request(request)
.protocol(Protocol.HTTP_1_0)
.addHeader("Content-Type", "application/json")
.body(ResponseBody.create(MediaType.parse("application/json"), dataJson))
.build();
}
return response;
}
}
6.最后,您可以使用以下代码请求服务器:
mHomeDataRequester.getEventList(new Subscriber<HttpResult<List<Event>>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
LogUtil.e(TAG, "onError: ", e);
if (mView != null) {
mView.onEventListLoadFailed();
}
}
@Override
public void onNext(HttpResult<List<Event>> httpResult) {
//Your json result will be convert by Gson and return in here!!!
});
}
谢谢阅读。
通过@Alec添加答案,我扩展了模拟客户端,以根据请求URL直接从资产文件夹中的文本文件获取响应。
防爆
@POST("/activate")
public void activate(@Body Request reqdata, Callback callback);
在这里,模拟客户端了解被触发的URL是激活的,并在资产文件夹中查找名为activate.txt的文件。它从资产/activate.txt文件读取内容,并将其作为对API的响应进行发送。
这是扩展 MockClient
public class MockClient implements Client {
Context context;
MockClient(Context context) {
this.context = context;
}
@Override
public Response execute(Request request) throws IOException {
Uri uri = Uri.parse(request.getUrl());
Log.d("MOCK SERVER", "fetching uri: " + uri.toString());
String filename = uri.getPath();
filename = filename.substring(filename.lastIndexOf('/') + 1).split("?")[0];
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
InputStream is = context.getAssets().open(filename.toLowerCase() + ".txt");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
String responseString = new String(buffer);
return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", responseString.getBytes()));
}
}
有关详细说明,您可以查看我的博客
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients
MockClient
文件中,使用robolectric编写了一个测试类。但是我无法从json文件获得任何响应。
https://jsonplaceholder.typicode.com/
如果要测试自定义的响应有效负载,以上两个可能不符合您的要求,则可以尝试邮递员模拟服务器。设置非常容易,并且可以灵活地定义自己的请求和响应有效负载。
https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE
现在,借助Mockinizer,使用Retrofit来模拟 api调用变得更加轻松,这使得使用MockWebServer变得非常简单:
import com.appham.mockinizer.RequestFilter
import okhttp3.mockwebserver.MockResponse
val mocks: Map<RequestFilter, MockResponse> = mapOf(
RequestFilter("/mocked") to MockResponse().apply {
setResponseCode(200)
setBody("""{"title": "Banana Mock"}""")
},
RequestFilter("/mockedError") to MockResponse().apply {
setResponseCode(400)
}
)
只需创建一个RequestFilter和MockResponses的映射,然后将其插入到OkHttpClient构建器链中即可:
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.mockinize(mocks) // <-- just plug in your custom mocks here
.build()
您不必担心配置MockWebServer等。只需添加您的模拟,其余所有工作都由Mockinizer完成。
(免责声明:我是Mockinizer的作者)
对我而言,自定义Retrofit Client非常出色,因为它具有灵活性。特别是当您使用任何DI框架时,您都可以快速简单地打开/关闭模拟。我还在单元测试和集成测试中使用了Dagger提供的自定义客户端。
编辑:在这里,您可以找到模拟改造的示例 https://github.com/pawelByszewski/retrofitmock
square-oss
什么?似乎多余retrofit
。