Java单元测试之模拟利器-使用PowerMock进行Mock测试

原文:http://www.w2bc.com/article/111452

简介

mock是模拟对象,用于模拟真实对象的行为。

Powermock主要用于打桩。比如:方法A的参数需要传入实例B,方法A需要调用B的某个方法B.C()。方法C因为耗时长或者根本没有实现或者其他不方便在单元测试中实现等原因,需要伪造返回,此时Powermock即可派上用场。

PowerMock扩展了EasyMock和Mockito框架,增加了对static和final方法mock支持等功能。这里主要基于PowerMock Mockito API进行介绍。

PowerMock支持JUnit和TestNG,这里基于JUnit。

安装

    下载地址:https://github.com/jayway/powermock/wiki/Downloads。下载" Mockito and JUnit including dependencies"版本。当前版本为”powermock-mockito-junit-1.6.3.zip"。 

    IntelliJ IDEA的设置如下:

    右击工程,选择“Open Module Settings”

    

    

    按下“ALT + Insert”,选择“Jars or directories...", 插入jar包:

    

    点击OK。

    在”Module Settings”对话框中点击“Sources”标签,右击右边底部面板,选择“New Folder...", 命名为test。

    在”Module Settings”对话框中选择test,标识为Test Sources,关闭”Module Settings”对话框

    Eclipse中只需要上述jar包放在工程下的lib目录即可。

    Maven在pom.xml添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     < dependency >
         < groupId >junit groupId >
         < artifactId >junit artifactId >
         < version >4.12 version >
         < scope >test scope >
     dependency >
     < dependency >
         < groupId >org.powermock groupId >
         < artifactId >powermock-module-junit4 artifactId >
         < version >1.6.3 version >
         < scope >test scope >
     dependency >    
     < dependency >    
         < groupId >org.powermock groupId >    
         < artifactId >powermock-api-mockito artifactId >    
         < version >1.6.3 version >    
         < scope >test scope >    
     dependency >        
   dependencies >

快速入门

    下面创建EmployeeController类用于给Employee类执行Create, Read, Update, and Delete (CRUD)。实际工作由EmployeeService完成。getProjectedEmployeeCount方法预计公司员工每年增加20%,并返回近似取整。

     

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  class  EmployeeController {
     
     private  EmployeeService employeeService;
     
     public  EmployeeController(EmployeeService employeeService) {
     
         this .employeeService = employeeService;
     }
     
     public  int  getProjectedEmployeeCount() {
     
         final  int  actualEmployeeCount = employeeService.getEmployeeCount();
         return  ( int ) Math.ceil(actualEmployeeCount *  1.2 );
     }
     
     public  void  saveEmployee(Employee employee) {
     
         employeeService.saveEmployee(employee);
     }    
}

1
2
3
4
5
6
7
8
9
10
public  class  EmployeeService {
     
     public  int  getEmployeeCount() {
         throw  new  UnsupportedOperationException();
     }
     
     public  void  saveEmployee(Employee employee) {
         throw  new  UnsupportedOperationException();
     }    
}

     

    由于getEmployeeCount等方法没有真正实现,我们需要mock:

1
2
3
public  class  Employee {
 
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import  static  org.junit.Assert.*;
 
import  org.junit.Test;
import  org.mockito.Mockito;
import  org.powermock.api.mockito.PowerMockito;
 
public  class  EmployeeControllerTest {
 
     @Test
     public  void  shouldReturnProjectedCountOfEmployeesFromTheService() {
         
         EmployeeService mock = PowerMockito.mock(EmployeeService. class );
         PowerMockito.when(mock.getEmployeeCount()).thenReturn( 8 );
         EmployeeController employeeController =  new  EmployeeController(mock);
         assertEquals( 10 , employeeController.getProjectedEmployeeCount());
     }
     
     @Test
     public  void
     shouldInvokeSaveEmployeeOnTheServiceWhileSavingTheEmployee() {
         
         EmployeeService mock = PowerMockito.mock(EmployeeService. class );    
         EmployeeController employeeController =  new  EmployeeController(mock);
         Employee employee =  new  Employee();
         employeeController.saveEmployee(employee);
         Mockito.verify(mock).saveEmployee(employee);
     }        
}

    

     注意如果上述代码出现莫名其妙的错误,建议先确认所有文件已经保存,再不行重启Eclipse。

    上面的saveEmployee(Employee)没有返回值,我们只需要用verify确认有调用即可。如果注释掉employeeController.saveEmployee(employee);就会有如下报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     Wanted but  not  invoked:
employeeService.saveEmployee(
     Employee@ 51081592
);
- > at EmployeeControllerTest.shouldInvokeSaveEmployeeOnTheServiceWhileSavingTheEmployee(EmployeeControllerTest.java: 27 )
Actually, there were zero interactions with this mock.
     at EmployeeControllerTest.shouldInvokeSaveEmployeeOnTheServiceWhileSavingTheEmployee(EmployeeControllerTest.java: 27 )
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     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.junit.runners.ParentRunner.runLeaf(ParentRunner.java: 325 )
     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java: 78 )
     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java: 57 )
     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.junit.runners.ParentRunner.run(ParentRunner.java: 363 )
     at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java: 86 )
     at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java: 38 )
     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java: 459 )
     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java: 675 )
     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java: 382 )
     at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java: 192 )

    另外有个非常用的MockSettings功能,用于设置mock名、实现额外接口(参见https://groups.google.com/forum/?fromgroups=#!topic/mockito/YM5EF0x90_4)、开启详细日志、注册listener用于mock时通知消息调用。比如:

1
     EmployeeService mock = PowerMockito.mock(EmployeeService. class , Mockito.withSettings().name( "EmployeeServiceMock" ).verboseLogging());

从上面例子可以看出,mock常用于参数,如果是方法内部的局部变量偏多、逻辑过于复杂,mock将是比较痛苦的过程,甚至无从下手。    

注意:Eclipse如果看不到lib,请选中工程目录,按F5刷新。lib中的每个jar,需要右键点击,选择"Build Path"->"Add to Build Path", 添加完毕的效果图如下:

   

模拟静态方法

    修改类Employee:

1
2
3
4
5
6
public  class  Employee {
     
     public  static  int  count() {
         throw  new  UnsupportedOperationException();
     }
}

    修改EmployeeService类的方法:

1
2
3
     public  int  getEmployeeCount() {    
         return  Employee.count();
     }

    新建EmployeeServiceTest类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import  static  org.junit.Assert.*;
 
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.powermock.api.mockito.PowerMockito;
import  org.powermock.core.classloader.annotations.PrepareForTest;
import  org.powermock.modules.junit4.PowerMockRunner;
 
@RunWith (PowerMockRunner. class )
@PrepareForTest (Employee. class )
public  class  EmployeeServiceTest {
 
     @Test
     public  void  shouldReturnTheCountOfEmployeesUsingTheDomainClass() {
         
         PowerMockito.mockStatic(Employee. class );
         PowerMockito.when(Employee.count()).thenReturn( 900 );
         
         EmployeeService employeeService =  new  EmployeeService();
         assertEquals( 900 , employeeService.getEmployeeCount());
         
     }
}

@RunWith(PowerMockRunner.class)语句告诉JUnit用PowerMockRunner执行测试。
@PrepareForTest(Employee.class)语句告诉PowerMock准备Employee类进行测试。适用于模拟final类或有final, private, static, native方法的类。

    
注意这里使用的是mockStatic而不是上面的mock。

    下面我们模拟下返回void的静态方法。在Employee添加加薪方法:

1
2
3
     public  static  void  giveIncrementOf( int  percentage) {
         throw  new  UnsupportedOperationException();
     }

    

    EmployeeService添加相应方法:

1
2
3
4
5
6
7
8
     public  boolean  giveIncrementToAllEmployeesOf( int  percentage) {
         try {
             Employee.giveIncrementOf(percentage);
             return  true ;
         catch (Exception e) {
             return  false ;
         }
     }

    

    修改EmployeeServiceTest类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import  static  org.junit.Assert.*;
 
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.powermock.api.mockito.PowerMockito;
import  org.powermock.core.classloader.annotations.PrepareForTest;
import  org.powermock.modules.junit4.PowerMockRunner;
  
@RunWith (PowerMockRunner. class )
@PrepareForTest (Employee. class )
public  class  EmployeeServiceTest {
  
     @Test
     public  void  shouldReturnTrueWhenIncrementOf10PercentageIsGivenSuccessfully() {
         
         PowerMockito.mockStatic(Employee. class );
         PowerMockito.doNothing().when(Employee. class );
         Employee.giveIncrementOf( 10 );
         EmployeeService employeeService =  new  EmployeeService();
         assertTrue(employeeService.giveIncrementToAllEmployeesOf( 10 ));
         
     }
     
     @Test
     public  void  shouldReturnFalseWhenIncrementOf10PercentageIsNotGivenSuccessfully() {
         
         PowerMockito.mockStatic(Employee. class );
         PowerMockito.doThrow( new  IllegalStateException()).when(Employee. class );
         Employee.giveIncrementOf( 10 );
         EmployeeService employeeService =  new  EmployeeService();
         assertFalse(employeeService.giveIncrementToAllEmployeesOf( 10 ));
     }    
}

    

    PowerMockito.doNothing方法告诉PowerMock下一个方法调用时什么也不做。

    PowerMockito.doThrow方法告诉PowerMock下一个方法调用时产生异常。

    PowerMock使用自定义类加载器和字节码操作来模拟静态方法。对于实例中没有mock的方法,也有默认返回值,比如返回int类型的方法,默认返回0。

    
PowerMockito.doNothing和PowerMockito.doThrow的语法可用于实例方法

    先在Employee类添加方法save:

1
2
3
     public  void  save() {
         throw  new  UnsupportedOperationException();
     }

    

    创建测试EmployeeTest 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import  static  org.junit.Assert.*;
 
import  org.junit.Test;
import  org.powermock.api.mockito.PowerMockito;
 
public  class  EmployeeTest {
     
     @Test ()
     public  void  shouldNotDoAnythingIfEmployeeWasSaved() {
     
         Employee employee = PowerMockito.mock(Employee. class );
         PowerMockito.doNothing().when(employee).save();
         try  {
             employee.save();
         catch (Exception e) {
             fail( "Should not have thrown an exception" );
         }
     }
     
     @Test (expected = IllegalStateException. class )
     public  void  shouldThrowAnExceptionIfEmployeeWasNotSaved() {
     
         Employee employee = PowerMockito.mock(Employee. class );
         PowerMockito.doThrow( new  IllegalStateException()).when(employee).save();
         employee.save();
     }
}

    注意这里doThrow和doNothing方法不会对下一行产生影响。

验证方法调用

    验证断言方法是否调用。

    修改EmployeeService类的saveEmployee方法。

1
2
3
4
5
6
7
     public  void  saveEmployee(Employee employee) {
         if (employee.isNew()) {
             employee.create();
             return ;
         }
         employee.update();
     }

修改Employee类,新增如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     public  boolean  isNew() {
         throw  new  UnsupportedOperationException();
     }
     
     public  void  update() {
         throw  new  UnsupportedOperationException();
     }
     
     public  void  create() {
         throw  new  UnsupportedOperationException();
     }
     
     public  static  void  giveIncrementOf( int  percentage) {
         throw  new  UnsupportedOperationException();
     }

在EmployeeServiceTest类中新增shouldCreateNewEmployeeIfEmployeeIsNew方法, 并新增导入import org.mockito.Mockito;:

1
2
3
4
5
6
7
8
9
10
     @Test
     public  void  shouldCreateNewEmployeeIfEmployeeIsNew() {
     
         Employee mock = PowerMockito.mock(Employee. class );
         PowerMockito.when(mock.isNew()).thenReturn( true );
         EmployeeService employeeService =  new  EmployeeService();
         employeeService.saveEmployee(mock);
         Mockito.verify(mock).create();
         Mockito.verify(mock, Mockito.never()).update();
     }

 Mockito.verify(mock).create()验证调用了create方法。 Mockito.verify(mock, Mockito.never()).update();验证没有调用update方法。

 

下面验证静态方法,在EmployeeServiceTest类添加shouldInvoke_giveIncrementOfMethodOnEmployeeWhileGivingIncrement方法:

   

1
2
3
4
5
6
7
8
9
10
11
     @Test
     public  void  shouldInvoke_giveIncrementOfMethodOnEmployeeWhileGivingIncrement() {
     
         PowerMockito.mockStatic(Employee. class );
         PowerMockito.doNothing().when(Employee. class );
         Employee.giveIncrementOf( 9 );
         EmployeeService employeeService =  new  EmployeeService();
         employeeService.giveIncrementToAllEmployeesOf( 9 );
         PowerMockito.verifyStatic();
         Employee.giveIncrementOf( 9 );
     }

同样,静态验证也要分两步走。

其他验证模式可以验证调用次数:

  • Mockito.times(int n) : This verification mode asserts that the mocked method was invoked exactly 'n' times

  • Mockito.atLeastOnce() : This verification mode asserts that the mocked method was invoked at least once

  • Mockito.atLeast(int n) : This verification mode asserts that the mocked method was invoked at least 'n' times

  • Mockito.atMost(int n) : This verification mode asserts that the mocked method was invoked at most 'n' times

使用Mockito.inOrder还可以验证调用的顺序,注意要导入import org.mockito.InOrder;

   

1
2
3
4
5
6
7
8
9
10
11
12
     @Test
     public  void  shouldInvokeIsNewBeforeInvokingCreate() {
         
         Employee mock = PowerMockito.mock(Employee. class );
         PowerMockito.when(mock.isNew()).thenReturn( true );
         EmployeeService employeeService =  new  EmployeeService();
         employeeService.saveEmployee(mock);
         InOrder inOrder = Mockito.inOrder(mock);
         inOrder.verify(mock).isNew();
         Mockito.verify(mock).create();
         Mockito.verify(mock, Mockito.never()).update();
     }


模拟final类或方法

新增EmployeeIdGenerator类:

1
2
3
4
5
6
public  final  class  EmployeeIdGenerator {
 
     public  final  static  int  getNextId() {
         throw  new  UnsupportedOperationException();
     }
}


在Employee类新增方法:

1
2
3
4
     public  void  setEmployeeId( int  nextId) {
 
         throw  new  UnsupportedOperationException();        
     }


修改EmployeeService类的saveEmployee方法:

1
2
3
4
5
6
7
8
     public  void  saveEmployee(Employee employee) {
         if (employee.isNew()) {
             employee.setEmployeeId(EmployeeIdGenerator.getNextId());
             employee.create();
             return ;
         }
         employee.update();
     }

修改EmployeeServiceTest类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.mockito.Mockito;
import  org.powermock.api.mockito.PowerMockito;
import  org.powermock.core.classloader.annotations.PrepareForTest;
import  org.powermock.modules.junit4.PowerMockRunner;
  
@RunWith (PowerMockRunner. class )
@PrepareForTest (EmployeeIdGenerator. class )
public  class  EmployeeServiceTest {
     
     @Test
     public  void  shouldGenerateEmployeeIdIfEmployeeIsNew() {
         
         Employee mock = PowerMockito.mock(Employee. class );
         PowerMockito.when(mock.isNew()).thenReturn( true );
         PowerMockito.mockStatic(EmployeeIdGenerator. class );
         PowerMockito.when(EmployeeIdGenerator.getNextId()).thenReturn( 90 );
         EmployeeService employeeService =  new
         EmployeeService();
         employeeService.saveEmployee(mock);
         PowerMockito.verifyStatic();
         EmployeeIdGenerator.getNextId();
         Mockito.verify(mock).setEmployeeId( 90 );
         Mockito.verify(mock).create();
     }
}

可见final和static的在类头部处理方法类似, 在测试方法中final和普通方法类似。

 处理构造方法

现在创建新职员的时候要发送欢迎邮件。

新增类WelcomeEmail:

1
2
3
4
5
6
7
8
9
10
public  class  WelcomeEmail {
 
     public  WelcomeEmail( final  Employee employee,  final  String message) {
         throw  new  UnsupportedOperationException();
     }
     
     public  void  send() {
         throw  new  UnsupportedOperationException();
     }
}

 

修改EmployeeService类的saveEmployee方法:

1
2
3
4
5
6
7
8
9
10
11
     public  void  saveEmployee(Employee employee) {
         if (employee.isNew()) {
             employee.setEmployeeId(EmployeeIdGenerator.getNextId());
             employee.create();
             WelcomeEmail emailSender =  new  WelcomeEmail(employee,
             "Welcome to Mocking with PowerMock How-to!" );
             emailSender.send();
             return ;
         }
         employee.update();
     }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.mockito.Mockito;
import  org.powermock.api.mockito.PowerMockito;
import  org.powermock.core.classloader.annotations.PrepareForTest;
import  org.powermock.modules.junit4.PowerMockRunner;
  
@RunWith (PowerMockRunner. class )
@PrepareForTest ({EmployeeIdGenerator. class , EmployeeService. class })
public  class  EmployeeServiceTest {
 
     @Test
     public  void  shouldSendWelcomeEmailToNewEmployees() throws  Exception {
         
         Employee employeeMock =PowerMockito.mock(Employee. class );
         PowerMockito.when(employeeMock.isNew()).thenReturn( true );
         PowerMockito.mockStatic(EmployeeIdGenerator. class );
         WelcomeEmail welcomeEmailMock = PowerMockito.mock(WelcomeEmail. class );
         PowerMockito.whenNew(WelcomeEmail. class ).withArguments(employeeMock,  "Welcome to Mocking with PowerMock How-to!" ).thenReturn(welcomeEmailMock);
         EmployeeService employeeService =  new  EmployeeService();
         employeeService.saveEmployee(employeeMock);
 
         PowerMockito.verifyNew(WelcomeEmail. class ).withArguments(employeeMock,  "Welcome to Mocking with PowerMock How-to!" );
         Mockito.verify(welcomeEmailMock).send();
     }
}

注意PowerMockito.verifyNew的第2个参数支持前面提到的验证模式。PowerMockito.whenNew().withArguments(...).thenReturn()是对构造方法的mock模式,PowerMockito.verifyNew().withArguments()是验证模式。

参数匹配

 PowerMock使用equals方法验证参数。matcher可更加灵活的处理参数。

为EmployeeController类添加如下方法:

   

1
2
3
4
5
6
7
8
     public  Employee findEmployeeByEmail(String email) {    
         return  employeeService.findEmployeeByEmail(email);
     }
         
     public  boolean  isEmployeeEmailAlreadyTaken(String email) {
         Employee employee =  new  Employee();
         return  employeeService.employeeExists(employee);
     }

为EmployeeService类添加如下方法:

   

1
2
3
4
5
6
7
     public  Employee findEmployeeByEmail(String email) {
         throw  new  UnsupportedOperationException();
     }
     
     public  boolean  employeeExists(Employee employee) {
         throw  new  UnsupportedOperationException();
     }

  

为EmployeeControllerTest类新增测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     @Test
     public  void  shouldFindEmployeeByEmail() {
     
         final  EmployeeService mock = PowerMockito.mock(EmployeeService. class );
         final  Employee employee =  new  Employee();
         PowerMockito.when(mock.findEmployeeByEmail(Mockito.startsWith( "deep" ))).thenReturn(employee);
         final  EmployeeController employeeController =  new  EmployeeController(mock);
         assertSame(employee, employeeController.findEmployeeByEmail( "[email protected]" ));
         assertSame(employee, employeeController.findEmployeeByEmail( "[email protected]" ));
         assertNull(employeeController.findEmployeeByEmail( "[email protected]" ));
     }
     
     @Test
     public  void  shouldReturnNullIfNoEmployeeFoundByEmail() {
         
         final  EmployeeService mock = PowerMockito.mock(EmployeeService. class );
         PowerMockito.when(mock.findEmployeeByEmail(Mockito.anyString())).thenReturn( null );
         final  EmployeeController employeeController =  new  EmployeeController(mock);
         assertNull(employeeController.findEmployeeByEmail( "[email protected]" ));
         assertNull(employeeController.findEmployeeByEmail( "[email protected]" ));
         assertNull(employeeController.findEmployeeByEmail( "[email protected]" ));        
     }

 后面还有个基于argThat例子,因为没有搞到源码,意思暂时没有揣度出来。先不涉及。

另外其他类似的内置匹配器如下:Mockito.eq、Mockito.matches、Mockito.any(anyBoolean , anyByte , anyShort , anyChar , anyInt ,anyLong , anyFloat , anyDouble , anyList , anyCollection , anyMap , anySet等等)、Mockito.isNull、Mockito.isNotNull、Mockito.endsWith、Mockito.isA。

 

回答(Answer)

在某些边缘的情况下不可能通过简单地通过PowerMockito.when().thenReturn()模拟,这时可以使用Answer接口。

在EmployeeControllerTest类中增加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import  org.mockito.stubbing.Answer;
   
public  class  EmployeeControllerTest {
   
     @Test
     public  void  shouldFindEmployeeByEmailUsingTheAnswerInterface() {
          
         final  EmployeeService mock = PowerMockito.mock(EmployeeService. class );
         final  Employee employee =  new  Employee();
         
         PowerMockito.when(mock.findEmployeeByEmail(Mockito.anyString())).then( new  Answer() {
 
             public  Employee answer(InvocationOnMock invocation)  throws  Throwable {
                 final  String email = (String) invocation.getArguments()[ 0 ];
                 if (email ==  null return  null ;
                 if (email.startsWith( "deep" ))  return  employee;
                 if (email.endsWith( "packtpub.com" ))  return  employee;
                 return  null ;
             }
         });
         
         final  EmployeeController employeeController =  new  EmployeeController(mock);
         assertSame(employee, employeeController.findEmployeeByEmail( "[email protected]" ));
         assertSame(employee, employeeController.findEmployeeByEmail( "[email protected]" ));
         assertNull(employeeController.findEmployeeByEmail( "[email protected]" ));
     }
          
     @Test
     public  void  shouldReturnCountOfEmployeesFromTheServiceWithDefaultAnswer() {
          
         EmployeeService mock = PowerMockito.mock(EmployeeService. class new  Answer() {
             
             public  Object answer(InvocationOnMock invocation) {
                 return  10 ;
             }
         });
         
         EmployeeController employeeController =  new  EmployeeController(mock);
         assertEquals( 12 , employeeController.getProjectedEmployeeCount());
     }
}


Answer接口指定执行的action和返回值执。 Answer的参数是InvocationOnMock的实例,支持:

  • callRealMethod():调用真正的方法

  • getArguments():获取所有参数

  • getMethod():返回mock实例调用的方法

  • getMock():获取mock实例

第一个测试方法根据不同情况构造不同返回。第2个测试方法设定调用返回的默认值。

使用spy进行部分模拟

现在调整类EmployeeService,拆分saveEmployee为方法:saveEmployee和createEmployee:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     public  void  saveEmployee(Employee employee) {
         if (employee.isNew()) {
             createEmployee(employee);
             return ;
         }
         employee.update();
     }  
     
     void  createEmployee(Employee employee) {
         employee.setEmployeeId(EmployeeIdGenerator.getNextId());
         employee.create();
         WelcomeEmail emailSender =  new  WelcomeEmail(employee,
         "Welcome to Mocking with PowerMock How-to!" );
         emailSender.send();
     }

EmployeeServiceTest类添加测试方法shouldInvokeTheCreateEmployeeMethodWhileSavingANewEmployee:

1
2
3
4
5
6
7
8
9
10
     @Test
     public  void  shouldInvokeTheCreateEmployeeMethodWhileSavingANewEmployee() {
         
         final  EmployeeService spy = PowerMockito.spy( new  EmployeeService());
         final  Employee employeeMock = PowerMockito.mock(Employee. class );
         PowerMockito.when(employeeMock.isNew()).thenReturn( true );
         PowerMockito.doNothing().when(spy).createEmployee(employeeMock);
         spy.saveEmployee(employeeMock);
         Mockito.verify(spy).createEmployee(employeeMock);      
     }

注意spy只能使用PowerMockito.doNothing()/doReturn()/doThrow()。

模拟私有方法

现在我们修改EmployeeService.createEmployee为private,在EmployeeServiceTest类添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.powermock.api.mockito.PowerMockito;
import  org.powermock.core.classloader.annotations.PrepareForTest;
import  org.powermock.modules.junit4.PowerMockRunner;
  
@RunWith (PowerMockRunner. class )
@PrepareForTest ({EmployeeIdGenerator. class , EmployeeService. class })
public  class  EmployeeServiceTest {
     
     @Test
     public  void  shouldInvokeTheCreateEmployeeMethodWhileSavingANewEmployee()  throws  Exception {
          
         final  EmployeeService spy = PowerMockito.spy( new  EmployeeService());
         final  Employee employeeMock = PowerMockito.mock(Employee. class );
         PowerMockito.when(employeeMock.isNew()).thenReturn( true );
         PowerMockito.doNothing().when(spy,  "createEmployee" , employeeMock);
         spy.saveEmployee(employeeMock);
         PowerMockito.verifyPrivate(spy).invoke( "createEmployee" , employeeMock);
     }
}

模拟私有方法还有另外一种相对较复杂的方法,这里不做介绍了。

 

查看封装内容

添加 Department类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import  java.util.ArrayList;
import  java.util.List;
 
public  class  Department {
     
     private  List employees =  new  ArrayList();    
     private  long  maxSalaryOffered;
     public  void  addEmployee( final  Employee employee) {
         employees.add(employee);
         updateMaxSalaryOffered();
     }
     
     /**
     * The private method that keeps track of
     * max salary offered by this department.
     */
     private  void  updateMaxSalaryOffered() {
         maxSalaryOffered =  0 ;
         for  (Employee employee : employees) {
             if (employee.getSalary() > maxSalaryOffered) {
                 maxSalaryOffered = employee.getSalary();
             }
         }
     }
}

修改Employee类的如下方法:

1
2
3
4
5
6
7
     public  long  getSalary() {
         return  salary;      
     }
     
     public  void  setSalary( int  i) {
         salary = i;        
     }

新建DepartmentTest类,添加如下测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import  static  org.junit.Assert.*;
import  java.util.ArrayList;
import  java.util.List;
import  org.junit.Test;
import  org.powermock.reflect.Whitebox;
 
public  class  DepartmentTest {
     @Test
     public  void  shouldVerifyThatNewEmployeeIsAddedToTheDepartment() {
         
         final  Department department =  new  Department();
         final  Employee employee =  new  Employee();
         department.addEmployee(employee);
         final  List employees = Whitebox.getInternalState(department,  "employees" );
         assertTrue(employees.contains(employee));
     }
     
     
     @Test
     public  void  shouldAddNewEmployeeToTheDepartment() {
         
         final  Department department =  new  Department();
         final  Employee employee =  new  Employee();
         final  ArrayList employees =  new  ArrayList();
         Whitebox.setInternalState(department,  "employees" , employees);
         department.addEmployee(employee);
         assertTrue(employees.contains(employee));
     }
     
     @Test
     public  void  shouldVerifyThatMaxSalaryOfferedForADepartmentIsCalculatedCorrectly()  throws  Exception
     {
         
         final  Department department =  new  Department();
         final  Employee employee1 =  new  Employee();
         final  Employee employee2 =  new  Employee();
         employee1.setSalary( 60000 );
         employee2.setSalary( 65000 );
         //Adding two employees to the test employees list.
         final  ArrayList employees =  new  ArrayList();
         employees.add(employee1);
         employees.add(employee2);
         Whitebox.setInternalState(department,  "employees" , employees);
         Whitebox.invokeMethod(department, "updateMaxSalaryOffered" );
         final  long  maxSalary = Whitebox.getInternalState(department,  "maxSalaryOffered" );
         assertEquals( 65000 , maxSalary);
     }
}

 Whitebox.getInternalState(department, "employees")类似堆栈,查看变量的值。Whitebox.setInternalState(department, "employees",

employees)设置变量的值。 Whitebox.invokeMethod(department, "updateMaxSalaryOffered")调用方法。

更多参考:http://powermock.googlecode.com/svn/docs/powermock-1.5/apidocs/org/powermock/reflect/Whitebox.html。


ads_sms中测试手机号码发送短信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package  com.oppo.os.ads.sms.sevice;
 
import  static  org.junit.Assert.*;
 
import  org.junit.After;
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.powermock.reflect.Whitebox;
import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.test.context.ContextConfiguration;
import  org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
 
@RunWith (SpringJUnit4ClassRunner. class
@ContextConfiguration (locations= "classpath:dubbo/ads-sms-svc.xml"
public  class  SmsTaskTest {
     
     @Autowired
     SmsTask svc;
 
     @After
     public  void  tearDown()  throws  Exception {
     }
 
     @Test
     public  void  checkLimitOk()  throws  Exception {
         System.out.println( "initAccUser" );
         boolean  result = Whitebox.invokeMethod(svc, "checkLimit" 6 "13246680798" 3 20160121 20160121 );
         assertEquals( true , result);
     }
 
     @Test
     public  void  checkLimitFail()  throws  Exception {
         System.out.println( "initAccUser" );
         boolean  result = Whitebox.invokeMethod(svc, "checkLimit" 0 "13246680998" 3 20160121 20160121 );
         assertEquals( false , result);
     }
}


ads_ad 中的pom.xml配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
         < dependency >    
             < groupId >org.powermock groupId >    
             < artifactId >powermock-module-junit4 artifactId >    
             < version >1.6.3 version >    
             < scope >test scope >    
         dependency >        
         < dependency >    
             < groupId >org.powermock groupId >    
             < artifactId >powermock-api-mockito artifactId >    
             < version >1.6.3 version >    
             < scope >test scope >    
         dependency >    
         < dependency >
               < groupId >org.powermock groupId >
               < artifactId >powermock-module-junit4-rule artifactId >
               < version >1.6.3 version >
               < scope >test scope >
         dependency >        
         
         < dependency >
               < groupId >org.powermock groupId >
               < artifactId >powermock-classloading-xstream artifactId >
               < version >1.6.3 version >
               < scope >test scope >
         dependency >

测试文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package  com.oppo.os.ads.ad.cpd.app.service;
 
import  org.junit.Rule;
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.powermock.core.classloader.annotations.PrepareForTest;
import  org.powermock.modules.junit4.rule.PowerMockRule;
import  org.powermock.reflect.Whitebox;
import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.test.context.ContextConfiguration;
import  org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import  static  org.mockito.Matchers.*;
 
import  com.oppo.os.ads.ad.cpd.app.entity.AdsAdBid;
 
@RunWith (SpringJUnit4ClassRunner. class
@ContextConfiguration (locations= "classpath:dubbo/ad-cpd-app-svc.xml" )
public  class  AdsAdAppBidSrvTest {
     
     //@Rule
     //public PowerMockRule rule = new PowerMockRule();
 
     @Autowired
     AdsAdAppBidSrv appSrvImpl;
     
     @Test
     public  void  testAccOutDayBudgetString() {
         appSrvImpl.accOutDayBudget( "2839488" );
     }
 
     @Test
     public  void  testAccOutDayBudgetInt() {
         appSrvImpl.accOutDayBudget( 2839488 );
     }
 
     @Test
     public  void  testOutDayBudget() {
         appSrvImpl.outDayBudget( 5160 );
     }
 
     @Test
     public  void  testBalanceInsufficientString() {
         appSrvImpl.balanceInsufficient( "2839488" );
     }
 
     @Test
     public  void  testBalanceInsufficientLong() {
         appSrvImpl.balanceInsufficient( 2839488 );
     }
 
     @Test
     public  void  testAddAd() {
         AdsAdBid bid =  new  AdsAdBid();
         bid.setId(5160L);
         appSrvImpl.addAd(bid);
     }
 
     @Test
     public  void  testUpdateAd() {
         AdsAdBid bid =  new  AdsAdBid();
         bid.setId(5160L);
         bid.setName( "789798" );
         appSrvImpl.updateAd(bid);
     }
     
 
     @Test
     public  void  testStartAd() {
         appSrvImpl.startAd( 5160 );
     }
 
     @Test
     public  void  testPauseAd() {
         AdsAdBid bid =  new  AdsAdBid();
         bid.setId(5160L);
         appSrvImpl.pauseAd(bid);
     }
     
     @Test
     public  void  testDeleteAdCache()  throws  Exception {
 
         Whitebox.invokeMethod(appSrvImpl, "deleteAdCache" "1001" );
     }
 
 
}



禁用非预期行为

新增类BaseEntity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  class  BaseEntity {
 
     static  {
         String x =  null ;
         x.toString();
     }
     
     public  BaseEntity() {
         throw  new  UnsupportedOperationException();
     }
     
     protected  void  performAudit(String auditInformation) {
         throw  new  UnsupportedOperationException();
     }
}

修改类Department:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public  class  Department  extends  BaseEntity {
 
     private  int  departmentId;
     private  String name;
     
     public  Department( int  departmentId) {
         super ();
         this .departmentId = departmentId;
     }
     
     public  void  setName(String name) {
         this .name = name;
         super .performAudit( this .name);
     }    
     
     protected  void  performAudit(String auditInformation) {
         throw  new  UnsupportedOperationException();
     }
     
     public  Object getDepartmentId() {
         return  departmentId;
     }
     
     public  Object getName() {
         return  name;
     }
}

修改类DepartmentTest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import  static  org.junit.Assert.*;
 
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.mockito.Mockito;
import  org.powermock.api.mockito.PowerMockito;
import  org.powermock.core.classloader.annotations.PrepareForTest;
import  org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
import  org.powermock.modules.junit4.PowerMockRunner;
 
 
@RunWith (PowerMockRunner. class )
@PrepareForTest (Department. class )
@SuppressStaticInitializationFor ( "BaseEntity" )
public  class  DepartmentTest {
     
     @Test
     public  void  shouldSuppressTheBaseConstructorOfDepartment() {
         PowerMockito.suppress(PowerMockito.constructor(BaseEntity. class ));
         assertEquals( 10 new  Department( 10 ).getDepartmentId());
     }
     
     @Test
     public  void  shouldSuppressThePerformAuditMethodOfBaseEntity() {
         PowerMockito.suppress(PowerMockito.constructor(BaseEntity. class ));
         PowerMockito.suppress(PowerMockito.method(BaseEntity. class "performAudit" , String. class ));
         final  Department department =  new  Department( 18 );
         department.setName( "Mocking with PowerMock" );
         assertEquals( "Mocking with PowerMock" , department.getName());
     }
     
     @Test
         public  void  shouldSuppressTheInitializerForBaseEntity() {
         
             PowerMockito.suppress(PowerMockito.constructor(BaseEntity. class ));
             assertNotNull( new  Department( 18 ));
     }
}

注意测试代码并未试验成功。

PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class));表示禁用BaseEntity的构造函数。PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class, String.class, Integer.class))后面表示带字符串和整数参数。

PowerMockito.suppress(PowerMockito.method(BaseEntity.class, "performAudit", String.class))表示禁用BaseEntity的performAudit方法。

@SuppressStaticInitializationFor("BaseEntity")表示禁用BaseEntity的静态初始化。注意引号部分通常需要全名,比如"com.gitshah.powermock.BaseEntity"。

PowerMockito.suppress(PowerMockito.field(BaseEntity.class,"identifier")):禁用域。

参考资料

简明教程: https://github.com/jayway/powermock

https://github.com/jayway/powermock/wiki/MockitoUsage

https://www.ibm.com/developerworks/cn/java/j-lo-powermock/

 书籍:Instant Mock Testing with PowerMock

你可能感兴趣的:(单元测试)