单元测试异步操作
Unit testing asynchronous operations
我正在尝试对将工作委托给后台执行程序的代码进行单元测试。在我将删除方法重构为 return id 列表后,我的单元测试中出现了 运行 问题。当方法抛出 sqlexception 失败时应验证行为的测试。
我以前没有在我的代码中使用 Futures,所以如果这个设计有缺陷,请原谅我。
以下是我的代码。
TaskInteractor.java:
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(ExaminationInteractor.java:79)
这是我的一项测试。
@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));
}
执行由具有以下实现的测试替身完成。
FakeWorkerThread.java:
/**
* Just runs the commands without invoking other threads
*/
public class FakeWorkerThread implements WorkerThread {
@Override
public void execute(Runnable interactor) {
interactor.run();
}
@Override
public <T> Future<T> execute(Callable<T> callable) throws Exception {
RunnableFuture<T> ftask = new FutureTask<T>(callable);
execute(ftask);
return ftask;
}
}
在此代码段中:
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
.
所以发生的事情如下:
future.get()
抛出一个 ExecutionException
包装一个 SQLiteConstraintException
(LINE 1);
SQLiteConstraintException
被包裹在 RuntimeException
(第 2 行)中;
- 由于
RuntimeException
与前 2 个 catch 子句不匹配,它最终在最后一个 catch 中处理:catch (final Exception e) {}
;
- 如测试输出所示,
mDeleteTaskCallbackMock(RuntimeException)
被调用;
建议你改成下面的:
try {
callback.onDeleteSuccess(future.get());
} catch (InterruptedException | ExecutionException e) {
Timber.e(e);
throw e.getCause(); //<--- throw the unwrapped exception
}
我正在尝试对将工作委托给后台执行程序的代码进行单元测试。在我将删除方法重构为 return id 列表后,我的单元测试中出现了 运行 问题。当方法抛出 sqlexception 失败时应验证行为的测试。
我以前没有在我的代码中使用 Futures,所以如果这个设计有缺陷,请原谅我。
以下是我的代码。
TaskInteractor.java:
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(ExaminationInteractor.java:79)
这是我的一项测试。
@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));
}
执行由具有以下实现的测试替身完成。
FakeWorkerThread.java:
/**
* Just runs the commands without invoking other threads
*/
public class FakeWorkerThread implements WorkerThread {
@Override
public void execute(Runnable interactor) {
interactor.run();
}
@Override
public <T> Future<T> execute(Callable<T> callable) throws Exception {
RunnableFuture<T> ftask = new FutureTask<T>(callable);
execute(ftask);
return ftask;
}
}
在此代码段中:
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
.
所以发生的事情如下:
future.get()
抛出一个ExecutionException
包装一个SQLiteConstraintException
(LINE 1);SQLiteConstraintException
被包裹在RuntimeException
(第 2 行)中;- 由于
RuntimeException
与前 2 个 catch 子句不匹配,它最终在最后一个 catch 中处理:catch (final Exception e) {}
; - 如测试输出所示,
mDeleteTaskCallbackMock(RuntimeException)
被调用;
建议你改成下面的:
try {
callback.onDeleteSuccess(future.get());
} catch (InterruptedException | ExecutionException e) {
Timber.e(e);
throw e.getCause(); //<--- throw the unwrapped exception
}