0

0

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

碧海醫心

碧海醫心

发布时间:2025-10-23 09:17:39

|

994人浏览过

|

来源于php中文网

原创

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的正确使用

    CA.LA
    CA.LA

    第一款时尚产品在线设计平台,服装设计系统

    下载
    • 将@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应用程序测试套件。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

845

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

745

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

740

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

420

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

447

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

431

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16947

2023.08.03

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

41

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.8万人学习

C# 教程
C# 教程

共94课时 | 7.5万人学习

Java 教程
Java 教程

共578课时 | 50.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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