Java中使用PowerMockito mock static方法/new对象/mock对象的public或private方法的简单示例
1 针对方法打桩
1.1 打桩类的public static方法
测试用例中如果需要对public静态方法的打桩,针对测试类增加注解@RunWith(PowerMockRunner.class)同时针对静态方法所在的类增加注解@PrepareForTest({StaticMethod.class}),接着在测试用例调用方法之前增加
PowerMockito.mockStatic(StaticMethod.class);
PowerMockito.when(StaticMethod.getJavaVersion()).thenReturn("1.8.0_92"); 或者用写法
PowerMockito.doReturn("1.8.0_92").when(StaticMethod.class, "getJavaVersion");
另外对PowerMockito的调用最好在产品类ProductClass对象new之前进行,这样保障一切模拟数据就绪后再进行产品类的测试,保证测试用例一次性成功编写的几率。
import junit.framework.TestCase; 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({StaticMethod.class}) public class ProductClassTest extends TestCase { public void test_getCustomNameByVersion() { PowerMockito.mockStatic(StaticMethod.class); PowerMockito.when(StaticMethod.getJavaVersion()).thenReturn("1.8.0_92"); ProductClass product = new ProductClass(); assertEquals("18VersionSeries", product.getCustomNameByVersion()); } }
public class ProductClass { public String getCustomNameByVersion() { String javaVersion = StaticMethod.getJavaVersion(); if(javaVersion.split("_")[0].contains("1.8")) { return "18VersionSeries"; } else { return "OTHER"; } } }
public class StaticMethod { public static String getJavaVersion() { throw new RuntimeException(); } }
1.2 打桩类的private static方法
针对StaticMethod类中的private static方法打桩的时候,外部调用StaticMethod类的public方法仍然保持实际代码的调用,因此在模拟private static方法之前,增加一行
PowerMockito.spy(StaticMethod.class);或者
PowerMockito.when(StaticMethod.getJavaVersion()).thenCallRealMethod();
以此保证除了具体的某个方法打桩,其他的方法保持照旧。另外,针对private方法,因为正常是无法直接访问的,因此需要使用
下面的形式进行调用。这里要注意一个事情,doReturn when 和 when thenReturn的差异,后者是虽然做了打桩但是实际的代码还是会走一遍。
PowerMockito.doReturn(true).when(StaticMethod.class, "isProduct");
import junit.framework.TestCase; 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({StaticMethod.class}) public class ProductClassTest extends TestCase { public void test_getCustomNameByVersion_product() throws Exception { PowerMockito.mockStatic(StaticMethod.class); PowerMockito.spy(StaticMethod.class); PowerMockito.doReturn(true).when(StaticMethod.class, "isProduct"); ProductClass product = new ProductClass(); assertEquals("18VersionSeries", product.getCustomNameByVersion()); } }
public class StaticMethod { public static String getJavaVersion() { if (isProduct()) { System.out.println("getJavaVersion()::product"); return System.getProperty("java.version"); } System.out.println("getJavaVersion()::test"); throw new RuntimeException(); } private static boolean isProduct() { if (System.getenv().containsKey("isProduct=false")) { System.out.println("isProduct()::false"); return false; } else { System.out.println("isProduct()::true"); return true; } } }
public class ProductClass { public String getCustomNameByVersion() { String javaVersion = StaticMethod.getJavaVersion(); if(javaVersion.split("_")[0].contains("1.8")) { return "18VersionSeries"; } else { return "OTHER"; } } }
1.3 打桩类的public方法实现部分中使用的new对象
如果要对 StudentHandler.java的new对象进行mock,那么需要把StudentHandler对象所在的类StudentMng放到 @PrepareForTest({StudentMng.class})
需要特别注意,在PrepareForTest中放的类在统计代码覆盖率的时候,覆盖率是0。如果要针对该类进行代码测试覆盖,那需要以其他的方式进行模拟测试。
1 package training; 2 3 import junit.framework.TestCase; 4 import org.junit.runner.RunWith; 5 import org.powermock.api.mockito.PowerMockito; 6 import org.powermock.core.classloader.annotations.PrepareForTest; 7 import org.powermock.modules.junit4.PowerMockRunner; 8 9 10 import java.util.ArrayList; 11 import java.util.List; 12 @RunWith(PowerMockRunner.class) 13 @PrepareForTest({StudentMng.class}) 14 public class StudentMngTest extends TestCase { 15 16 17 public void test_getSpecifiedStudents() throws Exception { 18 StudentHandler handler = PowerMockito.mock(StudentHandler.class); 19 PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents()); 20 PowerMockito.whenNew(StudentHandler.class).withNoArguments().thenReturn(handler); 21 StudentMng mng = new StudentMng(); 22 assertEquals(1, mng.getSpecifiedStudents(10).size()); 23 } 24 25 private List<Student> mockStudents() { 26 List<Student> students = new ArrayList<>(); 27 Student stu = new Student(); 28 stu.setScore(new Score(80, 11)); 29 students.add(stu); 30 return students; 31 } 32 }
1 package training; 2 3 import java.util.List; 4 5 public class StudentHandler { 6 public StudentHandler() { 7 System.out.println("invoke StudentHandler()"); 8 throw new RuntimeException(); 9 } 10 public List<Student> getAllStudents() { 11 System.out.println("invoke getAllStudents"); 12 throw new RuntimeException(); 13 } 14 }
1 package training; 2 3 public class Student { 4 private String sno; 5 private String name; 6 private Score score = new Score(100, 1000); 7 8 public String getName() { 9 System.out.println("invoke getName"); 10 return name; 11 } 12 13 public void setName(String name) { 14 System.out.println("invoke setName"); 15 this.name = name; 16 } 17 18 public String getSno() { 19 System.out.println("invoke getSno"); 20 return sno; 21 } 22 23 public void setSno(String sno) { 24 System.out.println("invoke setSno"); 25 this.sno = sno; 26 } 27 28 public void setScore(Score score) { 29 System.out.println("invoke setScore"); 30 this.score = score; 31 } 32 33 public Score getScore() { 34 System.out.println("invoke getScore"); 35 return score; 36 } 37 38 public String getScoreRecord(int delta) { 39 System.out.println("invoke getScoreRecord"); 40 return String.format("sno:%s,name:%s,value:%d,level:%d", sno, name, score.getValue() + delta, score.getLevel()); 41 } 42 }
1 package training; 2 3 public class Score { 4 private int value; 5 private int level; 6 7 public Score(int value, int level) { 8 this.value = value; 9 this.level = level; 10 } 11 12 public int getLevel() { 13 System.out.println("invoke getLevel"); 14 return level; 15 } 16 17 public void setLevel(int level) { 18 System.out.println("invoke setLevel"); 19 this.level = level; 20 } 21 22 public int getValue() { 23 System.out.println("invoke getValue"); 24 return value; 25 } 26 27 public void setValue(int value) { 28 System.out.println("invoke setValue"); 29 this.value = value; 30 } 31 }
1.4打桩类的public方法
2种方式,方式一:PowerMockito.mock方式,对应StudentTest.java中的test_mock_public_method_powermock()测试用例
方式二:函数复写override方式,对应StudentTest.java中的test_mock_public_method_override()测试用例
两种方式比较,方式一代码看起来简洁。 方式二 测试用例运行时间效率很高。
1 package training; 2 3 import junit.framework.TestCase; 4 import org.powermock.api.mockito.PowerMockito; 5 6 public class StudentTest extends TestCase { 7 public void test_mock_public_method_powermock() 8 { 9 Student stu = PowerMockito.mock(Student.class); 10 PowerMockito.when(stu.getName()).thenReturn("test_name"); 11 PowerMockito.when(stu.getNewName()).thenCallRealMethod(); 12 assertEquals("new_test_name", stu.getNewName()); 13 } 14 15 public void test_mock_public_method_override() 16 { 17 Student stu = new Student() { 18 @Override 19 public String getName() { 20 return "test_name"; 21 } 22 }; 23 assertEquals("new_test_name", stu.getNewName()); 24 } 25 }
1 package training; 2 3 public class Student { 4 private String name; 5 6 7 public String getName() { 8 System.out.println("invoke getName"); 9 throw new RuntimeException(); 10 // return name; 11 } 12 13 public String getNewName() { 14 System.out.println("invoke getNewName"); 15 return "new_" + getName(); 16 } 17 18 }
1.5 打桩类的private方法
Student.java类中getName方法为private的,因此需要使用PowerMockito.when(stu, "getName").thenReturn("test_name"); 方式进行mock。
被模拟的Student类需要在测试用例test类的开头增加以下两行。
@RunWith(PowerMockRunner.class)
@PrepareForTest({Student.class})
1 package training; 2 3 import junit.framework.TestCase; 4 import org.junit.runner.RunWith; 5 import org.powermock.api.mockito.PowerMockito; 6 import org.powermock.core.classloader.annotations.PrepareForTest; 7 import org.powermock.modules.junit4.PowerMockRunner; 8 9 @RunWith(PowerMockRunner.class) 10 @PrepareForTest({Student.class}) 11 public class StudentTest extends TestCase { 12 public void test_mock_public_method_powermock() 13 { 14 Student stu = PowerMockito.mock(Student.class); 15 try { 16 PowerMockito.when(stu, "getName").thenReturn("test_name"); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 PowerMockito.when(stu.getNewName()).thenCallRealMethod(); 21 assertEquals("new_test_name", stu.getNewName()); 22 } 23 24 }
1 package training; 2 3 public class Student { 4 private String sno; 5 private String name; 6 private Score score = new Score(100, 1000); 7 8 private String getName() { 9 System.out.println("invoke getName"); 10 throw new RuntimeException(); 11 // return name; 12 } 13 14 public String getNewName() { 15 System.out.println("invoke getNewName"); 16 return "new_" + getName(); 17 } 18 19 public void setName(String name) { 20 System.out.println("invoke setName"); 21 this.name = name; 22 } 23 24 public String getSno() { 25 System.out.println("invoke getSno"); 26 return sno; 27 } 28 29 public void setSno(String sno) { 30 System.out.println("invoke setSno"); 31 this.sno = sno; 32 } 33 34 public void setScore(Score score) { 35 System.out.println("invoke setScore"); 36 this.score = score; 37 } 38 39 public Score getScore() { 40 System.out.println("invoke getScore"); 41 return score; 42 } 43 44 public String getScoreRecord(int delta) { 45 System.out.println("invoke getScoreRecord"); 46 return String.format("sno:%s,name:%s,value:%d,level:%d", sno, name, score.getValue() + delta, score.getLevel()); 47 } 48 }
2 针对变量打桩
2.1 打桩类的private成员变量
方法一: Whitebox
来源StudentMngTest.java,关键模拟代码如下,Whitebox.setInternalState(mng, "handler", handler);打桩设置对象的私有成员变量。
PowerMockito.when(mng.getSpecifiedStudents(10)).thenCallRealMethod();控制调用实际的方法。
StudentMng mng = PowerMockito.mock(StudentMng.class); 控制StudentMng对象的初始化过程,保证在对象初始化的时候不去做StudentHandler成员变量的初始化。
public void test_getSpecifiedStudents() {
StudentHandler handler = PowerMockito.mock(StudentHandler.class);
PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents());
StudentMng mng = PowerMockito.mock(StudentMng.class);
Whitebox.setInternalState(mng, "handler", handler);
PowerMockito.when(mng.getSpecifiedStudents(10)).thenCallRealMethod();
assertEquals(1, mng.getSpecifiedStudents(10).size());
}
package training; import junit.framework.TestCase; import org.powermock.api.mockito.PowerMockito; import org.powermock.reflect.Whitebox; import java.util.ArrayList; import java.util.List; public class StudentMngTest extends TestCase { public void test_getSpecifiedStudents() { StudentHandler handler = PowerMockito.mock(StudentHandler.class); PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents()); StudentMng mng = PowerMockito.mock(StudentMng.class); Whitebox.setInternalState(mng, "handler", handler); PowerMockito.when(mng.getSpecifiedStudents(10)).thenCallRealMethod(); assertEquals(1, mng.getSpecifiedStudents(10).size()); } private List<Student> mockStudents() { List<Student> students = new ArrayList<>(); Student stu = new Student(); stu.setScore(new Score(80, 11)); students.add(stu); return students; } }
package training; import java.util.List; public class StudentHandler { public StudentHandler() { System.out.println("invoke StudentHandler()"); // throw new RuntimeException(); } public List<Student> getAllStudents() { System.out.println("invoke getAllStudents"); throw new RuntimeException(); } }
package training; public class Student { private String sno; private String name; private Score score = new Score(100, 1000); public String getName() { System.out.println("invoke getName"); return name; } public void setName(String name) { System.out.println("invoke setName"); this.name = name; } public String getSno() { System.out.println("invoke getSno"); return sno; } public void setSno(String sno) { System.out.println("invoke setSno"); this.sno = sno; } public void setScore(Score score) { System.out.println("invoke setScore"); this.score = score; } public Score getScore() { System.out.println("invoke getScore"); return score; } public String getScoreRecord(int delta) { System.out.println("invoke getScoreRecord"); return String.format("sno:%s,name:%s,value:%d,level:%d", sno, name, score.getValue() + delta, score.getLevel()); } }
package training; public class Score { private int value; private int level; public Score(int value, int level) { this.value = value; this.level = level; } public int getLevel() { System.out.println("invoke getLevel"); return level; } public void setLevel(int level) { System.out.println("invoke setLevel"); this.level = level; } public int getValue() { System.out.println("invoke getValue"); return value; } public void setValue(int value) { System.out.println("invoke setValue"); this.value = value; } }
方法二(更加通用):用以下方式修改属性的访问权限,另外 @PrepareForTest({StudentMng.class}) 和 PowerMockito.whenNew(StudentHandler.class).withNoArguments().thenReturn(handler); 同时来达到mock new对象的目的。
Field handlerField = mng.getClass().getDeclaredField("handler");
handlerField.setAccessible(true);
handlerField.set(mng, handler);
1 package training; 2 3 import junit.framework.TestCase; 4 import org.junit.runner.RunWith; 5 import org.powermock.api.mockito.PowerMockito; 6 import org.powermock.core.classloader.annotations.PrepareForTest; 7 import org.powermock.modules.junit4.PowerMockRunner; 8 9 import java.lang.reflect.Field; 10 import java.util.ArrayList; 11 import java.util.List; 12 13 @RunWith(PowerMockRunner.class) 14 @PrepareForTest({StudentMng.class}) 15 public class StudentMngTest extends TestCase { 16 public void test_getSpecifiedStudents() throws Exception { 17 StudentHandler handler = PowerMockito.mock(StudentHandler.class); 18 PowerMockito.when(handler.getAllStudents()).thenReturn(mockStudents()); 19 PowerMockito.whenNew(StudentHandler.class).withNoArguments().thenReturn(handler); 20 StudentMng mng = new StudentMng(); 21 Field handlerField = mng.getClass().getDeclaredField("handler"); 22 handlerField.setAccessible(true); 23 handlerField.set(mng, handler); 24 assertEquals(1, mng.getSpecifiedStudents(10).size()); 25 } 26 27 private List<Student> mockStudents() { 28 List<Student> students = new ArrayList<>(); 29 Student stu = new Student(); 30 stu.setScore(new Score(80, 11)); 31 students.add(stu); 32 return students; 33 } 34 }
2.2 打桩类的public static变量或者private static变量
针对此类情况,建议对static变量赋值的部分进行mock模拟,以此来达到模拟变量值得目的。
3 测试用例执行效率简单说明
override方式模拟打桩方式的执行测试用例时间消耗
< 不带@RunWith(PowerMockRunner.class)和@PrepareForTest({StudentMng.class}) 方式的执行测试用例时间消耗
< 只带@RunWith(PowerMockRunner.class)方式的的执行测试用例时间消耗
< 都带@RunWith(PowerMockRunner.class)和@PrepareForTest({StudentMng.class}) 方式的的执行测试用例时间消耗
关于代码覆盖率:在PrepareForTest中放的类在统计代码覆盖率的时候,覆盖率是0。如果要针对该类进行代码测试覆盖,
那需要以其他的方式进行模拟测试。
关于编写产品代码:建议在编写产品代码的时候,尽量按照对象注入方式,同时减少静态方法的实现方式,
以此也能增加代码的灵活性,同时在给编写测试用例也能带来较大的便利。
浙公网安备 33010602011771号