单元测试异步操作

时间:2017-10-30 11:56:14

标签: java android unit-testing concurrency exception-handling

我试图将代理工作的代码单元测试到后台执行程序。在我重构了一个delete-method以返回id列表之后,我在单元测试中遇到了问题。当方法抛出sqlexception时应该验证行为的测试失败。

我以前没有在我的代码中使用过Futures,所以如果这个设计存在缺陷,请原谅我。

以下是我的代码。

TaskInteractor.java:

public class GetJStrainNameController : ApiController
{
[HttpGet]
public HttpResponseMessage GetName(??????)
{

我收到以下消息:

public class TaskInteractor extends AbstractInteractor
    implements TaskContract.Interactor {

    private final TaskRepository mRepository;

    @Inject
    public TaskInteractor(WorkerThread workerThread, MainThread mainThread, TaskRepository repository) {
        super(workerThread, mainThread);
        this.mRepository = repository;
    }
    ...
    @Override
    @android.support.annotation.MainThread
    public void deleteTasks(@NonNull final List<Task> tasks, @NonNull final DeleteTaskCallback callback) {
        try {
        final Future<List<String>> future = mWorkerThread.execute(() ->
                mRepository.softDeleteAllInTransaction(tasks));
        mMainThread.post(() -> {
            try {
                callback.onDeleteSuccess(future.get());
            } catch (InterruptedException | ExecutionException e) {
                Timber.e(e);
                throw new RuntimeException(e.getCause());
            }
        });
    } catch (final SQLiteAbortException e) {
        mMainThread.post(() -> { callback.onAbortException(e); });
        throw e;
    } catch (final SQLiteConstraintException e) {
        mMainThread.post(() -> { callback.onConstraintException(e); });
        throw e;
    } catch (final Exception e) {
        mMainThread.post(() -> { callback.onFailure(e); });
        throw new RuntimeException(e);
    }
    }
}

这是我的一项测试。

Wanted but not invoked:
mDeleteTaskCallbackMock.onAbortException(
    <any android.database.sqlite.SQLiteAbortException>
);
-> at com.example.ui.task.ExaminationInteractorTest.whenDeleteFailWithSQLiteAbortException_shouldCallOnAbortFailureCallback(ExaminationInteractorTest.java:173)

However, there was exactly 1 interaction with this mock:
mDeleteTaskCallbackMock.onFailure(
    java.lang.RuntimeException: 
android.database.sqlite.SQLiteAbortException
);
-> at com.example.ui.examination.ExaminationInteractor.lambda$deleteExaminations$8(ExaminationInteractor.java:79)

执行由具有以下实现的测试双精度完成。

FakeWorkerThread.java:

@Test
public void whenDeleteFailWithSQLiteConstraintException_shouldCallOnConstraintFailureCallback() throws Exception {
    doThrow(new SQLiteConstraintException()).when(mRepositoryMock).softDeleteAllInTransaction(ArgumentMatchers.<Task>anyList());

    List<Task> tasks = Arrays.asList(TEST_TASKS);

    mInteractor.deleteTasks(tasks, mDeleteTaskCallback);
    verify(mDeleteTaskCallback).onConstraintException(
        any(SQLiteConstraintException.class));
}

1 个答案:

答案 0 :(得分:1)

在此代码段中

try {
    final Future<List<String>> future = mWorkerThread.execute(() ->
            mRepository.softDeleteAllInTransaction(tasks));
    mMainThread.post(() -> {
        try {
            callback.onDeleteSuccess(future.get()); // LINE 1
        } catch (InterruptedException | ExecutionException e) {
            Timber.e(e);
            throw new RuntimeException(e.getCause()); // LINE 2
        }
    });

您正在向工作人员传递Callable。其call()引发的任何异常都将包含在ExecutionException中,并存储在Future中。 然后,当您致电future.get()并且任务完成时,它将抛出ExecutionException

所以正在发生的事情如下

  1. future.get()引发ExecutionException包裹SQLiteConstraintException(第1行);
  2. SQLiteConstraintException包含在RuntimeException(第2行);
  3. 由于RuntimeException与前两个catch子句不匹配,因此最终会在最后一次捕获中处理:catch (final Exception e) {};
  4. 正如测试结果所示,mDeleteTaskCallbackMock(RuntimeException)被调用;
  5. 我建议您更改为以下

    try {
      callback.onDeleteSuccess(future.get());
    } catch (InterruptedException | ExecutionException e) {
      Timber.e(e);
      throw e.getCause(); //<--- throw the unwrapped exception
    }