1. 程式人生 > >SpringBoot單元測試(三)Mockito

SpringBoot單元測試(三)Mockito

描述

上一篇文章寫了幾個簡單MockMVC的測試類,可以簡單瞭解下MockMVC的用法。這篇文章主要是用來介紹Mockito的用法。它可以通過模擬物件來執行你需要的測試行為。

Mock介紹

Mock,從字面上就知道是模擬的意思。其實它就是建立一個虛擬的物件,然後在測試環境中代替真實的物件,以達到最終的測試目的。借用官方通用的說法:

  1. 驗證這個物件的某些方法的呼叫情況,呼叫了多少次,引數是什麼等等
  2. 指定這個物件的某些方法的行為,返回特定的值,或者是執行特定的動作

如果有開發前端的同行們都知道,Mock.js是前後端分離必不可少的一門技術。

Mockito介紹

Mockito是Mock框架裡應用最廣泛的,也是java的一個應用。市面上還有Powermock for Java、GoogleMock for C++、Mock Server、WireMock、MockMVC。

Mockito的使用

  • 依賴

    <!-- SpringBoot框架的測試包,自動會引入這些Mock相關的包-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- 如果不是Spring framwork或純java框架,要引入Junit包,且mockito-core -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <scope>test</scope>
    </dependency>
    
  • 建立Mock物件

    1. 使用靜態的mock()方法

    2. 使用@Mock註解

      注意的一點是,如果使用@Mock註解方法,你必須要觸發@Mock註解物件的建立。

      • 使用MockitoAnnotations.initMocks(this)方法觸發所有的@Mock註解物件的建立
      /**
       * MockitoTest
       *
       * @author orjrs
       * @date 2018-12-31 11:15
       */
      public class MockitoTest {
          @Mock
          private GirlValidateService girlValidateService;
      
          @Before // @Test註解的方法執行前執行,觸發所有的@Mock註解物件的建立
          public void setUp() {
              MockitoAnnotations.initMocks(this);
          }
      
      }
      
      • 或者使用 @RunWith(MockitoJUnitRunner.class)註解到類裡

        /**
         * MockitoTest
         *
         * @author orjrs
         * @date 2018-12-31 11:15
         */
        @RunWith(MockitoJUnitRunner.class) // 可以自動觸發所有的@Mock註解物件的建立
        public class MockitoTest {
            @Mock
            private GirlValidateService girlValidateService;
        
        //    @Before // @Test註解的方法執行前執行,觸發所有的@Mock註解物件的建立
        //    public void setUp() {
        //        MockitoAnnotations.initMocks(this);
        //    }
        
        }
        
        • 或者使用MockitoRule

          /**
           * MockitoTest
           *
           * @author orjrs
           * @date 2018-12-31 11:15
           */
          // @RunWith(MockitoJUnitRunner.class) // 可以自動觸發所有的@Mock註解物件的建立
          public class MockitoTest {
              @Mock
              private GirlValidateService girlValidateService;
          
              @Rule  // 可以自動觸發所有的@Mock註解物件的建立
              public MockitoRule mockitoRule = MockitoJUnit.rule();
          
          //    @Before // @Test註解的方法執行前執行,觸發所有的@Mock註解物件的建立
          //    public void setUp() {
          //        MockitoAnnotations.initMocks(this);
          //    }
          
          }
          

​ 完整的例子(GirlValidateServiceImpl和GirlServiceImpl後面再繼續貼了):

package com.orjrs.spring.test.service.impl;

import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.GirlService;
import com.orjrs.spring.test.service.GirlValidateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * GirlValidateServiceImpl
 *
 * @author orjrs
 * @date 2018-12-31 11:40
 */
@Service("girlValidateService")
public class GirlValidateServiceImpl implements GirlValidateService {
    private final GirlService girlService;

    @Autowired
    public GirlValidateServiceImpl(GirlService girlService) {
        this.girlService = girlService;
    }

    @Override
    public boolean validate(String name) {
        if (StringUtils.isEmpty(name)) {
            return false;
        }
        Girl girl = girlService.queryGirl(1L);
        if (girl != null && name.equals(girl.getName())) {
            return true;
        }
        return false;
    }
}
package com.orjrs.spring.test.service.impl;

import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.GirlService;
import org.springframework.stereotype.Service;

/**
 * GirlServiceImpl
 *
 * @author orjrs
 * @date 2018-12-31 12:25
 */
@Service("girlService")
public class GirlServiceImpl implements GirlService {
    @Override
    public Girl queryGirl(Long id) {
        Girl girl = new Girl();
        girl.setId(1L);
        girl.setName("張女士");
        girl.setAge(23);
        if (id.equals(girl.getId())) {
            return girl;
        }
        return null;
    }
}
/**
 * GirlValidateServiceTest
 *
 * @author orjrs
 * @date 2018-12-31 12:20
 */
public class GirlValidateServiceTest extends MockitoTest {// MockitoTest 參考上面其中任何一個
    @Mock
    private GirlService girlService;

    @Test
    public void validate() {
        // 建立例項,mock物件作為建構函式的引數
        GirlValidateService girlValidateService = new GirlValidateServiceImpl(girlService);
        
        // 該方法體內呼叫GirlService的方法,此時@Mock出來的虛擬物件,是不會呼叫其實際方法
        // girlService.queryGirl,所以返回的是null,導致判斷為false
        boolean flag = girlValidateService.validate("張女士");
        assertFalse(flag);
        Mockito.verify(girlService).queryGirl(1L); // 驗證queryGirl呼叫是通過Mock物件的,這個方法的結果為null,  girlService.queryGirl實際方法我已經寫死了:1L 張女士 23
    }
}
  • 配置模擬
  1. when thenReturn 和 when thenThrow

    簡單來說,當呼叫方法時,然後再返回自己設值或丟擲自己設定異常

    /**
     * GirlValidateServiceTest
     *
     * @author orjrs
     * @date 2018-12-31 12:20
     */
    public class GirlValidateServiceTest extends MockitoTest {
        @Mock
        private GirlService girlService;
    
        @Test
        public void validate() {
            // 建立例項,mock物件作為建構函式的引數
            GirlValidateService girlValidateService = new GirlValidateServiceImpl(girlService);
           
    
            // 2. 配置mocks的測試:如果呼叫這個方法,始終返回一個自定義的物件
            Mockito.when(girlService.queryGirl(1L)).thenReturn(new Girl() {{
                setId(0L);
                setName("張女士");
            }}); // girlService.queryGirl實際方法我已經寫死了:1L 張女士 23
            assertTrue(girlValidateService.validate("張女士")); // // 驗證校驗方法
        }
    }
    
  • doReturn when 和 doThrow when

它和when thenReturn 和when thenThrow很相似。它對於spy的用法來說是非常有用的。因為spy和mock的區別在於:前者會呼叫實際的方法,而mock不會。

請看下面關於spy的介紹,我會詳細說明這個方法的作用。

package com.orjrs.spring.test.service;

import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.impl.GirlValidateServiceImpl;
import com.orjrs.spring.test.unit.MockitoTest;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * GirlValidateServiceTest
 *
 * @author orjrs
 * @date 2018-12-31 12:20
 */
public class GirlValidateServiceTest extends MockitoTest {
    @Mock
    private GirlService girlService;

    @Spy // 如果不用@Spy註解,則可以Mockito.spy(spyGirlService)
    private GirlService spyGirlService;
    
    @Test
    public void testSpyWrong() {
        // mock 一個LinkedList
        List<Girl> list = new LinkedList<>();
        List<Girl> spy = Mockito.spy(list);

        // 這個方法不會執行,因為spy的物件會去執行其實際方法,即spy.get(0)會去呼叫實際的方法,而list目前一個empty,則get(0)會異常
        Mockito.when(spy.get(0)).thenReturn(new Girl() {{
            setId(0L);
            setName("李女士");
        }});
        // 上面一句會報錯,這裡不會執行
        assertEquals("李女士", spy.get(0).getName());
    }

    @Test
    public void testSpyCorrect() {
        // mock 一個LinkedList
        List<Girl> list = new LinkedList<>();
        List<Girl> spy = Mockito.spy(list);

        // 這個方法不會執行,因為spy的物件會去執行其實際方法,即spy.get(0)會去呼叫實際的方法,而list目前一個empty,則get(0)會異常
        // 但doReturn when方法和when thenReturn不一樣,.when(spy)並不會報錯,呼叫get(0)它始終會返回一個結果
        Mockito.doReturn(new Girl() {{
            setId(0L);
            setName("李女士");
        }}).when(spy).get(0); // .when(spy).get(0);如果改成 when(spy.get(0)),一樣會報錯
        // 會執行這行程式碼
        assertEquals("李女士", spy.get(0).getName());
    }
}
  • 使用@InjectMocks依賴注入

    @InjectMocks註解試圖將建構函式,方法或欄位通過基於型別來依賴注入。例如,假設你有下面的類。

    注意:一定是基於型別,比如下面@InjectMocks // 建立一個例項,並注入@Mock的例項
    ​ private GirlValidateServiceImpl girlValidateServiceImpl;換成 GirlValidateServiceI介面就會報錯

    package com.orjrs.spring.test.service;
    
    import com.orjrs.spring.test.model.Girl;
    import com.orjrs.spring.test.service.impl.GirlValidateServiceImpl;
    import com.orjrs.spring.test.unit.MockitoTest;
    import org.junit.Test;
    import org.mockito.*;
    
    import java.util.LinkedList;
    import java.util.List;
    
    import static org.junit.Assert.*;
    
    /**
     * GirlValidateServiceTest
     *
     * @author orjrs
     * @date 2018-12-31 12:20
     */
    public class GirlValidateServiceTest extends MockitoTest {
        @Mock
        private GirlService girlService;
    
        @Spy
        private GirlService spyGirlService;
    
        @InjectMocks // 建立一個例項,並注入@Mock的例項
        private GirlValidateServiceImpl girlValidateServiceImpl;
    
        @Test
        public void validate() {
            // 建立例項,mock物件作為建構函式的引數
            //GirlValidateService girlValidateService = new //GirlValidateServiceImpl(girlService);
            // 可以直接呼叫imGirlValidateServiceg,不需要上面的初始化
            imGirlValidateServiceg.validate("張女士");
    
        }
    
       
    
    }
    
  • 侷限性

Mockito不能模擬靜態方法和私有方法。