Java 8环境下验证匿名内部类方法抛出异常的Mockito测试问题
Java 8环境下验证匿名内部类方法抛出异常的Mockito测试问题
我看了你的代码和测试,发现测试失败的核心原因很明确:你只是触发了statusChange让代码走到创建MyIoClientBuilder并调用subscribeProcessed的步骤,但从来没有调用过MyInterface的myInnerMethod方法——JSON解析的逻辑根本没执行,自然不会抛出RuntimeException,这就是断言失败的原因。
要解决这个问题,我们需要分两步走:先重构代码让测试可控制,再调整测试逻辑验证异常。
第一步:重构MyIoClient,解除硬编码的依赖
你的MyIoClient里直接new MyIoClientBuilder(),这是硬编码依赖,导致测试时无法mock这个Builder,也就拿不到传入subscribeProcessed的MyInterface实例。我们可以通过依赖注入Supplier的方式,把Builder的创建逻辑暴露给测试:
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.function.Supplier; public class MyIoClient { private final Supplier<MyIoClientBuilder> builderSupplier; // 生产环境用的默认构造方法,保持原有逻辑 public MyIoClient(ClientListener clientListener) { this(clientListener, MyIoClientBuilder::new); } // 测试专用构造方法,传入Builder的创建器 MyIoClient(ClientListener clientListener, Supplier<MyIoClientBuilder> builderSupplier) { this.builderSupplier = builderSupplier; clientListener.addClientStatusListener(new StatusListener() { @Override public void statusChange(ClientStatus clientStatus) { if (clientStatus.equals(ClientStatus.AUTHORIZED)) { builderSupplier.get() .subscribeProcessed(new MyInterface() { @Override public void myInnerMethod(String jsonString) { try { Person person = new ObjectMapper().readValue(jsonString, Person.class); System.out.println("person = " + person); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } }).build(); } } }); } }
第二步:调整测试逻辑,捕获MyInterface实例并验证异常
现在我们可以mockMyIoClientBuilder,捕获传入subscribeProcessed的MyInterface实例,然后手动调用它的myInnerMethod传入无效JSON,验证是否抛出预期异常:
import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.*; public class MyIoClientTest { @Test public void shouldThrowRuntimeExceptionWhenJsonInvalid() { // Arrange // 1. Mock ClientListener,触发statusChange到AUTHORIZED状态 ClientListener clientListenerSpy = spy(ClientListener.class); doAnswer(invocation -> { StatusListener statusListener = invocation.getArgument(0); statusListener.statusChange(ClientStatus.AUTHORIZED); return null; }).when(clientListenerSpy).addClientStatusListener(any(StatusListener.class)); // 2. Mock MyIoClientBuilder,捕获传入的MyInterface实例 MyIoClientBuilder mockBuilder = mock(MyIoClientBuilder.class); ArgumentCaptor<MyInterface> myInterfaceCaptor = ArgumentCaptor.forClass(MyInterface.class); when(mockBuilder.subscribeProcessed(myInterfaceCaptor.capture())).thenReturn(mockBuilder); when(mockBuilder.build()).thenReturn(mockBuilder); // 3. 创建MyIoClient,传入mock的Builder Supplier new MyIoClient(clientListenerSpy, () -> mockBuilder); // 拿到捕获到的MyInterface实例 MyInterface myInterface = myInterfaceCaptor.getValue(); // Act & Assert:传入无效JSON,验证抛出异常 String invalidJson = "{invalid: json}"; // 明显不符合JSON规范的字符串 assertThatThrownBy(() -> myInterface.myInnerMethod(invalidJson)) .isInstanceOf(RuntimeException.class) .hasCauseInstanceOf(JsonProcessingException.class); // 进一步验证异常根源 } }
关键逻辑说明
- 重构的意义:通过Supplier注入Builder的创建逻辑,让测试可以替换成mock的Builder,从而捕获到
subscribeProcessed方法传入的MyInterface匿名实例。 - 捕获实例:用
ArgumentCaptor捕获subscribeProcessed的参数,这样我们就能直接调用这个实例的myInnerMethod方法。 - 精准验证:不仅断言抛出RuntimeException,还验证它的根源是
JsonProcessingException,这样测试的准确性更高。
这样调整后,测试就能准确验证当JSON无效时,代码会抛出预期的RuntimeException了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

