如何对 Retrofit api 调用进行单元测试?
Posted
技术标签:
【中文标题】如何对 Retrofit api 调用进行单元测试?【英文标题】:How to unit test Retrofit api calls? 【发布时间】:2015-11-29 05:19:59 【问题描述】:我正在尝试为每个可能的代码块集成单元测试用例。 但是我在为 通过改造进行的 api 调用添加测试用例时遇到了问题。
JUnit 编译器从不执行回调函数中的代码。
还有另一个选项可以使所有 api 调用 同步 以用于测试目的,但对于我的应用程序中的所有情况而言,这并不是可能的。
我该如何解决这个问题?我必须通过任何方式在 api 调用中添加测试用例。
【问题讨论】:
您能否先分享一个示例,说明您最初是如何为 api 调用编写测试用例的? 【参考方案1】:我使用 Mockito、Robolectric 和 Hamcrest 库测试了我的 Retrofit 回调。
首先,在你的模块的 build.gradle 中设置 lib 栈:
dependencies
testCompile 'org.robolectric:robolectric:3.0'
testCompile "org.mockito:mockito-core:1.10.19"
androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
在 jour 项目的全局 build.gradle 中添加以下行到 buildscript 依赖项:
classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1'
然后在 Android Studio 中进入“Build Variants”菜单(要快速找到它,按 Ctrl+Shift+A 并搜索它),并将“Test Artifact”选项切换为“Unit Tests”。 Android Studio 会将您的测试文件夹切换到“com.your.package (test)”(而不是 androidTest)。
好的。设置完成,是时候写一些测试了!
假设您有一些改造 api 调用来检索需要放入 RecyclerView 等适配器的对象列表。我们想测试适配器是否在成功调用时填充了正确的项目。 为此,我们需要切换您的 Retrofit 接口实现,您可以使用该接口通过模拟进行调用,并利用 Mockito ArgumentCaptor 类进行一些虚假响应。
@Config(constants = BuildConfig.class, sdk = 21,
manifest = "app/src/main/AndroidManifest.xml")
@RunWith(RobolectricGradleTestRunner.class)
public class RetrofitCallTest
private MainActivity mainActivity;
@Mock
private RetrofitApi mockRetrofitApiImpl;
@Captor
private ArgumentCaptor<Callback<List<YourObject>>> callbackArgumentCaptor;
@Before
public void setUp()
MockitoAnnotations.initMocks(this);
ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
mainActivity = controller.get();
// Then we need to swap the retrofit api impl. with a mock one
// I usually store my Retrofit api impl as a static singleton in class RestClient, hence:
RestClient.setApi(mockRetrofitApiImpl);
controller.create();
@Test
public void shouldFillAdapter() throws Exception
Mockito.verify(mockRetrofitApiImpl)
.getYourObject(callbackArgumentCaptor.capture());
int objectsQuantity = 10;
List<YourObject> list = new ArrayList<YourObject>();
for(int i = 0; i < objectsQuantity; ++i)
list.add(new YourObject());
callbackArgumentCaptor.getValue().success(list, null);
YourAdapter yourAdapter = mainActivity.getAdapter(); // Obtain adapter
// Simple test check if adapter has as many items as put into response
assertThat(yourAdapter.getItemCount(), equalTo(objectsQuantity));
通过右键单击测试类并点击运行来继续测试。
就是这样。我强烈建议使用 Robolectric(带有 robolectric gradle 插件)和 Mockito,这些库使测试 android 应用程序变得更加容易。 我从下面的blog post 学到了这个方法。另请参阅this answer。
更新:如果您正在使用 Retrofit 和 RxJava,请查看 my other answer on that。
【讨论】:
我阅读了同一篇博文,发现其中的解释很有帮助。对我来说,一个难点是这个例子。我尝试阅读示例,然后在我的项目中以类似的方式实施测试。我了解到,仅从测试代码 sn-p 并不清楚的是,在博客文章中描述的测试中,有一个 Activity 在 onCreate() 方法中进行网络调用。需要进行此调用,以便在测试中可以运行对 Mockito.verify() 的调用。如果您不在应用程序代码中进行实际的“网络”调用,测试代码将不会按原样运行。 这不是问题的答案 @JemshitIskenderov 怎么来的?愿意指出它的问题吗? 1) 问题是 jUnit,你介绍了 Roboelectric,hamcrest 2) 这是关于异步回调没有被执行,你从来没有回答为什么会发生,问题是什么...... 3) 你刚刚展示你在你的项目中做了什么,所以它不是这个问题的答案 @JemshitIskenderov 你一定错过了“任何关于另一种方法的建议也会有所帮助。”问题的一部分。这就是这个答案 - 一种建议的工作方法。【参考方案2】:如果您使用 .execute() 而不是 .enqueue() 它会使执行同步,因此测试可以正常运行而无需导入 3 个不同的库和添加任何代码或修改构建变体。
喜欢:
public class LoginAPITest
@Test
public void login_Success()
APIEndpoints apiEndpoints = RetrofitHelper.getTesterInstance().create(APIEndpoints.class);
Call<AuthResponse> call = apiEndpoints.postLogin();
try
//Magic is here at .execute() instead of .enqueue()
Response<AuthResponse> response = call.execute();
AuthResponse authResponse = response.body();
assertTrue(response.isSuccessful() && authResponse.getBearer().startsWith("TestBearer"));
catch (IOException e)
e.printStackTrace();
【讨论】:
虽然这是一种有效的方法,但问题指出 还有另一种选择可以使所有 api 调用同步以进行测试,但对于我的应用程序中的所有情况而言,这并非不可能。 mybad mybad mybad 这个解决方案给了我一个非常有用的线索。谢谢 什么是 RetrofitHelper?是自定义类还是依赖类 还想看RetrofitHelper
【参考方案3】:
JUnit 框架从不执行回调函数中的代码,因为执行的主线程在检索到响应之前终止。可以使用CountDownLatch
,如下图:
@Test
public void testApiResponse()
CountDownLatch latch = new CountDownLatch(1);
mApiHelper.loadDataFromBackend(new Callback()
@Override
public void onResponse(Call call, Response response)
System.out.println("Success");
latch.countDown();
@Override
public void onFailure(Call call, Throwable t)
System.out.println("Failure");
latch.countDown();
);
try
latch.await();
catch (InterruptedException e)
e.printStackTrace();
这个test sample 也可能有帮助。
我的建议是不要在 android 应用程序中测试 API 响应。为此有很多外部tools。【讨论】:
【参考方案4】:Junit 不会等待异步任务完成。您可以使用 CountDownLatch(不需要外部库的优雅解决方案)来阻塞线程,直到您收到来自服务器的响应或超时。
您可以使用CountDownLatch。 由于调用了 countDown() 方法,await 方法一直阻塞,直到当前计数达到零,之后所有等待的线程都被释放,任何后续的 await 调用立即返回。
//Step 1: Do your background job
latch.countDown(); //Step 2 : On completion ; notify the count down latch that your async task is done
latch.await(); // Step 3: keep waiting
或者您可以在 await 调用中指定超时
try
latch.await(2000, TimeUnit.MILLISECONDS);
catch (InterruptedException e)
e.printStackTrace();
示例测试用例
void testBackgroundJob()
Latch latch = new CountDownLatch(1);
//Do your async job
Service.doSomething(new Callback()
@Override
public void onResponse()
ACTUAL_RESULT = SUCCESS;
latch.countDown(); // notify the count down latch
// assertEquals(..
);
//Wait for api response async
try
latch.await();
catch (InterruptedException e)
e.printStackTrace();
assertEquals(expectedResult, ACTUAL_RESULT);
【讨论】:
【参考方案5】:如果已经用 rx 和 restful 封装了 retrofit2.0
open class BaseEntity<E> : Serializable
/*result code*/
var status: Int = 0
/**data */
var content: E? = null
和服务器 api 请求类似
@GET(api/url)
fun getData():Observable<BaseEntity<Bean>>
你的服务只回调一个同步请求 Observable
val it = service.getData().blockingSingle()
assertTrue(it.status == SUCCESS_CODE)
【讨论】:
【参考方案6】:作为@Islam Salah said:
JUnit 框架从不执行 CallBack 函数中的代码,因为执行的主线程在检索到响应之前终止。
您可以使用awaitility
解决问题。查看 *** 上的 this 答案。
【讨论】:
以上是关于如何对 Retrofit api 调用进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章
如何对 AngularJS $promise.then 进行单元测试
使用 Retrofit Android 对多个 API 进行常见成功/失败/错误处理的良好设计