You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MockMvc测试失败:Controller工具方法Mock失效引发空指针异常

解决MockMvc中工具方法Mock配置不生效的问题

我在Mock一个包含工具方法的Controller时遇到了麻烦:明明已经Mock了工具类的getOperatorsAdNameWrapper方法,但MockMvc执行请求时完全忽略了我用when(...)配置的返回结果,反而用空参数(""和null)调用这个方法,直接导致了空指针异常。请问怎么才能让我的Mock配置和mockMvc.perform()协同生效?


相关代码

Controller代码

@GetMapping(value = {"/wellbore"}) 
public String wellboreForm(Model model, @RequestParam("mode") String mode, HttpServletRequest request) { 
    Set<String> operators = new LinkedHashSet<>(); 
    String userName = (String) request.getSession().getAttribute("userName"); 
    Set<String> operatorsSet = (HashSet<String>) request.getSession().getAttribute("userRoles"); 
    Set<String> operatorsAdName = util.getOperatorsAdNameWrapper(userName, operatorsSet); 
    operatorsAdName.forEach(adName -> { 
        Query query = new Query() 
            .setClassname(Wellbore.CLASS) 
            .eq(Wellbore.operatorsGroup, adName); 
        operators.addAll(getWellboresNameList(query)); 
    }); 
    model.addAttribute("wellboreDataList", operators); 
    model.addAttribute("wellboreData", new WellboreForm()); 
    return "ui/selectWellbore"; 
} 

public static Set<String> getOperatorsAdName(String userName, Set<String> operatorsAdName) { 
    operatorsAdName.removeIf(x -> x.equals(userName) || x.equals("SCOUT") || x.equals("GTO") || x.equals("KADME") || x.equals("offline_access") || x.equals("uma_authorization")); 
    return operatorsAdName; 
} 

public Set<String> getOperatorsAdNameWrapper(String userName, Set<String> operatorsAdName) { 
    return getOperatorsAdName(userName,operatorsAdName); 
} 

测试代码

@Mock 
private Util utilMock; 

@Test 
@DisplayName("GET /wellbore - Select Wellbore") 
void testMockMvc() throws Exception { 
    HttpServletRequest req = Mockito.mock(HttpServletRequest.class); 
    when(req.getAttribute("userName")).thenReturn("abcd"); 
    String userName = (String) req.getAttribute("userName"); 
    //Here I get the correct result 
    when(utilMock.getOperatorsAdNameWrapper(userName, adNames)).thenReturn(roleSet); 
    //another call made here with empty parameters to utilMock.getOperatorsAdNameWrapper("", null) 
    mockMvc.perform(get("/wellbore").param("mode","selectWellbore") 
        .sessionAttr("wellboreDataList", new LinkedHashSet<>()) 
        .sessionAttr("wellboreData", new WellboreForm()) 
    ) 
    .andExpect(status().isOk()) 
    .andExpect(view().name("ui/selectWellbore")) 
    .andExpect(model().attribute("wellboreDataList", hasSize(2))); 
}

问题分析

你的Mock配置不生效主要有三个核心原因:

  1. Controller中的Util实例未被Mock替换:如果Controller里的util是直接实例化(比如private Util util = new Util();)而不是依赖注入,那么你Mock的utilMock根本不会被Controller使用。
  2. Mock的HttpServletRequest未被MockMvc使用:你手动Mock了HttpServletRequest,但MockMvc在执行请求时会创建自己的请求对象,这个Mock实例并没有被关联到请求中。
  3. Session属性设置错误:Controller从Session中获取userName和userRoles,但你的测试只设置了wellboreDataList和wellboreData,导致这两个关键属性为null/空字符串,进而触发了空参数调用。

解决方案

1. 改造Controller,让Util可注入

首先确保Controller通过依赖注入获取Util实例,这样Mockito才能替换它:

@Controller
public class YourController {
    // 推荐构造注入(Spring 4.3+支持无@Autowired的构造注入)
    private final Util util;

    public YourController(Util util) {
        this.util = util;
    }

    // ... 你的wellboreForm方法和其他代码
}

2. 修正测试类的Mock注入逻辑

使用@InjectMocks让Mockito把Mock的utilMock注入到Controller中,同时正确初始化MockMvc:

// JUnit 5用@ExtendWith,JUnit 4用@RunWith(MockitoJUnitRunner.class)
@ExtendWith(MockitoExtension.class)
public class YourControllerTest {
    @Mock
    private Util utilMock;

    @InjectMocks
    private YourController controller; // 替换成你的Controller类名

    private MockMvc mockMvc;

    @BeforeEach // JUnit 5用这个,JUnit 4用@Before
    void setUp() {
        // 用standaloneSetup初始化MockMvc,关联你的Controller实例
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    // ... 你的测试方法
}

3. 正确设置Session属性

在MockMvc请求中设置Controller需要的userName和userRoles,而不是Mock独立的HttpServletRequest:

@Test
@DisplayName("GET /wellbore - Select Wellbore")
void testMockMvc() throws Exception {
    // 准备测试数据
    String userName = "abcd";
    Set<String> adNames = new HashSet<>(Arrays.asList("ROLE1", "ROLE2", userName));
    Set<String> roleSet = new HashSet<>(Arrays.asList("ROLE1", "ROLE2"));

    // 配置Mock方法:如果参数匹配有问题,可以用宽松匹配(anyString(), anySet())
    when(utilMock.getOperatorsAdNameWrapper(eq(userName), eq(adNames))).thenReturn(roleSet);
    // 或者宽松匹配:when(utilMock.getOperatorsAdNameWrapper(anyString(), anySet())).thenReturn(roleSet);

    // 执行请求,设置正确的Session属性
    mockMvc.perform(get("/wellbore")
            .param("mode", "selectWellbore")
            .sessionAttr("userName", userName)
            .sessionAttr("userRoles", adNames)
        )
        .andExpect(status().isOk())
        .andExpect(view().name("ui/selectWellbore"))
        .andExpect(model().attribute("wellboreDataList", hasSize(2)));
}

4. 处理参数匹配问题

如果你的测试中adNames和Controller从Session获取的operatorsSet不是同一个对象,Mockito的精确匹配会失效。这时候可以用参数匹配器:

  • eq():匹配相等的参数值
  • anyString():匹配任意字符串
  • anySet():匹配任意Set类型参数
    根据你的测试场景选择合适的匹配方式即可。

验证

完成以上修改后,MockMvc执行请求时会使用你配置的utilMock实例,并且Session中的参数会正确传递到工具方法中,你的Mock配置就能正常生效了。

内容的提问来源于stack exchange,提问作者Noa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:42:52