使用square retrofit framework时,模拟服务器进行测试的最佳方法是什么。
潜在方式:
创建一个新的改造client并将其设置在RestAdapter.Builder()。setClient()中。这涉及解析Request对象并将json作为Response对象返回。
将此带注释的接口实现为模拟类,并使用它来代替RestAdapter.create()提供的版本(不测试gson序列化)
理想情况下,我希望模拟服务器提供json响应,以便我可以同时测试gson序列化。
非常感谢任何例子。
答案 0 :(得分:91)
由于创建MockClient
类并从Client
实现它的旧机制不再适用于Retrofit 2.0,因此我在这里描述了一种新的方法。您现在需要做的就是为OkHttpClient添加自定义拦截器,如下所示。 FakeInterceptor
类只覆盖intercept
方法,如果应用程序处于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;
}
}
上项目的源代码
答案 1 :(得分:78)
我决定尝试方法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字符串,而无需联系真实的服务器!
答案 2 :(得分:19)
测试对象的JSON反序列化(可能是TypeAdapters
?)似乎是一个需要单独进行单元测试的单独问题。
我亲自使用版本2。它提供了类型安全,重构友好的代码,可以轻松调试和更改。毕竟,如果你没有为测试创建它们的替代版本,那么将API声明为接口有什么用呢!胜利的多态性。
另一个选择是使用Java Proxy
。这实际上是Retrofit(当前)实现其底层HTTP交互的方式。这无疑需要更多的工作,但可以实现更加动态的模拟。
答案 3 :(得分:8)
在转移到真实服务器之前,我是Apiary.io的忠实粉丝用于模拟API。
您也可以使用flat .json文件并从文件系统中读取它们。
您还可以使用Twitter,Flickr等可公开访问的API。
以下是一些有关Retrofit的其他重要资源。
幻灯片: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
答案 4 :(得分:7)
你也可以使用像Squareup这样的Webservermock! - &GT; https://github.com/square/okhttp/tree/master/mockwebserver
答案 5 :(得分:7)
首先,创建您的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!!!
});
}
感谢阅读。
答案 6 :(得分:6)
Mockery(免责声明:我是作者)专为这项任务而设计。
Mockery是一个模拟/测试库,专注于验证网络层,内置支持Retrofit。它根据给定Api的规格自动生成JUnit测试。这个想法不是必须手动编写任何测试;既没有实现模拟服务器响应的接口。
答案 7 :(得分:5)
添加@Alec的答案,我已经扩展了模拟客户端,直接从资产文件夹中的文本文件获取响应,具体取决于请求URL。
实施例
@POST("/activate")
public void activate(@Body Request reqdata, Callback callback);
这里模拟客户端知道激活的URL是激活的,并在assets文件夹中查找名为activate.txt的文件。 它从assets / 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
答案 8 :(得分:1)
https://jsonplaceholder.typicode.com/
如果要测试自定义的响应有效负载,以上两个可能不符合您的要求,则可以尝试邮递员模拟服务器。 设置起来非常容易,并且可以灵活地定义自己的请求和响应有效负载。
https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE
答案 9 :(得分:0)
对我而言,由于灵活性,自定义Retrofit Client非常棒。特别是当你使用任何DI框架时,你可以快速简单地打开/关闭模拟。我正在使用Dagger提供的自定义客户端进行单元和集成测试。
编辑: 在这里,您可以找到模拟改造的示例 https://github.com/pawelByszewski/retrofitmock
答案 10 :(得分:0)
通过 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的作者)