如何寫好單元測試:Mock 脫離數據庫,告別 @SpringBootTest 的重型啟動

如何寫好單元測試:Mock 脫離數據庫,告別 @SpringBootTest 的重型啟動

作者:Killian(重慶) — 歡迎各位架構獵頭、技術布道者聯系我,項目實戰豐富,代碼穩健,Mock測試愛好者。
技術棧:Java 17、JUnit 5、Mockito 5、Spring Boot 3.x(可選)


一、前言

你是否遇到過以下問題:

  • 每次跑測試都要加載整個 Spring 容器,慢如蝸牛?
  • 明明只測一個方法,卻啟動了 Redis、MySQL、MQ 等服務?
  • 想 Mock 一個 Bean 卻被 @Autowired 綁死?

這時候,我們該說:不需要 @SpringBootTest!

本篇文章將系統講解:

  • 如何編寫真正的“單元”測試(Unit Test)
  • 如何使用 Mockito 精準 Mock 依賴,避免啟動數據庫等外部依賴
  • 如何寫出高覆蓋率、快反饋、可維護的業務邏輯測試

二、為什么要避免 @SpringBootTest?

問題描述
啟動慢@SpringBootTest 會加載整個上下文(Controller、Service、Repository、Config)
依賴重需要配置數據庫、緩存、RabbitMQ 等外部環境
不穩定環境不一致容易導致測試 flaky(有時通過,有時失敗)
非單元測試實際上是“集成測試”,容易誤用

三、正確的方式:使用 Mockito + JUnit 寫真正的單元測試

示例背景

我們有一個服務類:

@Service
public class OrderService {private final OrderRepository orderRepository;private final PaymentClient paymentClient;public OrderService(OrderRepository orderRepository, PaymentClient paymentClient) {this.orderRepository = orderRepository;this.paymentClient = paymentClient;}public String pay(String orderId) {Order order = orderRepository.findById(orderId).orElseThrow(() -> new RuntimeException("訂單不存在"));if (order.isPaid()) {return "重復支付";}boolean result = paymentClient.callPayGateway(order);if (result) {order.markPaid();orderRepository.save(order);return "支付成功";} else {return "支付失敗";}}
}

單元測試寫法(脫離容器 + Mock 依賴)

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {@Mock OrderRepository orderRepository;@Mock PaymentClient paymentClient;@InjectMocks OrderService orderService;@Test@DisplayName("支付成功時,訂單狀態應更新并保存")void testPaySuccess() {Order mockOrder = new Order("123", false);when(orderRepository.findById("123")).thenReturn(Optional.of(mockOrder));when(paymentClient.callPayGateway(mockOrder)).thenReturn(true);String result = orderService.pay("123");assertEquals("支付成功", result);assertTrue(mockOrder.isPaid());verify(orderRepository).save(mockOrder);}@Test@DisplayName("找不到訂單時,應拋出異常")void testOrderNotFound() {when(orderRepository.findById("999")).thenReturn(Optional.empty());assertThrows(RuntimeException.class, () -> orderService.pay("999"));}@Test@DisplayName("已支付訂單不應重復支付,也不應保存")void testAlreadyPaid() {Order paidOrder = new Order("456", true);when(orderRepository.findById("456")).thenReturn(Optional.of(paidOrder));String result = orderService.pay("456");assertEquals("重復支付", result);verify(orderRepository, never()).save(any());}
}

四、關鍵技巧:Mock 什么?怎么 Mock?

1. 只 Mock “外部依賴”

  • 數據庫 Repository
  • 第三方客戶端(如 FeignClient、HttpClient)
  • Redis 操作、MQ 發送器、ES 操作器

2. 不 Mock 的部分

  • 自己寫的業務邏輯類(即你要測的類)

3. 使用 Mockito 提供的能力

  • when(...).thenReturn(...):設置返回值
  • verify(...):驗證方法是否調用
  • argThat(...):匹配參數條件
  • doThrow(...):模擬異常

五、單元測試 vs 集成測試:職責邊界與框架選擇

對比表格

維度單元測試(Unit Test)集成測試(Integration Test)
啟動方式不啟動 Spring 容器啟動 Spring 容器(或部分)
測試目標業務邏輯、算法正確性Bean 交互、配置、環境集成
Mock 使用必須 Mock 外部依賴通常不 Mock,使用真實組件
性能快,毫秒級慢,秒級
數據源無數據庫或 H2 Mock真正連接數據庫(如 Docker 啟動 MySQL)
斷言粒度精確控制方法行為更偏向流程通路與集成穩定性

@DataJpaTest

用于測試 JPA Repository 層(不加載 Service、Controller):

@DataJpaTest
class UserRepositoryTest {@Autowired UserRepository repo;@Test@DisplayName("根據用戶名查詢用戶,應返回結果")void testFindByUsername() {User u = new User("tom", "123");repo.save(u);assertTrue(repo.findByUsername("tom").isPresent());}
}

自動配置內嵌數據庫(如 H2),速度適中,適合數據層測試。


@Mapper + MyBatis 的 Mapper 層測試(兩種方式)

? 方式一:真實數據庫 + @MybatisTest
@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 保持使用真實數據庫配置
class OrderMapperTest {@Autowired OrderMapper orderMapper;@Test@DisplayName("根據訂單ID查詢,應返回訂單信息")void testSelectById() {Order order = orderMapper.selectById("order123");assertNotNull(order);}
}

說明:

  • @MybatisTest 會只加載 MyBatis 相關的配置(不會加載 Service、Controller)
  • 默認使用 H2,可通過 @AutoConfigureTestDatabase 強制保留 MySQL 等真實庫
  • 可以測試 XML 映射、注解 SQL、分頁插件等
? 方式二:Mock Mapper(更適合單元測試)
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {@Mock OrderMapper orderMapper;@InjectMocks OrderService orderService;@Test@DisplayName("Mock Mapper 查詢訂單,應返回正確訂單")void testOrderFetch() {Order mockOrder = new Order("order789", false);when(orderMapper.selectById("order789")).thenReturn(mockOrder);Order result = orderService.getOrder("order789");assertEquals("order789", result.getId());}
}

說明:

  • Mapper 在 Service 中作為依賴,Mock 掉即可測試業務邏輯
  • 不需要數據庫、不用 @SpringBootTest,速度快、適合 CI

@WebMvcTest

用于測試 Controller 層(不加載業務邏輯):

@WebMvcTest(UserController.class)
class UserControllerTest {@Autowired MockMvc mockMvc;@MockBean UserService userService;@Test@DisplayName("調用 /hello 接口,應返回 hello tom")void testHelloApi() throws Exception {when(userService.getName()).thenReturn("tom");mockMvc.perform(get("/hello")).andExpect(status().isOk()).andExpect(content().string("hello tom"));}
}

優點是啟動快,只加載 Web 層相關 Bean,可精準控制 Controller 輸入輸出。


要點說明
不使用 @SpringBootTest減少啟動時間,提高測試速度
用 @ExtendWith(MockitoExtension.class)使用 Mockito 管理依賴注入
用 @InjectMocks注入被測類(業務類)
用 @Mock模擬依賴(Repository、外部接口)
每個測試只驗證一件事保證測試原子性和可維護性

六、擴展閱讀

  • Mockito 官方文檔:https://site.mockito.org
  • JUnit 5 用戶指南:https://junit.org/junit5/docs/current/user-guide/
  • 推薦閱讀:Martin Fowler《Unit Test vs Integration Test》

七、結語

如果你寫單元測試還依賴 @SpringBootTest,那就像每次微波爐加熱都要重啟電廠。Mock 依賴、聚焦業務、輕量高效,才是測試真正的姿勢。

下一次寫測試時,請問自己:“我是在測試業務邏輯,還是在啟動一個服務器?”

本文由 @killian 原創,轉載請注明出處。
? 請作者喝杯咖啡,持續更新更深入的干貨

💡 彩蛋時間:如果你看到了這里,說明你是那種喜歡動手實戰的人。那我悄悄分享一個開發圈流傳的工具試用入口,貌似跟高效調試很有關系,地址也挺特別的:

🔗 入口

據說注冊還能解鎖一些隱藏功能,懂的都懂(別外傳 😂)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/83854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/83854.shtml
英文地址,請注明出處:http://en.pswp.cn/web/83854.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【DNS】在 Windows 下修改 `hosts` 文件

在 Windows 下修改 hosts 文件,一般用于本地 DNS 覆蓋。操作步驟如下(以 Windows 10/11 為例): 1. 以管理員權限打開記事本 點擊 開始 → 輸入 “記事本”在“記事本”圖標上右鍵 → 選擇 以管理員身份運行 如果提示“是否允許此…

共享內存實現進程通信

目錄 system V共享內存 共享內存示意圖 共享內存函數 shmget函數 shmat函數 shmdt函數 shmctl函數 代碼示例 shm頭文件 構造函數 獲取key值 創建者的構造方式 GetShmHelper 函數 GetShmUseCreate 函數 使用者的構造方式 GetShmForUse 函數 分離附加操作 DetachShm 函數 AttachS…

6月15日星期日早報簡報微語報早讀

6月15日星期日,農歷五月二十,早報#微語早讀。 1、證監會擬修訂期貨公司分類評價:明確扣分標準,優化加分標準; 2、國家考古遺址公園再添10家,全國已評定65家; 3、北京多所高校禁用羅馬仕充電寶…

破解關鍵領域軟件測試“三重難題”:安全、復雜性、保密性

在國家關鍵領域,軟件系統正成為核心戰斗力的一部分。相比通用軟件,關鍵領域軟件在 安全性、復雜性、實時性、保密性 等方面要求極高。如何保障安全合規前提下提升測試效率,確保系統穩定,已成為軟件質量保障的核心挑戰。 關鍵領域…

記錄一次 Oracle DG 異常停庫問題解決過程

記錄一次 Oracle DG 異常停庫問題解決過程 某醫院有以下架構的雙節點 Oracle 集群: 節點1:172.16.20.2 節點2:172.16.20.3 SCAN IP:172.16.20.1 DG:172.16.20.1206月12日,醫院信息科用戶反映無法連接 DG 服務器。 登錄 DG 服務…

MySQL使用EXPLAIN命令查看SQL的執行計劃

1?、EXPLAIN 的語法 MySQL 中的 EXPLAIN 命令是用于分析 SQL 查詢執行計劃的關鍵工具,它能幫助開發者理解查詢的執行方式并找出性能瓶頸??。 語法格式: EXPLAIN <sql語句> 【示例】查詢學生表關聯班級表的執行計劃。 (1)創建班級信息表和學生信息表,并創建索…

Go語言2個協程交替打印

WaitGroup 無緩沖channel waitgroup 用來控制2個協程 Add() 、Done()、Wait() channel用來實現信號的傳遞和信號的打印 ch1: 用來記錄打印的信號 ch2:用來實現信號的傳遞&#xff0c;實現2個協程的順序打印 package mainimport ("fmt""sync" )func ma…

微信小程序 路由跳轉

路由方式 官方參考文檔 wx.switchTab 實現底部導航欄 1.配置信息 app.json"tabBar": {"custom": true,"list": [{"pagePath": "pages/home/index","text": "首頁"},{"pagePath": "p…

[Java 基礎]正則表達式

正則表達式是一種強大的文本模式匹配工具&#xff0c;它使用一種特殊的語法來描述要搜索或操作的字符串模式。在 Java 中&#xff0c;我們可以使用 java.util.regex包提供的類來處理正則表達式。 :::color3 正則表達式不止 Java 語言提供了相應的功能&#xff0c;很多其他語言…

ArcGIS安裝出現1606錯誤解決辦法

問題背景&#xff1a; 由于最近Arcgis10.2打是有些功能不正常退出&#xff0c;比如arctoolbox中的&#xff0c;table to excel 功能&#xff0c;只要一點擊&#xff0c;arcgis就報錯退出&#xff0c;平常在使用過程中&#xff0c;也經常出現一些莫名其妙的崩潰現象&#xff0c…

wpf 解決DataGridTemplateColumn中width綁定失效問題

感謝酪酪烤奶 提供的Solution 文章目錄 感謝酪酪烤奶 提供的Solution使用示例示例代碼分析各類交互流程 WPF DataGrid 列寬綁定機制分析整體架構數據流分析1. ViewModel到Slider的綁定2. ViewModel到DataGrid列的綁定a. 綁定代理(BindingProxy)b. 列寬綁定c. 數據流 關鍵機制詳…

語音轉文本ASR、文本轉語音TTS

ASR Automatic Speech Recognition&#xff0c;語音轉文本。 技術難點&#xff1a; 聲學多樣性 口音、方言、語速、背景噪聲會影響識別準確性&#xff1b;多人對話場景&#xff08;如會議錄音&#xff09;需要區分說話人并分離語音。 語言模型適配 專業術語或網絡新詞需要動…

通用embedding模型和通用reranker模型,觀測調研

調研Qwen3-Embedding和Qwen3-Reranker 現在有一個的問答庫&#xff0c;包括150個QA-pair&#xff0c;用10個query去同時檢索問答庫的300個questionanswer Embedding模型&#xff0c;query-question的匹配分數 普遍高于 query-answer的匹配分數。比如對于10個query&#xff0c…

基于YOLOv8+Deepface的人臉檢測與識別系統

摘要 人臉檢測與識別系統是一個集成了先進計算機視覺技術的應用&#xff0c;通過深度學習模型實現人臉檢測、識別和管理功能。系統采用雙模式架構&#xff1a; ??注冊模式??&#xff1a;檢測新人臉并添加到數據庫??刪除模式??&#xff1a;識別數據庫中的人臉并移除匹…

Grdle版本與Android Gradle Plugin版本, Android Studio對應關系

Grdle版本與Android Gradle Plugin版本&#xff0c; Android Studio對應關系 各個 Android Gradle 插件版本所需的 Gradle 版本&#xff1a; https://developer.android.com/build/releases/gradle-plugin?hlzh-cn Maven上發布的Android Gradle Plugin&#xff08;AGP&#x…

用c語言實現簡易c語言掃雷游戲

void test(void) {int input 0;do{menu();printf("請選擇&#xff1a; >");scanf("%d", &input);switch (input){menu();case 1:printf("掃雷\n");game();break;case 2:printf("退出游戲\n");break;default:printf("輸入…

系統辨識的研究生水平讀書報告期末作業參考

這是一份關于系統辨識的研究生水平讀書報告&#xff0c;內容系統完整、邏輯性強&#xff0c;并深入探討了理論、方法與實際應用。報告字數超過6000字 從理論到實踐&#xff1a;系統辨識的核心思想、方法論與前沿挑戰 摘要 系統辨識作為連接理論模型與客觀世界的橋梁&#xff…

開源、免費、美觀的 Vue 后臺管理系統模板

隨著前端技術的不斷發展&#xff0c;Vue.js 憑借其輕量、高效、易上手的特性&#xff0c;成為國內外開發者最喜愛的前端框架之一。在構建后臺管理系統時&#xff0c;Vue 提供了以下優勢&#xff1a; 響應式數據綁定&#xff1a;讓頁面和數據保持同步&#xff0c;開發效率高。 …

適合 Acrobat DC 文件類型解析

文件類型 (File Type)ProgID (Continuous)ProgID (Classic)主要用途.pdfAcroExch.Document.DCAcroExch.Document.20XX (版本特定)Adobe PDF文檔格式&#xff0c;用于存儲文檔內容和格式.pdfxmlAcroExch.pdfxmlAcroExch.pdfxmlPDF與XML結合的格式&#xff0c;可能用于結構化數據…

C/C++數據結構之漫談

概述 在當今的數字化時代&#xff0c;無論是刷短視頻、社交聊天&#xff0c;還是使用導航軟件、網絡購物&#xff0c;背后都離不開計算機技術的支持。但你是否想過&#xff1a;為什么同樣的功能&#xff0c;有的軟件運行得飛快&#xff0c;有的卻嚴重卡頓&#xff0c;半天沒有響…