您的位置:

Mockito:一个强大的Java测试框架

一、Mockito mock静态方法

mockito可以mock掉Java中的静态方法,但需要依赖一个第三方库Power Mock。Power Mock是一个Java测试框架,它增强了Mockito和EasyMock两个框架的功能。

首先,需要在pom.xml中引入以下依赖:


<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-api-mockito2</artifactId>
  <version>2.0.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4-rule-agent</artifactId>
  <version>2.0.0</version>
  <scope>test</scope>
</dependency>

接下来我们会创建一个名为 Calculator 的类,其中有一个名为 add 的静态方法。然后,我们将会测试一个使用 add 方法的类:CalculatorService。


public class Calculator {
    public static int add(int x, int y) {
        return x + y;
    }
}


public class CalculatorService {
    public int sum(int x, int y) {
        return Calculator.add(x, y);
    }
}

现在我们假设要测试 CalculatorService 的 sum 方法。由于用到了 Calculator.add 方法,我们需要使用 Mockito 和 PowerMockito 来 mock 这个方法。


@RunWith(PowerMockRunner.class)
@PrepareForTest(Calculator.class)
public class CalculatorServiceTest {

    @Test
    public void testSum() {
        PowerMockito.mockStatic(Calculator.class);
        Mockito.when(Calculator.add(1, 2)).thenReturn(3);
        CalculatorService calculatorService = new CalculatorService();
        int sum = calculatorService.sum(1, 2);
        Assert.assertEquals(3, sum);
    }
}

在这个测试用例中,我们首先使用 PowerMockito.mockStatic(MockedClass.class) 来 mock 掉 Calculator 类中的静态方法 add。接着,使用 Mockito.when(MockedClass.method(params)).thenReturn(returnValue) 来指定 add 方法在参数为 1 和 2 时的返回值为 3。最后调用 CalculatorService 的 sum 方法进行测试。

二、Mockito指定对象

mockito可以mock掉Java中的具体对象。我们可以使用 Mockito.mock(Class classToMock) 方法来 mock 一个类的实例。也可以使用 Mockito.spy(T object) 方法来 spy 一个对象。

举个例子,我们来模拟一个 Invoice 对象,其中含有两个方法:addItem 和 removeItem。addItem 方法将 item 添加到该发票,并且更新 item 的总价。removeItem 方法将指定的 item 从发票中删除,并且更新 item 的总价。


public class Invoice {
    private List<Item> items = new ArrayList<>();
    private BigDecimal total = BigDecimal.ZERO;
    public void addItem(Item item) {
        items.add(item);
        total = total.add(item.getPrice());
    }
    public void removeItem(Item item) {
        items.remove(item);
        total = total.subtract(item.getPrice());
    }
    public BigDecimal getTotal() {
        return total;
    }
}

使用 Mockito.mock(Class<T> classToMock) 方法来 mock Invoice 类:


@Test
public void testMock() {
    Invoice invoice = Mockito.mock(Invoice.class);
    Mockito.when(invoice.getTotal()).thenReturn(BigDecimal.valueOf(100));
    Assert.assertEquals(BigDecimal.valueOf(100), invoice.getTotal());
}

在这个测试用例中,我们使用 mock 方法来 mock 一个 Invoice 对象。当调用 getTotal() 方法时,该对象将返回一个固定值 100。

现在我们来使用 Mockito.spy(T object) 方法来 spy 一个 Invoice 对象:


@Test
public void testSpy() {
    Item item = new Item("item1", BigDecimal.valueOf(50));
    Invoice invoice = new Invoice();
    Invoice spyInvoice = Mockito.spy(invoice);
    spyInvoice.addItem(item);
    spyInvoice.addItem(item);
    spyInvoice.removeItem(item);
    Assert.assertEquals(BigDecimal.valueOf(50), spyInvoice.getTotal());
}

在这个测试用例中,我们首先实例化了一个正常的 Invoice 对象和一个 spied Invoice 对象。然后我们通过 spyInvoice.addItem(item) 和 spyInvoice.removeItem(item) 的操作来间接修改 spied Invoice 对象的 total 属性。最后,我们使用 assertEquals 来确保 spied Invoice 对象的总价确实为 50。上述代码的正确性可以用下图来描述:

三、Mockito mock void方法

针对 void 方法我们可以使用以下两种方式进行 Mock 单元测试:

第一种方法是使用 Mockito.doNothing().when(mockedObject).voidMethod() 来 mock void 方法:


public class Service {
    public void doSomething(int i) {
        if (i == 0) {
            throw new IllegalArgumentException("i can not be zero");
        }
    }
}

@Test
public void testVoidMethod() {
    Service service = Mockito.mock(Service.class);
    Mockito.doNothing().when(service).doSomething(0);
}

在这个测试用例中,我们使用 Mockito.mock(Class<T> classToMock) 方法来 mock 一个 Service 对象。然后使用 Mockito.doNothing().when(service).doSomething(0) 来指定当 service.doSomething(0) 方法被调用时,什么事情都不用做。

另一种方法是使用 Mockito.verify(mockedObject).voidMethod() 来验证 mock 的 void 方法是否被调用过:


public class Service {
    public void doSomething(int i) {
        if (i == 0) {
            throw new IllegalArgumentException("i can not be zero");
        }
    }
}

@Test
public void testVerifyVoidMethod() {
    Service service = Mockito.mock(Service.class);
    service.doSomething(1);
    Mockito.verify(service).doSomething(1);
}

在这个测试用例中,我们使用 Mockito.mock(Class<T> classToMock) 方法来 mock 一个 Service 对象。然后执行 service.doSomething(1) 方法,并使用 Mockito.verify(service).doSomething(1) 验证该方法确实被调用过一次。

四、Mockito教程

Mockito 是一个流行的 Java 单元测试框架,用于为 Java 开发人员提供方便的 mock 对象功能。Mockito 的主要优点包括易于学习、基本上无需编写代码、在 JUnit 和 TestNG 中无缝集成、可读性和良好的 Javadoc 文档。Mockito 使测试变得容易简单,并提供了一个强大的 API 来创建灵活的测试。

Mockito 的 API 主要围绕 mock 和 spy 两个方法。mock 方法将创建一个新的 mock 对象,并模拟指定类的行为。spy 方法将创建真实对象的模拟对象,并允许部分方法进行模拟、部分方法进行真实调用。

在 Mockito 中,重要的 API 包括 Mockito.mock()、Mockito.when()、Mockito.verify()、Mockito.doReturn() 等等。Mockito API 的使用方式非常流畅,这使得 Mockito 可以嵌入到开发过程中,从而提高代码质量和可维护性。以下是一个基本的 Mockito 教程,介绍如何开始使用 Mockito 来进行 Java 单元测试。

1. 在 Maven 项目的 pom.xml 文件中添加以下依赖项:


<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>3.8.0</version>
  <scope>test</scope>
</dependency>

2. 创建一个 Java 类(下面例子中是 Calculator),并实现一些方法:


public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    public int subtract(int a, int b) {
        return a - b;
    }
    public int multiply(int a, int b) {
        return a * b;
    }
    public int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("division by zero");
        }
        return a / b;
    }
}

3. 创建测试类(下面例子中是 CalculatorTest):


public class CalculatorTest {
    @Test
    public void testAdd() {
        Calculator calculator = Mockito.mock(Calculator.class);
        Mockito.when(calculator.add(1, 2)).thenReturn(3);
        Assert.assertEquals(calculator.add(1, 2), 3);
    }
    @Test
    public void testDivide() {
        Calculator calculator = Mockito.spy(Calculator.class);
        Mockito.when(calculator.divide(6, 2)).thenReturn(3);
        try {
            Assert.assertEquals(calculator.divide(6, 2), 3);
        } catch (Exception e) {
            fail("will not have this exception");
        }
    }
}

4. 运行这些测试。

通过一个简单的四步操作,我们就可以使用 Mockito 进行单元测试。

五、Mockito中文文档

Mockito 中文文档详细的介绍了 Mockito 的用法,回答了许多人在使用 Mockito 过程中可能遇到的问题。文档主要包括:

1. 快速入门:介绍基本的 mock 对象、stub 方法调用等用法

2. 验证 Mock 的调用:介绍如何验证方法的调用次数,以及如何验证连续调用

3. 参数匹配器:介绍如何使用参数匹配器以及一些用法例子

4. Stubbing 流:介绍如何从一个被 Mock 的对象中创建流,并对流进行操作、返回流

5. Mockito 外部依赖:介绍如何使用 Mockito 处理外部依赖

除了这些,该文档还包括 Mockito.Javadoc,Mockito 示例和 Mockito-Framework-3.x.pdf 等内容。

Mockito 中文文档为中文用户提供了方便实用的方法,在使用 Mockito 进行单元测试时有很大的帮助。

六、Mockito怎么throw一个私有方法

Mockito 的 spy 方法不仅可以 spy 一个对象的部分方法。如果 spy 对象内部依然有私有方法,我们还可以使用 Mockito.spy(object) 这个功能,对私有方法进行 mock,即下面的例子


public class PrivateServiceImpl implements PrivateService {
    private String doPrivate(String str) {
        return str + " world!";
    }
}

public interface PrivateService {
    String doPrivate(String str);
}

@Test
public void testThrowPrivateMethodException() throws Exception {
    PrivateServiceImpl spy = Mockito.spy(new PrivateServiceImpl());
    Mockito.when(spy, Methods.PRIVATE.method(PrivateServiceImpl.class, "doPrivate", String.class))
            .thenThrow(new IllegalStateException("test"));
    Class<?>[] args = new Class<?>[] {String.class};
    Method method = PrivateServiceImpl.class.getDeclaredMethod("doPrivate", args);
    method.setAccessible(true);
    try {
        method.invoke(spy, "hello,");
    } catch (InvocationTargetException e) {
        Assert.assertEquals(IllegalStateException.class, e.getTargetException().getClass());
    }
}

在上述