用于测试的方形改造服务器模拟

使用 方形改装框架方形改装框架模拟服务器进行测试的最佳方法是什么。

潜在方法:

  1. 创建一个新的改进 客户并在 RestAdapter 中设置它。建造者()。SetClient ().这涉及到解析 Request 对象并将 json 作为 Response 对象返回。

  2. 将这个带注释的接口实现为一个模拟类,并使用它来代替 RestAdapter.create ()(不会测试 gson 序列化)提供的版本

理想情况下,我希望模拟服务器提供 json 响应,这样我就可以同时测试 gson 序列化。

任何例子我都非常感激。

79798 次浏览

I decided to try method 1 as follows

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

And using it by:

RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new MockClient());

It works well and allows you to test your json strings without having to contact the real server!

I am a big fan of Apiary.io for a mocking an API before moving to a real server.

You could also use flat .json files and read them from the file system.

You could also use publicly accessible API's like Twitter, Flickr, etc.

Here are some other great resources about Retrofit.

Slides: https://docs.google.com/presentation/d/12Eb8OPI0PDisCjWne9-0qlXvp_-R4HmqVCjigOIgwfY/edit#slide=id.p

Video: http://www.youtube.com/watch?v=UtM06W51pPw&feature=g-user-u

Example Project: https://github.com/dustin-graham/ucad_twitter_retrofit_sample

Testing JSON deserialization to your objects (presumably with TypeAdapters?) seems like a separate problem that require separate unit tests.

I use version 2 personally. It affords type-safe, refactor-friendly code that can be easily debugged and altered. After all, what good is declaring your API as interfaces if you aren't creating alternate versions of them for testing! Polymorphism for the win.

Another option is using a Java Proxy. This is actually how Retrofit (currently) implements its underlying HTTP interaction. This will admittedly require more work, but would allow for much more dynamic mocks.

For me the custom Retrofit Client is great because of flexibility. Especially when you use any DI framework you can fast and simple turn on/off mock. I am using custom Client provided by Dagger also in unit and integration tests.

Edit: Here you find example of mocking retrofit https://github.com/pawelByszewski/retrofitmock

You can also use something like Webservermock from Squareup! --> https://github.com/square/okhttp/tree/master/mockwebserver

Adding to the answer by @Alec, I have extended the mock client to get the response directly from a text file in asset folder depending on the request URL.

Ex

@POST("/activate")
public void activate(@Body Request reqdata, Callback callback);

Here the mock client, understands that the URL being fired is activate and looks for a file named activate.txt in the assets folder. It reads the content from assets/activate.txt file and sends it as response for the API.

Here is the extended 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()));
}
}

For a detailed explanation you can checkout my blog
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients

Mock Retrofit 2.0 Requests for Testing

As the old mechanisms like creating MockClient class and implementing it from Client are not working anymore with Retrofit 2.0, here I describe a new way of doing that. All what you need to do now is to add your custom interceptors for OkHttpClient like it is shown below. FakeInterceptor class just overrides intercept method and in the case if application is in DEBUG mode return given JSON.

RestClient.java

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;
}
}

IRestService.java

public interface IRestService {


String ENDPOINT = "http://www.vavian.com/";


@GET("/")
Call<Teacher> getTeacherById(@Query("id") final String id);
}

FakeInterceptor.java

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;
}
}

Source code of project on GitHub

Mockery (disclaimer: I’m the author) was designed just for this exactly task.

Mockery is a mocking/testing library focused on validating networking layers with built-in support for Retrofit. It auto-generates JUnit tests based on the specs of a given Api. The idea is not to have to write manually any test; neither implementing interfaces for mocking server responses.

  1. First,create your Retrofit interface.

    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();
    }
    
  2. Your Requester in follow:

    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);
    }
    }
    
  3. If you using the second choice(use Retrofit interface to Mock server data),you need to MockRetrofit,use code follow:

    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.My data is from asset file(Asset/server/EventList.json),this file content is:

    {
"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.If you are using okhttp3 interceptor,you need to self-defined interceptor,like this:

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.Finally,you can request your server with code:

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!!!
});
}

Thanks for reading.

JSONPlaceholder: Fake Online REST API for Testing and Prototyping

https://jsonplaceholder.typicode.com/

ReqresIn: Another Online REST API

https://reqres.in/

Postman mock server

If you want to test customized response payload, the above two might not suit your requirement, then you can try postman mock server. It's quite easy to set up and flexible to define your own request and response payload.

enter image description here https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE

Mocking api calls with Retrofit is now even easier with Mockinizer which makes working with MockWebServer really straight forward:

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)
}


)

Just create a map of RequestFilter and MockResponses and then plug it into your OkHttpClient builder chain:

OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.mockinize(mocks) // <-- just plug in your custom mocks here
.build()

You don't have to worry about configuring MockWebServer etc. Just add your mocks all the rest is done by Mockinizer for you.

(Disclaimer: I am the author of Mockinizer)