目录
引言
Power Mock提供了强大的Mock能力,其中一个体现就是局部Mock变量,而一般情况下在做单元测试的时候我们无法触碰到局部变量。同时其也支持对私有方法、静态方法、final修饰方法的Mock。
Mock变量
Mock局部变量
为了更好的说明局部变量的Mock,需要对Dao层和Service层稍微做出一些修改:
Book
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String name;
private Double price;
}
BookDao
public class BookDao {
public int count() {
throw new UnsupportedOperationException("BookDao does not support count() operation.");
}
public void insert(Book book) {
throw new UnsupportedOperationException("BookDao does not support insert() operation.");
}
}
BookService
@Data
@NoArgsConstructor
public class BookService {
public int count() {
BookDao bookDao = new BookDao();
return bookDao.count();
}
public void save(Book book) {
BookDao bookDao = new BookDao();
System.out.println("...BookService save book...");
bookDao.insert(book);
}
}
BookServiceTestWithPowerMock
//定义使用的Runner类型与需要准备的测试类
@RunWith(PowerMockRunner.class)
@PrepareForTest(BookService.class)
public class BookServiceTestWithPowerMock {
@Test
public void count() throws Exception {
//Mock对象,也可以使用 org.mockito.Mock 注解标记来实现
BookDao bookDao = PowerMockito.mock(BookDao.class);
//录制bookDao的行为以便后续进行回放,具体含义为:当bookDao执行count()时返回一个数值15
PowerMockito.when(bookDao.count()).thenReturn(15);
//录制行为,在创建BookDao实例时,不管参数如何都返回上述mock的bookDao
PowerMockito.whenNew(BookDao.class).withAnyArguments().thenReturn(bookDao);
//创建Service实例
BookService bookService = new BookService();
//执行bookService.count()时会在其中回放第一步录制的行为,也就是说会拿到bookDao.count()返回的值20.
int count = bookService.count();
//断言结果,同时verify是否执行了BookDao的count()方法
Assert.assertEquals(15, count);
Mockito.verify(bookDao).count();
}
@Test
public void save() throws Exception {
//创建Book实例
Book book = new Book("C++ Primer", 30.5);
//Mock对象,也可以使用 org.mockito.Mock 注解标记来实现
BookDao bookDao = PowerMockito.mock(BookDao.class);
//录制bookDao的行为以便后续进行回放,insert为void类型,没有返回值。由于具体含义为:当bookDao执行insert(book)什么也不做.
PowerMockito.doNothing().when(bookDao).insert(book);
//录制行为,在创建BookDao实例时,不管参数如何都返回上述mock的bookDao
PowerMockito.whenNew(BookDao.class).withAnyArguments().thenReturn(bookDao);
//创建Service实例
BookService bookService = new BookService();
//执行保存操作
bookService.save(book);
//验证Mock BookDao对象insert方法没有被调用
Mockito.verify(bookDao).insert(book);
}
}
可以看到BookDao被成功Mock并且在BookService层的测试中也打印了我们期望的结果。
Mock方法
Mock静态方法
有时候代码中需要调用某个静态工具类指定的方法,如果这个方法非常复杂或者依赖了一些其他组件或者服务, 而我们希望”屏蔽”掉该函数的真实调用情况而代码能够继续运行下去, 那么可以对该静态方法进行Mock。为此我们需要对BookDao做出一些轻微的修改将其中的方法静态化以便我们进行测试。
BookDao
public class BookDao {
public static int count() {
throw new UnsupportedOperationException("BookDao does not support count() operation.");
}
public static void insert(Book book) {
throw new UnsupportedOperationException("BookDao does not support insert() operation.");
}
}
BookService
@NoArgsConstructor
public class BookService {
public int count() {
return BookDao.count();
}
public void save(Book book) {
System.out.println("......BookService save book......");
BookDao.insert(book);
}
}
BookServiceTestWithPowerMock
/定义使用的Runner类型,同时需要用注释的形式将需要测试的静态方法提供给 PowerMockPrepareForTest可以加在指定的方法上.
@RunWith(PowerMockRunner.class)
@PrepareForTest({BookService.class, BookDao.class})
public class BookServiceTestWithPowerMock {
@Test
public void count() {
//Mock静态方法所在的类
PowerMockito.mockStatic(BookDao.class);
//录制bookDao的行为以便后续进行回放,具体含义为:当bookDao执行count()时返回一个数值10.
PowerMockito.when(BookDao.count()).thenReturn(10);
//创建Service实例
BookService bookService = new BookService();
//执行bookService.count()时会在其中回放第一步录制的行为,也就是说会拿到bookDao.count()返回的值20.
int bookCount = bookService.count();
//断言结果,同时verify是否执行了BookDao的count()方法
Assert.assertEquals(10, bookCount);
}
@Test
public void save() {
//创建Book实例
Book book = new Book();
//Mock静态方法所在的类
PowerMockito.mockStatic(BookDao.class);
//录制bookDao的行为以便后续进行回放,insert为void类型,没有返回值。由于具体含义为:当bookDao执行insert(book)什么也不做.
PowerMockito.doNothing().when(BookDao.class);
//创建Service实例
BookService bookService = new BookService();
//执行保存操作
bookService.save(book);
//验证Mock BookDao对象insert方法没有被调用
PowerMockito.verifyStatic();
}
}
Mock final修饰的方法
Book
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String name;
private Double price;
}
BookDao
public class BookDao {
public int count() {
throw new UnsupportedOperationException("BookDao does not support count() operation.");
}
public void insert(Book book) {
throw new UnsupportedOperationException("BookDao does not support insert() operation.");
}
public final boolean stored(Book book) {
System.out.println("......confirm whether specified book is stored by BookDao......");
return true;
}
}
BookService
@Data
@NoArgsConstructor
public class BookService {
private BookDao bookDao;
public int count() {
return bookDao.count();
}
public void save(Book book) {
System.out.println("......BookService save book......");
bookDao.insert(book);
}
public final boolean stored(Book book) {
System.out.println("......confirm whether specified book is stored by BookService......");
return bookDao.stored(book);
}
}
BookServiceTestWitPowerMock
@RunWith(PowerMockRunner.class)
@PrepareForTest({BookService.class, BookDao.class})
public class BookServiceTestWitPowerMock {
@Test
public void isStored() {
//mock一个Book实例.
Book book = PowerMockito.mock(Book.class);
//mock一个BookDao实例.
BookDao bookDao = PowerMockito.mock(BookDao.class);
//录制bookDao的行为以便后续进行回放,具体含义为:当bookDao执行stored()时返回一个数值10.
PowerMockito.when(bookDao.stored(book)).thenReturn(false);
//mock一个BookService实例.
BookService bookService = PowerMockito.mock(BookService.class);
//设置service层中的dao.
bookService.setBookDao(bookDao);
//调用store()判断书籍是否存在.
boolean stored = bookService.stored(book);
//断言返回结果
Assert.assertFalse(stored);
}
}
Mock构造方法
构造方法分为有参构造和无参构造两种,分别使用:
PowerMockito.whenNew(X.class).withArguments().thenReturn(X instance)
PowerMockito.whenNew(X.class).withAnyArguments().thenReturn(X instance)或者PowerMockito.whenNew(X.class).withNoArguments().thenReturn(X instance)来实现:
Book
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String name;
private Double price;
}
BookDao
public class BookDao {
public int count() {
throw new UnsupportedOperationException("BookDao does not support count() operation.");
}
public void insert(Book book) {
throw new UnsupportedOperationException("BookDao does not support insert() operation.");
}
}
BookService
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BookService {
private BookDao bookDao;
public int count() {
return bookDao.count();
}
public void save(Book book) {
System.out.println("BookService save book.");
bookDao.insert(book);
}
}
BookServiceTestWitPowerMock
//定义使用的Runner类型,同时需要用注释的形式将需要测试的静态方法提供给 PowerMockPrepareForTest可以加在指定的方法上.
@RunWith(PowerMockRunner.class)
@PrepareForTest({BookService.class, BookDao.class})
public class BookServiceTestWitPowerMock {
@Test
public void save() throws Exception {
String name = "C++ Primer";
Double price = 20.6;
//Mock一个BookDao实例.
BookDao bookDao = PowerMockito.mock(BookDao.class);
//Mock一个Book实例.
Book book = PowerMockito.mock(Book.class);
//录制行为,在创建Book实例时,根据name和price参数构建一个book实例并返回.
PowerMockito.whenNew(Book.class).withArguments(name, price).thenReturn(book);
//录制行为,在创建BookDao实例时,不管参数如何都返回上述mock的bookDao.
PowerMockito.whenNew(BookDao.class).withAnyArguments().thenReturn(bookDao);
//录制bookDao的行为以便后续进行回放,insert为void类型,没有返回值。由于具体含义为:当bookDao执行insert(book)什么也不做.
PowerMockito.doNothing().when(bookDao).insert(book);
BookService bookService = new BookService(bookDao);
//执行保存操作
bookService.save(book);
//验证Mock BookDao对象insert方法没有被调用
Mockito.verify(bookDao).insert(book);
System.out.println(book.getClass());
System.out.println(bookDao.getClass());
}
}
最后打印的BookService层方法信息,Book和BookDao实例信息分别为:
BookService save book. class com.w3sun.mock.method.construct.bean.Book$$EnhancerByMockitoWithCGLIB$$b1ce93f9 class com.w3sun.mock.method.construct.dao.BookDao$$EnhancerByMockitoWithCGLIB$$e7eecb6f
其实PowerMock在mock实例的过程中使用CGLIB进行代理,详细信息可以参考:https://github.com/cglib/cglib/wiki
Mock私有方法
BookService
public class BookService {
public boolean exist(String name) {
return checkExist(name);
}
private boolean checkExist(String name) {
System.out.println("---BookService checkExist---");
throw new UnsupportedOperationException("UserService checkExist unsupported exception.");
}
}
BookServiceTestWitPowerMock
//定义使用的Runner类型,同时需要用注释的形式将需要测试的静态方法提供给 PowerMockPrepareForTest可以加在指定的方法上.
@RunWith(PowerMockRunner.class)
@PrepareForTest(BookService.class)
public class BookServiceTestWithPowerMock {
@Test
public void exist() throws Exception {
String arg = "yidian.io";
//如果使用spy且不满足断言的情况下将会调用真正的方法,否则调用mock的方法。
BookService bookService = PowerMockito.spy(new BookService());
//录制行为,在调用BookService的checkExist方法且在传入参数为yidian.io时返回true
PowerMockito.doReturn(true)
.when(bookService, "checkExist", arg);
//调用方法
boolean exist = bookService.exist(arg);
//断言结果
Assert.assertTrue(exist);
System.out.println("==============");
try {
//如果调用了bookService.exist且传入的参数与录入时指定参数不一致时则会调用真实的方法
bookService.exist("yidian");
fail("Process here will be error.");
} catch (Exception e) {
Assert.assertTrue(e instanceof UnsupportedOperationException);
}
}
}
参考资料
- http://jmock.org/
- http://easymock.org/
- https://site.mockito.org/
- https://junit.org/junit4/
- https://github.com/cglib/cglib
转载请注明:雪后西塘 » PowerMock – 局部变量与方法
