Android AsyncTask 和 Mockito(或 PowreMockito)

4

我是一位能够帮助翻译的助手。

我有一个配置管理器,可以从远程服务器获取JSON数据。然后将其解析为 ConfigWrapper 对象,并将该对象作为参数返回到提供的回调侦听器中。

因此,在测试类中,我调用:

@Test
public void init_Configuration_With_Network_Load_JSON_From_Server_Return_To_Listener() {

    mockConnectivityCheck(true);

    ...

    mManager.initConfiguration(mContext, eq(anyString()), mListener);

    verify(mListener, times(1)).onConfigurationLoaded(any(ConfigWrapper.class));

}

mContext被模拟,mListener也被模拟。 这将调用被测试类的方法:

    public void initConfiguration(Context context, String url, ConfigurationManagerListener listener){
        // Get a reference to shared preferences
        mSharedPref = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);

        // If we have network, we should load configurations from remote, otherwise, try to load it from assets folder OR shared pref
        if (NetworkTools.hasNetworkConnection(context)){
            getConfigurationRemote(context, url, listener);
        } else {
            getConfigurationOffline(context, listener);
        }
    }

如果我们有网络,就可以从服务器获取配置。这个方法可以实现:

private void getConfigurationRemote(final Context context, String url, final ConfigurationManagerListener
        listener) {

    // Send a request to get the configuration
    new AsyncTask<String, Void, HashMap<String, Object>> () {

        @Override
        protected HashMap<String, Object> doInBackground(String... params) {
            InputStream in = null;
            HashMap result = null;
            try {
                URL url = new URL(params[0]);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(10000 /* milliseconds */);
                conn.setConnectTimeout(10000 /* milliseconds */);
                conn.setRequestMethod("GET");
                conn.setDoInput(true);
                conn.connect();
                in = conn.getInputStream();
                result = new ObjectMapper().readValue(in, HashMap.class);
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (in != null){
                        in.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                return result;
            }
        }

        @Override
        protected void onPostExecute(HashMap<String, Object> config) {

            // We if have a valid result, save the data to shared pref AND save it to a ConfigurationWrapper object
            // other wise, try to load from assets folder or shared pref if available
            if (config != null && config.size() > 0){

                // We want to save the hierarchy of the JSON so we save its string representation to shared pref
                JSONObject object = new JSONObject(config);
                mSharedPref.edit().putString(CONFIGURATIONS_KEY, object.toString()).apply();
                mConfigurationWrapper = new ConfigWrapper(config);
                listener.onConfigurationLoaded(mConfigurationWrapper);
            } else {
                // No valid configuration from remote server, so load it from local source
                getConfigurationOffline(context, listener);
            }
        }

    }.execute(url);
}

现在,我正在尝试使用Mockito(如果需要则使用PowerMockito)编写单元测试来测试此代码。

我不确定如何处理调用其中包含新的AsyncTask().execute()调用的方法的情况。

到目前为止,调用initConfiguration方法并模拟网络检查返回true后,在调用execute()后停止。doInBackground()似乎没有被调用。

您将如何测试这样的代码呢?

谢谢!

1个回答

3
你的代码结构使单元测试变得困难。我建议重新排列它。
getConfigurationRemote方法目前执行了3个不同的操作:
- 创建AsyncTask - 定义该任务的操作 - 执行该任务
我的解决方案:
- 将匿名的AsyncTask移动到自己的类中。 - 将任务的创建(new AsyncTask(....))移动到一个工厂类中,或者更好的方法是使用依赖注入框架,如Dagger。 最终我想象中的结果应该是这样的:
private void getConfigurationRemote(final Context context, String url, final ConfigurationManagerListener listener) {
    // create the task
    ConfigurationRemoteAsyncTask task = taskFactory.createConfigurationRemoteTask(listener);
    // Send a request to get the configuration
    task.execute(url);
}

现在您可以更轻松地模拟和测试:
  • verify that task.execute is called with the given url when .initConfiguration is called by simply mocking the task returned by the task factory.

  • verify that listener.onConfigurationLoaded is called when task.onPostExecute is called, without mocking the whole network infrastructure. This might look like this:

    @Test
    public void init_Configuration_With_Network_Load_JSON_From_Server_Return_To_Listener() {
        ConfigurationRemoteAsyncTask task = new ConfigurationRemoteAsyncTask(mockedListener);
        HashMap<String, Object> config = getNotEmptyConfig();
        task.onPostExecute(config);
        verify(mockedListener).onConfigurationLoaded(any(ConfigWrapper.class));
    }
    

OO,感谢您的回复! task.onPostExecute提供了一个参数,即我从服务器收到的实际结果。我假设我可以调用task.onPostExecute(某个哈希映射),然后验证期望的行为是否正确? 为了完全理解,您能否请添加一下测试方法本身可能如何编写的说明呢? 我假设我需要从工厂类中获取AsyncTask,然后简单地调用它的onPostExecute方法,对吗? - Alon Minski
@AlonMinski 我已经添加了一个示例测试。是的,您可以使用非空映射调用onPostExecute,然后验证预期的行为。但是在任务本身的单元测试中,我没有使用任务工厂,因为我只想测试任务本身的行为。 - Bewusstsein
太棒了。这个帮了我很多!谢谢。 - Alon Minski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接