
本文旨在解决spring mvc控制器单元测试中常见的`nullpointerexception`问题,该问题通常在使用`@webmvctest`时因服务依赖未正确模拟和注入而导致。我们将深入探讨`@mockbean`注解的正确用法,以及它如何与`mockmvc`协同工作,确保控制器能够访问到其模拟的服务依赖,从而实现健壮且高效的web层测试。
在Spring Boot项目中,当我们使用@WebMvcTest对控制器(Controller)进行单元测试时,一个常见的需求是隔离控制器,使其不依赖于实际的业务逻辑层(Service)和数据访问层(Repository)。这时,我们会选择使用Mocking框架(如Mockito)来模拟这些依赖。然而,如果模拟的方式不正确,很容易遇到java.lang.NullPointerException,指示控制器内部调用的服务对象为null。
例如,考虑以下一个典型的测试场景:
@WebMvcTest // 专注于Web层测试
@ContextConfiguration(classes = { TestAppContext.class }) // 指定测试上下文配置
class BillEntryControllerTest {
@Autowired
private BillEntryService billEntryService; // 尝试注入服务
@Autowired
private MockMvc mockMvc; // 自动注入MockMvc
@BeforeEach
public void setup() {
// 尝试手动设置MockMvc,这在@WebMvcTest下是不必要的
this.mockMvc = MockMvcBuilders.standaloneSetup(new BillEntryController())
.build();
}
@Test
public void checkUpdateBill() throws Exception {
// 尝试在测试方法内部模拟服务
billEntryService = Mockito.mock(BillEntryServiceImpl.class);
// 定义模拟行为
doNothing().when(billEntryService).addOrUpdateBill(any(BillEntry.class));
this.mockMvc
.perform(MockMvcRequestBuilders.post("/bill-entry/saveBillEntry").accept(MediaType.TEXT_HTML)
.param("amount", "10.0")
// ... 其他参数
)
.andExpect(model().errorCount(0)).andExpect(status().isOk());
}
}以及对应的控制器方法:
@PostMapping("/saveBillEntry")
public String saveBillEntry(Model model, @Valid @ModelAttribute("billEntry") BillEntryFormDto dto,
BindingResult theBindingResult) {
// ... 其他逻辑
// failing at here (line 157)
billEntryService.addOrUpdateBill(billEntry); // 这里抛出NullPointerException
return "redirect:"+ dto.getRedirect();
}上述测试代码的问题在于,尽管在测试类中声明了@Autowired private BillEntryService billEntryService;,并在@Test方法中使用了Mockito.mock()创建了一个模拟对象,但这个模拟对象并没有被注入到BillEntryController实例中。@WebMvcTest会启动一个有限的Spring应用上下文,并尝试注入控制器及其依赖。如果依赖是一个Spring管理的Bean,并且我们想用Mock替代它,仅仅在测试类中创建一个Mockito.mock()实例是不足以让Spring容器知道并将其注入到控制器中的。因此,控制器内部的billEntryService字段仍然是null,导致调用时出现NullPointerException。
此外,手动设置MockMvc(如在@BeforeEach中使用MockMvcBuilders.standaloneSetup())也是不必要的,因为@WebMvcTest会自动配置并注入一个功能完备的MockMvc实例。
Spring Boot提供了一个专门用于测试的注解@MockBean,它能够优雅地解决上述问题。@MockBean的作用是在Spring应用上下文中为指定的类型创建一个Mockito模拟对象,并将其注入到所有需要该类型依赖的Bean中。这意味着,当我们使用@WebMvcTest测试控制器时,@MockBean会自动将模拟的服务实例注入到控制器中。
以下是使用@MockBean修正后的测试代码:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
// 假设TestAppContext包含BillEntryController的配置
// 或者如果BillEntryController是@Controller注解的,@WebMvcTest会自动扫描到
@WebMvcTest(BillEntryController.class) // 指定要测试的控制器,更精确
@Import(TestAppContext.class) // 如果TestAppContext包含必要的Bean定义,可以使用@Import
// @Transactional 在@WebMvcTest中通常不是必需的,因为它不涉及数据库事务
class BillEntryControllerTest {
@MockBean // 使用@MockBean来创建并注入BillEntryService的模拟对象
private BillEntryService billEntryService;
@Autowired // @WebMvcTest会自动配置并注入MockMvc
private MockMvc mockMvc;
// 移除@BeforeEach方法,因为MockMvc已由@WebMvcTest自动配置和注入
// 移除在测试方法中手动创建Mockito.mock()的调用
@Test
public void checkUpdateBill() throws Exception {
// 定义模拟对象的行为。此时billEntryService已经是@MockBean创建的模拟对象
doNothing().when(billEntryService).addOrUpdateBill(any(BillEntry.class));
this.mockMvc
.perform(MockMvcRequestBuilders.post("/bill-entry/saveBillEntry")
.accept(MediaType.TEXT_HTML)
.param("amount", "10.0")
.param("owner", "User")
.param("property", "Prop")
.param("receiptNumber", "ABC")
.param("accountName", "AC")
.param("billerName", "BN")
.param("datePaid", "20/10/2022")
.param("dateDue", "20/10/2022"))
.andExpect(model().errorCount(0))
.andExpect(status().isOk());
}
}@MockBean的正确使用:
MockMvc的自动配置:
@WebMvcTest的精确性:
@ContextConfiguration或@Import:
模拟行为的定义:
@Transactional的适用性:
通过正确地使用Spring Boot提供的@MockBean注解,我们可以有效地在@WebMvcTest环境下模拟控制器所依赖的服务,从而避免NullPointerException并实现真正的单元测试。@MockBean简化了Mock对象的创建和注入过程,使其与Spring的依赖注入机制无缝集成。同时,依赖@WebMvcTest自动配置的MockMvc实例,可以避免不必要的MockMvc手动设置,使测试代码更加简洁和符合惯例。掌握这些最佳实践,将有助于构建更健壮、可维护的Spring MVC应用程序测试套件。
以上就是Spring MVC控制器测试:使用@MockBean正确模拟服务依赖的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号