如何为AlbumsController编写单元测试Mock网络响应?
解决AlbumsController测试的Mock与断言问题
看起来你现在卡在了没法Mock内部的AlbumsService,还没法验证解析后的结果对吧?核心问题在于AlbumsController里直接new了AlbumsService,这导致你没法替换成Mock对象,而且解析逻辑藏在匿名回调里,没法直接断言结果。咱们一步步来解决:
第一步:重构代码,解除硬依赖
首先得把AlbumsService的创建从Controller内部抽出来,通过构造方法注入,这样测试的时候就能传入Mock的Service了。修改后的AlbumsController:
public class AlbumsController { private final AlbumsService albumsService; // 提供默认构造方法,兼容原有调用逻辑 public AlbumsController() { this(new AlbumsService()); } // 用于测试的构造方法,注入Service依赖 public AlbumsController(AlbumsService albumsService) { this.albumsService = albumsService; } public void getAlbums(final ServiceCallback<AlbumsWrapper> albumsServiceCallback) { albumsService.fetchAlbums(new ServiceCallback<NetworkResponse>() { @Override public void onSuccess(NetworkResponse response) { // 执行解析逻辑后回调给上层 AlbumsWrapper wrapper = parseResponse(response); albumsServiceCallback.onSuccess(wrapper); } @Override public void onFailure(NetworkResponse error) { // 直接转发失败回调 albumsServiceCallback.onFailure(error); } // 解析逻辑抽为私有方法,保持封装性无需公开 private AlbumsWrapper parseResponse(NetworkResponse response) { // 这里放你的原解析代码 return new AlbumsWrapper(); } }); } }
这里的核心改动:
- 用构造方法注入AlbumsService,既兼容原有业务代码,又给测试留了替换入口
- 把解析逻辑抽成私有方法,不破坏原有封装,同时让代码结构更清晰
第二步:编写测试用例,Mock Service并验证解析结果
现在可以用Mockito来Mock AlbumsService,捕获它的回调,手动触发测试数据的回调,最后验证解析后的结果是否符合预期。
import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static org.mockito.Mockito.*; public class AlbumControllerTest { @Mock private AlbumsService mockAlbumsService; // Mock掉真实的网络请求服务 @Mock private ServiceCallback<AlbumsWrapper> mockControllerCallback; // 用来验证Controller的最终回调结果 private ArgumentCaptor<ServiceCallback<NetworkResponse>> serviceCallbackCaptor; // 捕获Service的回调参数 private AlbumsController albumsController; @Before public void init() { MockitoAnnotations.initMocks(this); serviceCallbackCaptor = ArgumentCaptor.forClass(ServiceCallback.class); // 注入Mock的Service到Controller中 albumsController = new AlbumsController(mockAlbumsService); } @Test public void getAlbums_EmptyResponse_ReturnsEmptyAlbumsWrapper() { // 1. 调用Controller方法,同时捕获Service的回调 albumsController.getAlbums(mockControllerCallback); verify(mockAlbumsService).fetchAlbums(serviceCallbackCaptor.capture()); // 2. 构造测试用的空响应,手动触发Service的成功回调 NetworkResponse emptyResponse = new NetworkResponse(); serviceCallbackCaptor.getValue().onSuccess(emptyResponse); // 3. 验证Controller是否正确解析并回调了预期的空结果 AlbumsWrapper expectedEmptyWrapper = new AlbumsWrapper(); verify(mockControllerCallback).onSuccess(expectedEmptyWrapper); } @Test public void getAlbums_FailureResponse_ForwardsFailure() { // 测试失败场景:验证Controller是否正确转发失败回调 albumsController.getAlbums(mockControllerCallback); verify(mockAlbumsService).fetchAlbums(serviceCallbackCaptor.capture()); NetworkResponse errorResponse = new NetworkResponse(); serviceCallbackCaptor.getValue().onFailure(errorResponse); verify(mockControllerCallback).onFailure(errorResponse); } }
测试逻辑拆解:
- Mock核心依赖:将真实的AlbumsService替换为Mock对象,彻底避免真实网络请求
- 捕获回调参数:用ArgumentCaptor抓住AlbumsService的fetchAlbums方法接收的匿名回调
- 模拟网络结果:手动调用回调的onSuccess/onFailure,传入你准备好的测试数据
- 断言最终结果:验证Controller是否把解析后的正确结果传给了它的上层回调
为什么之前的测试没成功?
你之前的代码存在几个关键问题:
- 没有Mock AlbumsService,导致始终创建真实服务对象,无法控制其行为
- 回调捕获的顺序错误:应该先调用Controller的getAlbums,再去捕获Service的回调,而非反过来
- 没有正确关联Controller的上层回调与解析结果的验证逻辑
这样重构后,既不用公开解析逻辑破坏封装,又能完整测试Controller从发起请求、接收响应、解析到回调结果的全链路流程。
内容的提问来源于stack exchange,提问作者abhinavrathore019
相关产品推荐
相关产品推荐

