Java 17多线程场景下流式断言ResultCaptor设计方案咨询
解决方案:多线程下流式断言的ResultCaptor实现
核心问题分析
你当前的实现中,子线程抛出的断言异常会被线程自身的异常处理逻辑消化,无法直接传递到测试主线程。这是因为每个子线程拥有独立的执行上下文,主线程不会主动感知子线程的异常,必须通过主动收集或同步等待的方式来捕获。
修正基础ResultCaptor(修复空指针问题)
原ResultCaptor存在values未初始化的空指针隐患,先修正这个基础类:
public static class ResultCaptor<T> implements Answer<T> { protected final List<T> values = Collections.synchronizedList(new ArrayList<>()); protected T lastValue; public List<T> getValues() { return Collections.unmodifiableList(values); } public T getLastValue() { return lastValue; } @Override public T answer(InvocationOnMock invocation) throws Throwable { lastValue = (T) invocation.callRealMethod(); values.add(lastValue); return lastValue; } }
支持多线程流式断言的MyResultCaptor实现
实现需要满足:线程安全存储异常、主线程同步等待、首次异常立即终止多余断言并传递到主线程:
public static class MyResultCaptor<T> extends ResultCaptor<T> { private final List<T> expectedResults; private final AtomicInteger invocationCount = new AtomicInteger(0); private final AtomicReference<AssertionError> firstError = new AtomicReference<>(); private final CountDownLatch latch; public MyResultCaptor(List<T> expectedResults) { this.expectedResults = expectedResults; this.latch = new CountDownLatch(expectedResults.size()); } @Override public T answer(InvocationOnMock invocation) throws Throwable { T actual = super.answer(invocation); int currentIndex = invocationCount.getAndIncrement(); // 已有异常时跳过后续断言,避免无效执行 if (firstError.get() != null) { latch.countDown(); return actual; } try { if (currentIndex >= expectedResults.size()) { throw new AssertionError("Unexpected extra invocation at index " + currentIndex); } Assertions.assertEquals(expectedResults.get(currentIndex), actual); } catch (AssertionError e) { // 仅保留第一个异常 firstError.compareAndSet(null, e); } finally { latch.countDown(); } return actual; } /** * 等待所有预期调用完成,若有异常则抛出 */ public void awaitAndVerify() throws InterruptedException { latch.await(); AssertionError error = firstError.get(); if (error != null) { throw error; } // 额外验证调用次数是否匹配预期 if (invocationCount.get() != expectedResults.size()) { throw new AssertionError("Expected " + expectedResults.size() + " invocations, but got " + invocationCount.get()); } } }
测试方法中的使用示例
@Test void test() throws InterruptedException { List<String> expectedStrings = List.of("Hello", "world", "this", "is", "strangely", "difficult"); MyResultCaptor<String> resultCaptor = new MyResultCaptor<>(expectedStrings); doAnswer(resultCaptor).when(myService).doesSomethingThatReturnsAString(); // 启动多线程任务 otherService.doTheThingThatStartsThreads(); // 等待所有调用完成并验证结果 resultCaptor.awaitAndVerify(); }
关键设计说明
- 线程安全保障:使用
AtomicReference存储首个异常、AtomicInteger计数、synchronizedList存储结果,避免多线程竞争问题。 - 高效终止逻辑:一旦捕获到第一个异常,后续调用直接跳过断言,减少无效执行。
- 主线程同步:通过
CountDownLatch让主线程等待所有预期的Mock调用完成,确保断言全部执行后再检查异常。 - 完整性验证:除了结果断言,还会验证调用次数是否与预期一致,避免漏调用或多调用的情况。
内容的提问来源于stack exchange,提问作者buddyp450
相关产品推荐
相关产品推荐

