方形改装服务器模拟测试


97

什么是嘲笑使用时,测试服务器的最好办法广场改造框架

可能的方式:

  1. 创建一个新的改装客户端,并在RestAdapter.Builder()。setClient()中进行设置。这涉及解析Request对象并返回json作为Response对象。

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

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

任何例子将不胜感激。


@JakeWharton,目的是square-oss什么?似乎多余retrofit
查尔斯

@Alec Holmes:您解决了问题吗?
AndiGeeky

Answers:


104

模拟翻新2.0请求测试

由于旧的机制(例如创建MockClient类和从中实现类)Client在Retrofit 2.0中不再起作用,因此在此我描述一种新的实现方式。您现在需要做的就是为OkHttpClient添加自定义拦截器,如下所示FakeInterceptor类仅覆盖interceptmethod,如果应用程序处于DEBUG模式下,则返回给定的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;
    }
}

GitHub上项目的源代码


9
为了避免UnsupportedOperationException,请使用OkHttpClient.Builder。最后的OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new FakeInterceptor()).build();
约翰

4
有两个问题,我有:1没有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()
Hesam

2
使用chain.request()。url()。uri();
Amol Gupta

如何模拟401错误以测试httpClient.authenticator方法?通过仅放置代码“ 401”的身份验证方法就不会调用。我该如何处理?
马赫迪

我采用了伪造的拦截器方法来将Web api模拟到一个新的水平,并为此发布了一个小库,以使其更加轻松和便捷。见 github.com/donfuxx/Mockinizer
donfuxx

85

我决定尝试如下方法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字符串!


我已经更新了不推荐使用的Response构造函数,因为旧的不推荐使用,它IllegalArgumentException url == null与Retrofit 1.4.1一起使用。
Dan J

1
还需要向构建器添加一个端点:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogression 2014年

我已经扩展了上面的模拟客户端,以根据URL请求从资产文件夹中的文件中获取响应。
praveena_kd 2015年

21
现在,Retrofit 2将OkHttpClient用于客户端层,并且此代码不起作用。¿任何想法如何使OkHttpClient模拟?可能是关于扩展和覆盖它的全部,但是我不确定如何实现。
GuillermoMP 2015年

1
您是否也可以基于Retrofit2更新答案?谢谢
Hesam

20

测试对象的JSON反序列化(大概使用TypeAdapters?)似乎是一个单独的问题,需要单独的单元测试。

我个人使用版本2。它提供了类型安全,易于重构的代码,可以轻松地对其进行调试和更改。毕竟,如果您没有创建API的替代版本进行测试,那么将API声明为接口有什么好处!多态为赢。

另一种选择是使用Java Proxy。实际上,这就是Retrofit(当前)实现其基础HTTP交互的方式。诚然,这将需要更多的工作,但会允许更多的动态模拟。


这也是我的首选方式。如上所述,调试起来要简单得多,然后必须直接与响应主体进行处理。@alec如果要测试GSON序列化,请生成/读入json字符串,然后使用gson对象进行反序列化。在我的领导下,我相信这就是Retrofit所做的。
loeschg 2014年

@JakeWharton您能否提供一个简短的示例说明呢?我在可视化方面遇到了麻烦...谢谢!
uncle_tex



8

我是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


7

Mockery(免责声明:我是作者)是专为这项任务而设计的。

Mockery是一个模拟/测试库,致力于通过对Retrofit的内置支持来验证网络层。它根据给定Api的规格自动生成JUnit测试。这个想法是不必手动编写任何测试;均未实现用于模拟服务器响应的接口。


7
  1. 首先,创建您的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();
    }
  2. 您的要求者如下:

    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. 如果使用第二种选择(使用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!!!
    });
}

谢谢阅读。


5

通过@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


嗨,当我使用robolectric编写测试类并使用模拟客户端模拟改造api时,它没有给出任何响应。你能指导我如何做到这一点。
多莉(Dory)2015年

嗨,@ Dory,请确保您在资源文件夹中包含URL结尾部分和文件名。例如,假设您的URL如下(在此处使用Reftrofit)@POST(“ / redeemGyft”)public void redeemGyft(@Body MposRequest reqdata,Callback <RedeemGyftResponse> callback); 那么资产文件夹中相应的文件名为redeemgyft.txt
praveena_kd 2015年

我给了静态文件名,在我的MockClient文件中,使用robolectric编写了一个测试类。但是我无法从json文件获得任何响应。
多莉(Dory)2015年

如果您将文件保留在资产文件夹中,则应将其选中。
praveena_kd 2015年

1

JSONPlaceholder:用于测试和原型制作的假在线REST API

https://jsonplaceholder.typicode.com/

ReqresIn:另一个在线REST API

https://reqres.in/

邮差模拟服务器

如果要测试自定义的响应有效负载,以上两个可能不符合您的要求,则可以尝试邮递员模拟服务器。设置非常容易,并且可以灵活地定义自己的请求和响应有效负载。

在此处输入图片说明 https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE


1

现在,借助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的作者)


0

对我而言,自定义Retrofit Client非常出色,因为它具有灵活性。特别是当您使用任何DI框架时,您都可以快速简单地打开/关闭模拟。我还在单元测试和集成测试中使用了Dagger提供的自定义客户端。

编辑:在这里,您可以找到模拟改造的示例 https://github.com/pawelByszewski/retrofitmock

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.