PowerMock笔记

本文介绍了如何使用PowerMock进行单元测试,包括mock静态方法、阻止静态初始化、模拟构造函数、打破封装的进阶示例以及抑制不想要的行为。详细阐述了每个步骤和具体实现方式,提供了丰富的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mock静态方法

有时候代码中需要调用工具类的某个静态函数,但是这个函数非常复杂, 或者依赖了一些其他的东西, 我们希望”屏蔽”掉这个函数调用, 而让代码能够继续运行下去, 那么可以mock掉这个函数调用.

假设, 工具类为 MyUtil.java , 需要mock的方法为methodA()methodB(Object arg1, Object arg2), 单元测试类为MyClassTest.

步骤

  1. 首先在单元测试类 MyClassTest上面添加注解 @PrepareForTest(MyUtil.class), 注意不要在测试方法上面加, 可能会报错.
  2. @Before注解的方法里面初始化mock mockStatic(MyUtil.class), 注意每一个包含了静态方法的类都要初始化mock.
  3. 在测试方法里面进行mock处理.

第3步中有两种处理方式:

  1. 如果静态方法的返回值为void, 我们可能只想跳过这个静态方法, 可以通过以下方式mock

    PowerMockito.doNothing().when(MyUtil.class, "methodA");
  2. 如果静态方法有返回值, 我们可以使用when().thReturn()的方式:

    PowerMockito.when(MyUtil.class, "methodB", arg1, arg2).thenReturn("你想要返回的内容");

阻止静态初始化, 包括static{}静态代码块或者static变量的初始化

static{}静态代码块或者static变量的初始化, 在class被容器load的时候就要被执行,如果执行错误就会导致junit单元测试错误 .

解决方法如下:

@SuppressStaticInitializationFor("com.xxx.test.MyStaticClass") //阻止静态初始化

mock构造函数

有时候在我们的测试代码内部通过new调用构造函数生成临时对象, 这样的对象无法通过在方法调用之前mock出来, 所以需要借助PowerMock来处理.

假设, 被测试的类为MyRealClass.java, 针对被测试类我们编写的单元测试类为MyRealClassTest.java, 在MyRealClass.java的某个被测试方法中调用了 new SomeClass(Object arg1, Object arg2)来创建临时对象.

步骤

  1. 首先需要在单元测试类上MyRealClassTest.java使用@PrepareForTest(MyRealClass.class)注解, 注意这里不是SomeClass.class, 这里填的应该是调用SomeClass.class的调用者类, 也就是MyRealClass.class.

  2. 然后通过whenNew().withAnyArguments().thenReturn()来mock:

    PowerMockito.whenNew(SomeClass.class).withAnyArguments().thenReturn("任何你想返回的对象");

    注意这里, 我不想关心参数是什么, 所以使用的是withAnyArguments(), 其实这样特别省事儿, 可以不用构造参数对象, 用起来很爽快!

    如果真要去关心参数情况, 可以使用其他几种,比如withNoArguments(), withArguments, withParameterTypes.

打破封装(进阶)

翻译: Instant Mock Testing with PowerMock

封装是面向对象编程的基本原则之一. 在一个好的面向对象的设计, 我们最终会得到一些重要的私有方法, 涉及具有重要状态信息的业务操作和字段. 有时, 单独测试它们是非常重要的。接下来, 我们将学习如何编写这种情况下的测试.

例子

  1. 首先定义我们的实体类 Department:

    public class Department {
    
    private List<Employee> employees = new ArrayList<>();
    
    /**
     * 最高薪水
     */
    private long maxSalaryOffered;
    
    public void addEmployee(final Employee employee) {
        employees.add(employee);
        updateMaxSalaryOffered();
    }
    
    private void updateMaxSalaryOffered() {
        maxSalaryOffered = 0;
        for (Employee employee : employees) {
            if (employee.getSalary() > maxSalaryOffered) {
                maxSalaryOffered = employee.getSalary();
            }
        }
    }
    
    public long getMaxSalaryOffered() {
        return maxSalaryOffered;
    }
    }
  2. Department类有两个方法:

    • addEmployee: 这个方法向Department对象中的私有List对象employees中添加一个employee.
    • updateMaxSalaryOffered: 这个方法更新Department提供的最高薪水.
  3. 首先我们来测试addEmployee方法:

    @Test
    public void shouldVerifyThatNewEmployeeIsAddedToTheDepartment() {
       final Department department = new Department();
       final Employee employee = new Employee();
    
       // 向department中添加一个employee
       department.addEmployee(employee);
    
       // 从Department实例中获取私有的成员employees
       final List<Employee> employees = Whitebox.getInternalState(department, "employees");
    
       // 设置断言,验证employee是否被添加到了employees中
       Assert.assertTrue(employees.contains(employee)); 
    }
    
    @Test
    public void shouldAddNewEmployeeToTheDepartment() {
       final Department department = new Department();
       final Employee employee = new Employee();
    
       final ArrayList<Employee> employees = new ArrayList<>();
    
       // 设置私有属性employees
       Whitebox.setInternalState(department, "employees", employees);
    
       // 添加employee
       department.addEmployee(employee);
    
       // 设置断言,验证employee是否被添加到了employees中
       Assert.assertTrue(employees.contains(employee));
    }
  4. 我们可以通过两个途径来测试addEmployee方法:

  5. 方案一:

    • 首先向department中添加employee.
    • 然后通过使用Whitebox.getInternalState方法从department中获得私有成员employees.
    • 最后通过断言判断.
  6. 方案二:

    • 首先通过Whitebox.setInternalState替换department中的employees.
    • 然后向department中添加employee.
    • 最后通过断言判断.
  7. 最后, 我们来测试Department类的私有方法updateMaxSalaryOffered:

    @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);
    
          // 添加employee1和employee2
          final ArrayList<Employee> employees = new ArrayList<>();
          employees.add(employee1);
          employees.add(employee2);
    
          // 替换department的私有成员employees
          Whitebox.setInternalState(department, "employees", employees);
    
          // 调用私有方法updateMaxSalaryOffered
          Whitebox.invokeMethod(department, "updateMaxSalaryOffered");
    
          // 获取私有成员maxSalaryOffered的值
          final long maxSalary = Whitebox..getInternalState(department, "maxSalaryOffered");
    
          // 断言
          Assert.assertEquals(65000, maxSalary); 
      }

抑制不想要的行为

翻译: Instant Mock Testing with PowerMock

有时, 我们可能需要抑制构造器方法、方法、字段或静态初始化器, 因为它们执行一些操作对于执行单元来说不是很理想测试他们自己的代码。在处理第三方库或处理第三方库时,可能会出现这种情况

本节, 我们将讨论如何抑制这些行为.

例子

首先, 我们修改Department类, 让它继承一个基类BaseEntity, 这个基类使得Department的单元测试难以进行.

  1. BaseEntity类如下:

    public class BaseEntity {
    
       /**
        * 默认构造函数抛出UnsupportedOperationException异常 
        */
       public BaseEntity() {
           throw new UnsupportedOperationException();
       } 
    }
  2. 这是一个简单的基类, 它的默认构造函数抛出一个UnsupportedOperationException异常.

  3. 修改后的Department类如下:

      public class Department extends BaseEntity {
    
          private int departmentId;
          private List<Employee> employees = new ArrayList<>();
          /**
           * 最高薪水
           */
          private long maxSalaryOffered;
    
    
          // 构造函数
          public Department(int departmentId) {
              super();
              this.departmentId = departmentId;
          }
    
          public void addEmployee(final Employee employee) {
              employees.add(employee);
              updateMaxSalaryOffered();
          }
    
          private void updateMaxSalaryOffered() {
              maxSalaryOffered = 0;
              for (Employee employee : employees) {
                  if (employee.getSalary() > maxSalaryOffered) {
                      maxSalaryOffered = employee.getSalary();
                  }
              }
          }
    
          public long getMaxSalaryOffered() {
              return maxSalaryOffered;
          }
    
          public int getDepartmentId() {
              return this.departmentId;
          }
      }
  4. 唯一的改变就是Department现在继承BaseEntity.由于BaseEntity的构造函数会抛出异常, 任何人试图实例化Department的时候都会获得这个异常.

  5. 我们在Department的构造函数中也加入了对departmentId成员的初始化, 但是是在调用super()构造函数之后初始化.

  6. 如果我们就这样为Departmentd的构造函数编写测试用例, 我们会获得抛出的异常.

  7. 为了给Department的构造函数编写测试用例, 我们需要”抑制”基类BaseEntity的构造函数的调用:

         @RunWith(PowerMockRunner.class)
         @PrepareForTest(Department.class)
         public class DepartmentTest {
    
             @Test
             public void shouldSuppressTheBaseConstructorOfDepartment() {
                 // 抑制基类的行为
                 PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class));
                 Assert.assertEquals(10, new Department(10).getDepartmentId());
             }
         }
  8. 让我们为Department添加一个方法:

         public void setName(String name) {
             this,name = name;
             super.performAudit(this.name);
         }
  9. DepartmentsetName方法是一个很简单的setter方法, 在setName方法中调用了super.performAudit(this.name)方法,这个方法是在基类中定义的.

         protected void performAudit(String auditInformation) {
             throw new UnsupportedOperationException();
         }
  10. 测试DepartmentsetName方法的测试代码为:

         @Test
         public void shouldSuppressThePerformAuditMethodOfBaseEntity() {
    
             // 抑制基类的行为
             PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class));
             PowerMockito.suppress(PowerMockito.method(BaseEntity.class, "performAudit", String.class));
    
             final Department department = new Department();
             department.setName("Mocking with PowerMock.");
    
             Assert.assertEquals("Mocking with PowerMock.", department.getName());
         }
  11. 让我们再来改变一下BaseEntity类, 添加一个静态代码块.

    public class BaseEntity {
        static {
            String x = null;
            x.toString();
        }
    
        // 省略剩下的代码...
    }
  12. 真如上面所展示的, 静态代码块在执行的时候会抛出NullPointerException异常.

  13. 抑制静态代码块的测试应该像下面一样:

          @RunWith(PowerMockRunner.class)
          @PrepareForTest(Department.class)
          @SuppressStaticInitializationFor("com.gitshah.powermock.BaseEntity")
          public class DepartmentTest {
    
              @Test
            public void shouldSuppressTheInitializerForBaseEntity() {
                PowerMockito.suppress(PowerMockito.constructor(BaseEntity.class));
                Assert.assertNotNull(new Department());
            }
          }
  14. 通过@SuppressStaticInitializationFor注解来抑制静态初始化, 包括静态代码和静态变量的初始化.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值