테스트를위한 Square Retrofit 서버 모의


97

어떤 것은 사용하는 경우 테스트 서버 조롱하는 가장 좋은 방법 평방 개조 프레임 워크를 .

잠재적 인 방법 :

  1. 새 개조 클라이언트를 만들고 RestAdapter.Builder (). setClient ()에서 설정합니다. 여기에는 Request 객체를 구문 분석하고 json을 Response 객체로 반환하는 작업이 포함됩니다.

  2. 이 주석이 달린 인터페이스를 모의 클래스로 구현하고 RestAdapter.create ()에서 제공하는 버전 대신 사용합니다 (Want test gson serialization).

  3. ?

이상적으로는 mocked 서버가 json 응답을 제공하여 gson 직렬화를 동시에 테스트 할 수 있도록하고 싶습니다.

어떤 예라도 대단히 감사하겠습니다.


@JakeWharton, 목적은 square-oss무엇입니까? 주어진 중복 된 것 같습니다 retrofit.
Charles

@Alec Holmes : 문제를 해결 했습니까?
AndiGeeky

답변:


104

모의 Retrofit 2.0 테스트 요청

MockClient클래스 생성 및 구현과 같은 이전 메커니즘 Client이 더 이상 Retrofit 2.0에서 작동하지 않으므로 여기에서 새로운 방법을 설명합니다. 지금해야 할 일은 아래와 같이 OkHttpClient에 대한 사용자 정의 인터셉터추가하는 것 입니다. FakeInterceptor클래스는 intercept메서드를 재정의 하고 응용 프로그램이 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 ();
John

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

httpClient.authenticator 메서드를 테스트하기 위해 401 오류를 어떻게 모의 할 수 있습니까? 코드 "401"을 넣어서 인증 메소드를 호출하지 않습니다. 어떻게 처리 할 수 ​​있습니까?
Mahdi

나는 가짜 인터셉터 접근 방식을 사용하여 웹 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 문자열을 테스트 할 수 있습니다!


IllegalArgumentException url == nullRetrofit 1.4.1 에서 던지는 이전 생성자가 더 이상 사용되지 않기 때문에 사용 된 응답 생성자를 업데이트했습니다 .
Dan J

1
또한 빌더에 엔드 포인트를 추가해야합니다.builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogression

위의 모의 클라이언트를 확장하여 URL 요청에 따라 자산 폴더의 파일에서 응답을 가져 왔습니다.
praveena_kd

21
Retrofit 2는 이제 클라이언트 계층에 OkHttpClient를 사용하며이 코드는 작동하지 않습니다. ¿ OkHttpClient 모의를 만드는 방법에 대한 아이디어가 있습니까? 아마 그것은 확장하고 재정의하는 것에 관한 것이지만 어떻게해야할지 모르겠습니다.
GuillermoMP

1
Retrofit2를 기반으로 답변을 업데이트 할 수 있습니까? 감사
Hesam

20

객체에 대한 JSON 역 직렬화 테스트 (아마도 TypeAdapters?를 사용하여)는 별도의 단위 테스트가 필요한 별도의 문제처럼 보입니다.

개인적으로 버전 2를 사용합니다. 쉽게 디버깅하고 변경할 수있는 형식 안전하고 리팩터링하기 쉬운 코드를 제공합니다. 결국, 테스트 용으로 대체 버전을 만들지 않는 경우 API를 인터페이스로 선언하는 것이 좋습니다! 승리를위한 다형성.

또 다른 옵션은 Java를 사용하는 것 Proxy입니다. 이것은 실제로 Retrofit (현재)이 기본 HTTP 상호 작용을 구현하는 방법입니다. 이것은 분명히 더 많은 작업이 필요하지만 훨씬 더 역동적 인 모의를 허용합니다.


이것은 또한 내가 선호하는 방법입니다. 위에서 언급 한대로 디버그하는 것이 훨씬 간단하고 응답 본문을 직접 처리해야합니다. @alec GSON 직렬화를 테스트하려면 json 문자열을 생성 / 읽고 gson 객체를 사용하여 역 직렬화합니다. 머릿속에서 나는 그것이 Retrofit이 어쨌든하는 일이라고 믿습니다.
loeschg 2014-06-12

@JakeWharton 이것이 무엇을 원하는지에 대한 간단한 예를 제공 할 수 있습니까? 시각화하는 데 문제가 있습니다 ... 감사합니다!
uncle_tex 2015-09-02



8

저는 실제 서버로 이동하기 전에 API를 조롱하는 Apiary.io 의 열렬한 팬입니다 .

플랫 .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


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. 두 번째 선택 (Mock 서버 데이터에 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라는 파일을 찾습니다. 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에서 확인할 수 있습니다
.


안녕하세요, robolectric을 사용하여 테스트 클래스를 작성하고 mock 클라이언트를 사용하여 retrofit api를 조롱 할 때 응답이 없습니다. 이 작업을 수행하는 방법을 안내해 주시겠습니까?
Dory

안녕 @Dory는 URL 끝 부분과 자산 폴더 안에 파일 이름이 있는지 확인하십시오. 예를 들어, 귀하의 URL이 아래와 같다고 가정 해 봅시다 (여기서 Reftrofit 사용). @POST ( "/ redeemGyft") public void redeemGyft (@Body MposRequest reqdata, Callback <RedeemGyftResponse> callback); 다음 자산 폴더에 correspodning 파일 이름은 redeemgyft.txt입니다
praveena_kd

MockClient파일 에 정적 파일 이름을 지정하고 robolectric을 사용하여 테스트 클래스를 작성했습니다. 하지만 json 파일에서 응답을받을 수 없습니다.
Dory

자산 폴더 안에 파일을 보관했다면 선택해야합니다.
praveena_kd

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

MockWebServer 로 작업 할 수있게 해주는 Mockinizer 를 사용하면 Retrofit으로 API 호출을 모의 하는 것이 훨씬 쉬워 졌습니다 .

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.