最新消息:天气越来越冷,记得加一件厚衣裳

PowerMock – 局部变量与方法

Java w3sun 5004浏览 0评论

引言

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);
    }
  }
}

参考资料

转载请注明:雪后西塘 » PowerMock – 局部变量与方法

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址