PowerMock(一):PowerMock的基本使用

文章目錄

    為啥要使用PowerMock
    PowerMock的使用
        環(huán)境
        引入依賴
        注解說明
        mock普通方法
        mock拋出異常
        mock新建對象
        mock無返回值的方法
        mock被final修飾的方法
        參數(shù)模糊匹配
        mock靜態(tài)方法
        mock私有方法
        總結
        參考

為啥要使用PowerMock

現(xiàn)在流行的測試驅(qū)動開發(fā)TDD(Test-Driven Development) ,是敏捷開發(fā)中一項核心實踐和技術。也是一種設計方法論。其中最重要的一環(huán)就是使用單元測試。
單元測試是保證代碼質(zhì)量的一個重要手段,通過單元測試我們可以快速的測試代碼的各個分支,各種場景,代碼重構時只需要重新跑下單元測試就是能知道代碼潛在的問題。
單元測試是通過Mock的方式調(diào)用被測試的方法,其有如下幾個優(yōu)點:

    Mock可以解除測試對象對外部服務的依賴(比如數(shù)據(jù)庫,第三方接口等),使得測試用例可以獨立運行。不管是單體應用還是微服務,這點都特別重要。
    Mock的第二個好處就是替換外部服務調(diào)用,提升測試用例的運行速度。因為任何外部服務調(diào)用至少是跨進程級別的消耗,甚至是跨系統(tǒng)、跨網(wǎng)絡的消耗,而Mock可以把消耗降低到進程內(nèi)。
    Mock的第三個好處就是提升測試效率,提高單位時間內(nèi)測試的接口數(shù)量。
    Mock的框架有很多中比如EasyMock等,這里選用PowerMock是因為PowerMock可以用來Mock 私有方法,靜態(tài)方法以及final方法。EasyMock等則不能。

PowerMock的使用
環(huán)境
軟件    版本
junit    4.13
powermock    2.0.7
引入依賴

  <properties>
        <powermock.version>2.0.7</powermock.version>
    </properties>
     <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <!--powermock開始-->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <!--powermock結束-->


這里引入了是三個依賴,junit依賴如果項目中已有的話,則不需要重復引入,需要注意的是JUnit 4.4及以上版本的JUnit需要引入2.0.x 版本以上的 powermock 。如果項目中有mockito依賴還需要注意mockito的版本與powermock版本對應關系,對應如下圖:
詳細請參考Using PowerMock with Mockito,如果引入的版本不匹配則可能會報如下錯誤:

java.lang.TypeNotPresentException: Type org.powermock.modules.junit4.PowerMockRunner not present



依賴引入之后就可以編寫單元測試代碼了。
注解說明

現(xiàn)有一個待測試的類UserServiceImpl,該類中注入了一個UserMapper的類實例。

@Service
public class UserServiceImpl {
     @Autowried
    private UserMapper userMapper;
    ........省略部分方法
}


那么如何對上面的類通過powermock的方式進行單元測試呢?首先是定義一個測試類,定義如下:

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class})
public class UserServiceImplTest {
      @Mock
    private UserMapper userMapper;
    @InjectMocks
    private UserServiceImpl userServiceImpl = new UserServiceImpl();
}



@RunWith(PowerMockRunner.class) 注解表明使用PowerMockRunner運行測試用例,這個必須添加,不然無法使用PowerMock。
@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class}) @PrepareForTest 注解是用來添加所有需要測試的類,這里列舉了三個需要測試的類。
@Mock注解修飾會mock出來一個對象,這里mock出來的是UserMapper類實例。
@InjectMocks 注解會主動將已存在的mock對象注入到bean中,按名稱注入,這個注解修飾在我們需要測試的類上。必須要手動new一個實例,不然單元測試會有問題。
這幾個注解是一個測試類必須要的。說完了測試類的定義,接下來就讓我們來看看各種方法是如何mock的。
mock普通方法

    待測試的方法(UserMapper中)

 boolean saveUser(User user) {
        int i = userMapper.addUser(user);
        return i == 1 ? true : false;
    }



這里的方法int i = userMapper.addUser(user); 有入?yún)?,有出參,沒有關鍵字修飾,是一個普通的方法,mock的方式也很簡單,就是PowerMockito.when(userMapper.addUser(user)).thenReturn(1); 在when方法中調(diào)用你需要mock的方法,thenReturn方法寫入你期待返回的值。從字面意思理解就是當調(diào)用xxx方法時,返回xxx值。 詳細的示例如下:
2. 測試方法

 User user = new User();
        user.setId(1);
        user.setUserName("test");
        user.setPassword("admin123");
        PowerMockito.when(userMapper.addUser(user)).thenReturn(1);
        boolean result = userServiceImpl.saveUser(user);
        Assert.assertEquals(true, result);



mock拋出異常

單元測試中我們有時候需要mock異常的拋出,其mock的方式也很簡單就是在thenThrow(new Exception())寫入你期待拋出的異常。如果被mock的方法拋出的是受檢異常(checked exception)的話,那么thenThrow拋出new Exception()或者其子類。
如果被mock的方法拋出的是非受檢異常(unchecked exception),那么thenThrow拋出new RuntimeException或其子類。使用的示范如下:

    待測試的方法(UserMapper中)

 int delUser(int id) throws Exception {
        if (id == -1) {
            throw new Exception("傳入的id值不對");
        } else {
            return 1;
        }
    }



    測試方法

   PowerMockito.when(userMapper.delUser(-1)).thenThrow(new Exception());


這里delUser方法拋出的是受檢異常Exception,所以在thenThrow中需要new一個Exception對象。
mock新建對象

如果我們要對一個實體對象Bean進行Mock,只需要這樣寫PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user)
這個代碼的意思是創(chuàng)建一個User實例對象,不管傳入啥參數(shù)都返回定義的實例user,用于替換被測試方法中相應的User對象。使用示范如下:

    待測試的方法(UserMapper中)

 public int countUser() {
        User user = new User();
        int count = 0;
        if (user.getId() > 0) {
            count += 1;
        }
        return count;
    }



    測試方法

 // 6.mock新建對象
        User user = new User();
        user.setId(11);
        PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user);
        int result = userServiceImpl.countUser();
        Assert.assertEquals(1, result);



這里mock了一個User對象。id是11,當調(diào)用countUser方法時可以拿到之前mock的User對象,所以返回的結果是1。
mock無返回值的方法

對于返回值是通過void修飾的方法,他的mock方式與普通方法的mock方式不同。有兩種方式mock。
方式一:

  PowerMockito.doNothing().when(userMapper, "updateUser", new User());



在when方法中傳入userMapper類實例,需要調(diào)用的方法名,以及需要傳入的參數(shù)。
方式二:

  PowerMockito.doNothing().when(userMapper).updateUser(user);


在when方法中只傳入userMapper類實例,然后通過函數(shù)式調(diào)用的方式調(diào)用待測試的方法。
使用示范如下:

    待測試的方法(UserServiceImpl中)

 public void updateUser(User user) {
        userMapper.updateUser(user);
    }

    測試方法

  User user = new User();
        // 4.mock返回值為void的方法
        //方法一
        PowerMockito.doNothing().when(userMapper, "updateUser", new User());
        //方法二
        PowerMockito.doNothing().when(userMapper).updateUser(user);
        userServiceImpl.updateUser(user);



mock被final修飾的方法

現(xiàn)在有一個方法被final關鍵字修飾,那么該如何要mock這個方法,首先需要mock出一個類實例。如下所示:

    UserMapper mock = PowerMockito.mock(UserMapper.class);

這里需要特別注意的是被mock的類必須要在@PrepareForTest注解中指定,如本例中的@PrepareForTest({UserMapper.class})。不然就會報如下錯誤:

org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.

使用示范如下:

    待測試的方法(UserMapper中)

    final String getUserName() {
        return "admin";
    }


    測試方法

  UserMapper mock = PowerMockito.mock(UserMapper.class);
  when(mock.getUserName()).thenReturn("123");


參數(shù)模糊匹配

前面的測試方法中,參數(shù)我們都是指定的,在一些場景下,對于一些比較復雜的參數(shù),我們不好構造,這時候參數(shù)模糊匹配就派上用場了。如下所示,現(xiàn)有方法selectUser,他有三個參數(shù),參數(shù)類型個不相同。

User selectUser(Integer id, String userName, String password)



當對這個方法進行mock時,可以不用傳入具體的參數(shù)值。就行這樣進行mock。

PowerMockito.when(userMapper.selectUser(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(user);



其中ArgumentMatchers.anyInt()是指任意的int類型的值,ArgumentMatchers.anyString()是指任意String類型的值。需要特別注意的是一個方法中只要有一個參數(shù)使用了模糊匹配,其余的參數(shù)也都需要使用模糊匹配。
mock靜態(tài)方法

對靜態(tài)方法的mock也比較簡單,與普通方法的mock相比只是多了一行代碼。就是首先需要對靜態(tài)方法的所在的類進行mock。

 PowerMockito.mockStatic(DateUtil.class);


同時被mock的類必須要在@PrepareForTest注解中指定,像本例中的DateUtil類。@PrepareForTest({ DateUtil.class}),其他的與普通方法的mock一樣,再此就不在贅述了。
mock私有方法

當我們需要測試的方法中調(diào)用了一個比較復雜的私有方法時,我們該如何mock呢?針對這種情況PowerMock也可以輕松應對。首先調(diào)用spy方法創(chuàng)建出一個新的UserServiceImpl類實例。然后通過這個實例來mock這個私有方法。如下所示:

     UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
     PowerMockito.when(spy, "verifyId", 0).thenReturn(true);

   

使用示范:

    待測試的方法

  boolean delUser(int id) throws Exception {
        int i = userMapper.delUser(id);
        return verifyId(i);
    }

 

    測試方法

   public void testVerifyId() throws Exception {
        // 5.mock私有方法
        UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
        PowerMockito.when(spy, "verifyId", 0).thenReturn(true);
        PowerMockito.when(userMapper.delUser(0)).thenReturn(1);
        Assert.assertEquals(userServiceImpl.delUser(0), true);
    }


總結

本文詳細介紹了PowerMock的常見使用,PowerMock是一個應用比較廣泛的單元測試框架,運用在單元測試中可以很好的提供測試效率。PowerMock可以mock 普通方法,私有方法,靜態(tài)方法,final修飾的方法。
參考

無所不能的PowerMock,mock私有方法,靜態(tài)方法,測試私有方法,final類
power mock 入門介紹及使用示例





作者:碼農(nóng)飛哥
微信公眾號:碼農(nóng)飛哥