Mockito 如何 mock 静态方法

本文介绍了如何在Mockito 3.4.0及以上版本中对静态方法进行模拟,包括在pom.xml中引入mockito-core和mockito-inline依赖,以及使用MockedStatic进行静态方法mock的示例。同时,文章讨论了不关闭静态mock对象导致的错误及其解决方案,提倡在每个测试方法中使用完静态mock后调用close()方法,或者在测试类的生命周期方法中进行管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在实际工作当中,我们经常会遇到需要对静态方法进行 mock 的情况。在 mockito 2.x 的时代,我们需要借助 powmock 才能实现。当 mockito 进化到了 3.4.0 版本以后,也开始对静态方法 mock 进行了支持(主要是通过 mockito-inline 包)。

简单的介绍就到这里,下面让我们进入主题吧。

  1. 首先确保 pom 文件中 mockito 相关 jar 包的版本(这里我用的版本是 3.7.7),如下:

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>3.7.7</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-inline</artifactId>
        <version>3.7.7</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>3.7.7</version>
        <scope>test</scope>
    </dependency>
    <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.13</version>
         <scope>test</scope>
     </dependency>
    
  2. 使用方式

    Mockito.mockStatic(Class mockClass),如下:

    // 这里 DateUtil 内提供了静态方法
    MockedStatic<DateUtil> dateUtil = mockStatic(DateUtil.class);
    

    示例:

    import static org.mockito.Mockito.mockStatic;
    
    @RunWith(MockitoJUnitRunner.class)
    public class AlphaServiceTest {
    
        @Test
        public void testHttp() {
            ...
            
            MockedStatic<HTTPClient> httpClient = mockStatic(HTTPClient.class);
            httpClient.when(() -> HTTPClient.sendPost("xxx/zzz/ccc", "hello")).thenReturn("success");
            
            ...
            
            // 关闭
            httpClient.close();
        }
        
    

    这样基本上就 OK 了。唯一需要注意下的就是 httpClient.close(),这个问题会在「3.其他」中 “错误提示 static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered” 进行详细说明)

  3. 其他

    • 如果项目中未引入 mockito-inline,会出现如下错误信息:

      org.mockito.exceptions.base.MockitoException: 
      The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks
      
      Mockito's inline mock maker supports static mocks based on the Instrumentation API.
      You can simply enable this mock mode, by placing the 'mockito-inline' artifact where you are currently using 'mockito-core'.
      Note that Mockito's inline mock maker is not supported on Android.
      
      	at com.annoroad.order.service.PreOrderServiceTestCase.testSaveClinicalFreeSuccess1(PreOrderServiceTestCase.java:86)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
      	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
      	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
      	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
      	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
      	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
      	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
      	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
      	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
      	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
      	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
      	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
      	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
      	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
      	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
      	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
      	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
      	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
      	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
      	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
      	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
      	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
      	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
      	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
      	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
      	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
      
    • 错误提示 static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered

      当多个单元测试都使用了同一个 static mock 对象,且使用完成后都没有进行 close。此时,若这几个单元测试用一起执行,第一个单元测试占用了 static mock 对象,第二个单元测试就没有办法再占用了。

      如果出现了这种情况,解决办法也很简单,就是关闭 static mock 对象,如下:

      import static org.mockito.Mockito.mockStatic;
      
      @RunWith(MockitoJUnitRunner.class)
      public class AlphaServiceTest {
      
          @Test
          public void testHttp1() {
              ...
              
              MockedStatic<HTTPClient> httpClient = mockStatic(HTTPClient.class);
              httpClient.when(() -> HTTPClient.sendPost("xxx/zzz/ccc", "hello")).thenReturn("success");
              
              ...
      
      		httpClient.close();
          }
          
          @Test
          public void testHttp2() {
              ...
              
              MockedStatic<HTTPClient> httpClient = mockStatic(HTTPClient.class);
              httpClient.when(() -> HTTPClient.sendPost("xxx/zzz/ccc", "hello")).thenReturn("success");
              
              ...
      
      		httpClient.close();
          }
          
          @Test
          public void testHttp3() {
              ...
              
              MockedStatic<HTTPClient> httpClient = mockStatic(HTTPClient.class);
              httpClient.when(() -> HTTPClient.sendPost("xxx/zzz/ccc", "hello")).thenReturn("success");
              
              ...
      		
      		httpClient.close();
          }
      

      如果你的很多单元测试中都用到了 mockStatic(HTTPClient.class),且觉得在每个单元测试当中都写一遍 mockStatic()…close() 很低效,可以采用下边的方式:

      import static org.mockito.Mockito.mockStatic;
      
      @RunWith(MockitoJUnitRunner.class)
      public class AlphaServiceTest {
      	private MockedStatic<HttpClietn> httpClient;
      
      	// 每个单元测试启动前,先执行该方法(高版本中 @Before 被替换成 @BeforeEach)
          @Before
          public void setUp() {
              this.httpClient = mockStatic(HTTPClient.class);
          }
          
      	// 每个单元测试执行完成后,执行该方法(高版本中 @After 被替换成 @AfterEach)
          @After
          public void teardown() {
              this.httpClient.close();
          }
      
          @Test
          public void testHttp1() {
              ...
              
              httpClient.when(() -> HTTPClient.sendPost("xxx/zzz/ccc", "hello")).thenReturn("success");
              
              ...
          }
          
          @Test
          public void testHttp2() {
              ...
              
              httpClient.when(() -> HTTPClient.sendPost("xxx/zzz/ccc", "hello")).thenReturn("success");
              
              ...
          }
          
          @Test
          public void testHttp3() {
              ...
              
              httpClient.when(() -> HTTPClient.sendPost("xxx/zzz/ccc", "hello")).thenReturn("success");
              
              ...
          }
      

      这样就清爽多了 ~:)

<think>嗯,用户想了解如何使用Mockito对void方法进行mock操作,特别是示例教程。我需要先回忆一下Mockito的基本用法,尤其是处理void方法的部分。记得在Mockito中,普通的mock方法通常是用when().thenReturn(),但void方法没有返回值,所以处理方式不同。 首先,用户可能需要知道Mockito中如何mock void方法。我记得应该使用doNothing()或者doThrow()这类方法来处理。比如,当调用一个void方法时,可以用doNothing().when(mock对象).void方法(),这样可以避免实际执行。但如果有需要抛出异常的情况,可以用doThrow()。 接下来,用户可能需要具体的示例。比如,假设有一个类有一个void的save方法,如何mock它。这时候应该展示一个测试类的例子,包括初始化Mockito注解、使用@Mock创建mock对象、在@Test方法中使用doNothing()来设置void方法的行为,然后调用方法并验证是否被调用过。 另外,用户可能想知道如何处理void方法抛出异常的情况。这时候需要展示如何使用doThrow()来模拟异常,并在测试中捕获它,或者验证异常是否被正确处理。比如,当调用某个void方法时,抛出RuntimeException,然后测试是否真的抛出了异常。 还有,用户可能对验证void方法的调用次数感兴趣。Mockito的verify方法可以验证方法是否被调用了指定次数,比如verify(mock, times(1)).voidMethod()。这也是需要提到的点。 需要注意的是,引用中的内容提到@Mock注解的使用,以及可能需要结合PowerMock来处理抽象类的情况,但用户的问题主要聚焦在void方法的mock,所以可能需要集中在Mockito本身的功能上,除非用户的问题涉及到更复杂的场景。 另外,用户提供的引用中有关于mock静态方法的内容,但当前问题主要是void方法,所以可能不需要涉及静态方法,除非示例中有需要。但根据示例,用户的问题更关注普通实例方法的void方法。 总结一下,回答的结构应该是先说明处理void方法的两种主要场景:无操作和抛出异常,然后给出具体的代码示例,包括注解的使用、测试方法的编写,以及验证步骤。同时,引用相关的引用资料,比如@Mock注解的使用来自引用[1]和[2],以及Mockito的基本介绍来自引用[3]和[4]。 需要确保代码示例正确,比如正确使用@Mock注解,并在@Before方法中初始化MockitoAnnotations.openMocks(this)。此外,示例中的类和方法命名要清晰,比如DatabaseService和save方法,这样用户容易理解。 可能还需要提到,为什么需要mock void方法,比如避免执行实际的操作(如数据库保存),从而让单元测试更快速和独立。这部分可以引用引用[3]中的内容,解释为什么需要模拟void方法。 最后,生成的相关问题需要围绕用户可能进一步想了解的内容,比如异常测试、验证调用次数、与其他框架集成,或者更复杂的场景如静态方法或构造函数的mock。这些相关问题可以帮助用户深入学习和解决问题。</think>### 使用Mockito对void方法进行Mock操作的示例教程 Mockito中针对`void`方法的mock操作需要使用`doNothing()`或`doThrow()`等语法,因为普通方法调用链`when().thenReturn()`无法直接处理无返回值的方法。以下是具体实现步骤和示例: --- #### **1. 基础场景:模拟无操作的void方法** 假设有一个`DatabaseService`类,其`save`方法为`void`类型: ```java public class DatabaseService { public void save(String data) { // 实际数据库操作(需要被模拟) } } ``` **测试代码示例**: ```java import static org.mockito.Mockito.*; public class DatabaseServiceTest { @Mock private DatabaseService mockDatabaseService; @Before public void setup() { MockitoAnnotations.openMocks(this); // 初始化@Mock注解[^1] } @Test public void testSaveData() { // 模拟void方法不执行实际逻辑 doNothing().when(mockDatabaseService).save(anyString()); // 调用被测试方法(例如调用依赖DatabaseService的业务逻辑) someBusinessLogic.saveData("test"); // 验证方法是否被调用 verify(mockDatabaseService, times(1)).save("test"); } } ``` --- #### **2. 抛出异常的void方法** 若需模拟`void`方法抛出异常,使用`doThrow()`: ```java @Test(expected = RuntimeException.class) public void testSaveDataWithException() { // 模拟调用save时抛出异常 doThrow(new RuntimeException("DB error")).when(mockDatabaseService).save(anyString()); // 触发调用(预期抛出异常) someBusinessLogic.saveData("error"); } ``` --- #### **3. 验证void方法调用参数** 通过`ArgumentCaptor`捕获调用参数: ```java @Test public void testSaveDataArgumentCapture() { ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); doNothing().when(mockDatabaseService).save(captor.capture()); someBusinessLogic.saveData("captured"); assertEquals("captured", captor.getValue()); } ``` --- #### **关键点总结** - **`doNothing()`**:默认行为,避免执行实际逻辑[^4]。 - **`doThrow()`**:模拟异常场景[^3]。 - **`verify()`**:验证方法调用次数及参数。 - **`@Mock`注解**:简化Mock对象创建[^2]。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cab5

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值