【问题标题】:Android Unit Test with Retrofit2 and Mockito or Robolectric使用 Retrofit2 和 Mockito 或 Robolectric 进行 Android 单元测试
【发布时间】:2016-06-15 09:22:33
【问题描述】:

我可以测试retrofit2beta4 的真实反应吗?我需要 Mockito 还是 Robolectic?

我的项目中没有活动,它将是一个库,我需要测试服务器是否正确响应。 现在我有这样的代码并卡住了......

@Mock
ApiManager apiManager;

@Captor
private ArgumentCaptor<ApiCallback<Void>> cb;

@Before
public void setUp() throws Exception {
    apiManager = ApiManager.getInstance();
    MockitoAnnotations.initMocks(this);
}

@Test
public void test_login() {
    Mockito.verify(apiManager)
           .loginUser(Mockito.eq(login), Mockito.eq(pass), cb.capture());
    // cb.getValue();
    // assertEquals(cb.getValue().isError(), false);
}

我可以做出虚假的回应,但我需要测试真实的。是成功吗?身材对吗? 你能帮我写代码吗?

【问题讨论】:

    标签: android unit-testing mockito robolectric retrofit2


    【解决方案1】:

    测试真实的服务器请求通常不是一个好主意。有关该主题的有趣讨论,请参阅 this blog post。根据作者的说法,使用你的真实服务器是一个问题,因为:

    • 另一个可能间歇性故障的移动部件
    • 需要一些 Android 域之外的专业知识来部署服务器并保持更新
    • 难以触发错误/边缘情况
    • 测试执行缓慢(仍在进行 HTTP 调用)

    您可以通过使用 OkHttp 的MockWebServer 等模拟服务器来模拟真实的响应结果来避免上述所有问题。例如:

    @Test
    public void test() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
    
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(mockWebServer.url("").toString())
                //TODO Add your Retrofit parameters here
                .build();
    
        //Set a response for retrofit to handle. You can copy a sample
        //response from your server to simulate a correct result or an error.
        //MockResponse can also be customized with different parameters
        //to match your test needs
        mockWebServer.enqueue(new MockResponse().setBody("your json body"));
    
        YourRetrofitService service = retrofit.create(YourRetrofitService.class);
    
        //With your service created you can now call its method that should 
        //consume the MockResponse above. You can then use the desired
        //assertion to check if the result is as expected. For example:
        Call<YourObject> call = service.getYourObject();
        assertTrue(call.execute() != null);
    
        //Finish web server
        mockWebServer.shutdown();
    }
    

    如果您需要模拟网络延迟,您可以按如下方式自定义您的响应:

    MockResponse response = new MockResponse()
        .addHeader("Content-Type", "application/json; charset=utf-8")
        .addHeader("Cache-Control", "no-cache")
        .setBody("{}");
    response.throttleBody(1024, 1, TimeUnit.SECONDS);
    

    或者,您可以使用 MockRetrofitNetworkBehavior 来模拟 API 响应。请参阅here 的使用示例。

    最后,如果您只想测试改造服务,最简单的方法是创建它的模拟版本,为您的测试发出模拟结果。比如你有如下GitHub服务接口:

    public interface GitHub {
        @GET("/repos/{owner}/{repo}/contributors")
        Call<List<Contributor>> contributors(
            @Path("owner") String owner,
            @Path("repo") String repo);
    }
    

    然后您可以为您的测试创建以下MockGitHub

    public class MockGitHub implements GitHub {
        private final BehaviorDelegate<GitHub> delegate;
        private final Map<String, Map<String, List<Contributor>>> ownerRepoContributors;
    
        public MockGitHub(BehaviorDelegate<GitHub> delegate) {
            this.delegate = delegate;
            ownerRepoContributors = new LinkedHashMap<>();
    
            // Seed some mock data.
            addContributor("square", "retrofit", "John Doe", 12);
            addContributor("square", "retrofit", "Bob Smith", 2);
            addContributor("square", "retrofit", "Big Bird", 40);
            addContributor("square", "picasso", "Proposition Joe", 39);
            addContributor("square", "picasso", "Keiser Soze", 152);
        }
    
        @Override public Call<List<Contributor>> contributors(String owner, String repo) {
            List<Contributor> response = Collections.emptyList();
            Map<String, List<Contributor>> repoContributors = ownerRepoContributors.get(owner);
            if (repoContributors != null) {
                List<Contributor> contributors = repoContributors.get(repo);
                if (contributors != null) {
                    response = contributors;
                }
            }
            return delegate.returningResponse(response).contributors(owner, repo);
        }
    }
    

    然后您可以在测试中使用MockGitHub 来模拟您正在寻找的响应类型。有关完整示例,请参阅此 Retrofit exampleSimpleServiceSimpleMockService 的实现。

    说了这么多,如果你绝对必须连接到实际的服务器,你可以将 Retrofit 设置为与自定义 ImmediateExecutor 同步工作:

    public class ImmediateExecutor implements Executor {
        @Override public void execute(Runnable command) {
            command.run();
        }
    }
    

    然后将其应用于您在构建改造时使用的OkHttpClient

    OkHttpClient client = OkHttpClient.Builder()
            .dispatcher(new Dispatcher(new ImmediateExecutor()))
            .build();
    
    Retrofit retrofit = new Retrofit.Builder()
            .client(client)
            //Your params
            .build();
    

    【讨论】:

    • 我提供了替代方案,因为我认为在真实服务器上进行测试没有意义。你不能确定测试是否会在不同的位置为不同的用户工作,你不能轻易地测试连接问题等等。我认为服务器不是属于您的图书馆的东西,不应该被这样对待。这就是为什么最好改为测试服务器 responses。如果您使用MockWebServer,您可以让您的测试像连接到真实服务器一样运行。您的图书馆不会知道其中的区别。
    • 如果我做出虚假的成功响应 - 我将获得成功测试。这个测试有什么意义?我只需要知道服务器响应何时更改(通过测试),以更新我的库以获得新的响应。如果我做出虚假的回应,我永远不会知道发生了什么变化。
    • 这就是为什么你需要做出成功的回应和糟糕的回应。并测试您的图书馆如何对服务器可以向您的图书馆提供的不同类型的响应做出反应。使用MockWebServer,您可以并且应该测试尽可能多的不同类型的响应。
    • 如果您需要测试服务器没有更改模型,那么您应该测试的是服务器本身。您的 api 测试应该只依赖于您的实际 api 代码并模拟所有服务器响应(正确和不正确的响应)
    • 如果您不针对生产环境进行测试,那么端点测试的意义何在。如果那部分失败了,那就太好了!你可以告诉你该死的 API 团队!否则你只是在测试改造......毫无意义。
    【解决方案2】:

    答案比我想象的要简单:

    使用 CountDownLatch 让您的测试等到您调用 countDown()

    public class SimpleRetrofitTest {
    
    private static final String login = "your@login";
    private static final String pass = "pass";
    private final CountDownLatch latch = new CountDownLatch(1);
    private ApiManager apiManager;
    private OAuthToken oAuthToken;
    
    @Before
    public void beforeTest() {
        apiManager = ApiManager.getInstance();
    }
    
    @Test
    public void test_login() throws InterruptedException {
        Assert.assertNotNull(apiManager);
        apiManager.loginUser(login, pass, new ApiCallback<OAuthToken>() {
            @Override
            public void onSuccess(OAuthToken token) {
                oAuthToken = token;
                latch.countDown();
            }
    
            @Override
            public void onFailure(@ResultCode.Code int errorCode, String errorMessage) {
                latch.countDown();
            }
        });
        latch.await();
        Assert.assertNotNull(oAuthToken);
    }
    
    @After
    public void afterTest() {
        oAuthToken = null;
    }}
    

    【讨论】:

      【解决方案3】:

      除非您正在测试 QA 服务器 API,否则出于多种原因,这是一个坏主意。

      • 首先,您在生产数据库中填充了错误/虚假信息 数据
      • 使用服务器资源,当它们可以更好地用于服务时 有效的请求

      使用 Mockito 或模拟您的回复的最佳方式

      另外,如果您必须测试您的生产 API,请测试一次并添加 @Ignore 注释。这样它们就不会一直运行并且不会向您的服务器发送虚假数据垃圾邮件,并且您可以在您觉得 api 行为不正确时使用它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-13
        • 2020-02-22
        • 2017-07-15
        • 1970-01-01
        • 1970-01-01
        • 2017-09-19
        • 2019-04-27
        • 1970-01-01
        相关资源
        最近更新 更多