首页 > Java > java教程 > 正文

Spring MVC控制器测试:使用@MockBean正确模拟服务依赖

碧海醫心
发布: 2025-10-23 09:17:39
原创
978人浏览过

Spring MVC控制器测试:使用@MockBean正确模拟服务依赖

本文旨在解决spring mvc控制器单元测试中常见的`nullpointerexception`问题,该问题通常在使用`@webmvctest`时因服务依赖未正确模拟和注入而导致。我们将深入探讨`@mockbean`注解的正确用法,以及它如何与`mockmvc`协同工作,确保控制器能够访问到其模拟的服务依赖,从而实现健壮且高效的web层测试。

Spring MVC控制器测试中的常见挑战

在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实例。

解决方案:利用@MockBean进行依赖注入

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());
    }
}
登录后复制

关键调整和最佳实践

  1. @MockBean的正确使用

    帮衣帮-AI服装设计
    帮衣帮-AI服装设计

    AI服装设计神器,AI生成印花、虚拟试衣、面料替换

    帮衣帮-AI服装设计 106
    查看详情 帮衣帮-AI服装设计
    • 将@Autowired private BillEntryService billEntryService;替换为@MockBean private BillEntryService billEntryService;。
    • @MockBean会自动在Spring上下文中注册一个BillEntryService的Mock实例,并将其注入到BillEntryController中。
    • 不再需要在测试方法内部手动调用Mockito.mock(BillEntryServiceImpl.class)。
  2. MockMvc的自动配置

    • @WebMvcTest注解会自动配置MockMvc,并将其注入到测试类中。因此,@BeforeEach方法中手动创建MockMvc实例(如MockMvcBuilders.standaloneSetup(...))是多余的,应该被移除。
  3. @WebMvcTest的精确性

    • 为了提高测试的效率和隔离性,建议在@WebMvcTest注解中明确指定要测试的控制器类,例如@WebMvcTest(BillEntryController.class)。这样可以进一步限制Spring上下文加载的Bean数量。
  4. @ContextConfiguration或@Import

    • 如果控制器依赖的Bean(例如BillEntryController自身)需要特定的配置或组件扫描才能被@WebMvcTest发现和加载,可以使用@ContextConfiguration或@Import来引入必要的配置类。在大多数情况下,@WebMvcTest会自动扫描控制器。
  5. 模拟行为的定义

    • 一旦@MockBean创建了模拟对象,就可以像使用任何Mockito模拟对象一样,通过when().then...()或do...().when()来定义其行为。
  6. @Transactional的适用性

    • 在@WebMvcTest这种专注于Web层的测试中,通常不需要@Transactional注解,因为这类测试通常不涉及实际的数据库操作。如果测试确实需要事务管理,例如在集成测试中,才应考虑使用。

总结

通过正确地使用Spring Boot提供的@MockBean注解,我们可以有效地在@WebMvcTest环境下模拟控制器所依赖的服务,从而避免NullPointerException并实现真正的单元测试。@MockBean简化了Mock对象的创建和注入过程,使其与Spring的依赖注入机制无缝集成。同时,依赖@WebMvcTest自动配置的MockMvc实例,可以避免不必要的MockMvc手动设置,使测试代码更加简洁和符合惯例。掌握这些最佳实践,将有助于构建更健壮、可维护的Spring MVC应用程序测试套件。

以上就是Spring MVC控制器测试:使用@MockBean正确模拟服务依赖的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号