Java单元测试对void方法的测试

1 背景介绍

日常系统单测开发都是通过对方法的返回值进行验证。而void方法没有返回值,这是我们可以对其行为进行验证。下面是几个常见的例子,被验证方法均是void方法。

  • 方法内部依赖外部接口,当外部接口超时会最多会重试3次。3次全部重试失败则会抛出异常
  • 方法内部负责聚合外部接口,关键的外部接口每个仅调用一次。

这时我们就可以通过mock并验证这组行为是否发生。在java项目中一般通过mockito来实现。

2 使用mockito进行测试

首先我们准备好要用到的测试类。

public class MyList extends AbstractList<String> {
 
    @Override
    public void add(int index, String element) {
        // no-op
    }
}

2.1 对void方法进行mock及行为验证

我们创建了MyList的Mock对象,这里用到了doNothing,myList的void方法被调用,并传入一个整数和字符串时。

    @Test
    public void whenAddCalledVerified() {
        MyList myList = Mockito.mock(MyList.class);
        Mockito.doNothing().when(myList).add(Mockito.isA(Integer.class), Mockito.isA(String.class));
        myList.add(0, "");
        Mockito.verify(myList, Mockito.times(1)).add(0, "");
    }

方法是Mockito中对void方法会默认完成doNothing的调用,实现void方法的插桩,因此还可以简化写为如下形式。

    @Test
    public void whenAddCalledVerified() {
        MyList myList = Mockito.mock(MyList.class);
        myList.add(0, "");
        Mockito.verify(myList, Mockito.times(1)).add(0, "");
    }

此外还可通过doThrow方法对异常抛出进行模拟,

    @Test(expected = Exception.class)
    public void givenNull_addThrows() {
        MyList myList = Mockito.mock(MyList.class);
        Mockito.doThrow().when(myList).add(Mockito.isA(Integer.class), Mockito.isNull());
        myList.add(0, null);
    }

2.2 参数捕获

前面我们用到了doNothing时,如果就是验证void方法的默认行为,可以省略doNothing。当我们需要捕获参数时,我们可以对默认行为调整。例如验证传入void方法的参数是否符合预期。这时就可以覆盖默认行为,进行参数捕获,最终完成验证。

    @Test
    public void whenAddCalledValueCaptured() {
        MyList myList = Mockito.mock(MyList.class);
        ArgumentCaptor<String> valueCapture = ArgumentCaptor.forClass(String.class);
        Mockito.doNothing().when(myList).add(Mockito.any(Integer.class), valueCapture.capture());
        myList.add(0, "captured");
        Assert.assertEquals("captured", valueCapture.getValue());
    }

2.3 使用Answer对象对void方法调用与返回逻辑进行定制验证

一个方法的行为通常会很复杂,不会像add或set方法一样简单。这种场景我们可以使用Mockito的Answer方法添加我们需要的行为。

    @Test
    public void whenAddCalledAnswered() {
        MyList myList = Mockito.mock(MyList.class);
        Mockito.doAnswer(invocation -> {
            Object arg0 = invocation.getArgument(0);
            Object arg1 = invocation.getArgument(1);

            Assert.assertEquals(3, arg0);
            Assert.assertEquals("answer me", arg1);
            return null;
        }).when(myList).add(Mockito.any(Integer.class), Mockito.any(String.class));
        
        myList.add(3, "answer me");
    }

这里我们通过answer获取调用的入参并进行验证。同理还可以基于invocation对象做其它的事情。

2.4 部分mock

部分mock即我们mock对象后,某些方法调用是直接调用真实方法而不是调用创建的桩模拟的方法。这里我们用到doCallRealMethod(),最后当myList.add被调用,这时是实际的方法被调用。

    @Test
    public void whenAddCalledRealMethodCalled() {
        MyList myList = Mockito.mock(MyList.class);
        Mockito.doCallRealMethod().when(myList).add(Mockito.any(Integer.class), Mockito.any(String.class));
        myList.add(1, "real");
        Mockito.verify(myList,Mockito. times(1)).add(1, "real");
    }

2.5 mock方法内部依赖外部对象方法的验证

@Component
public class MyHandler {

  @AutoWired
  private MyDependency myDependency;

  public int someMethod() {
    ...
    return anotherMethod();
  }

  public int anotherMethod() {...}
}

这种场景,进行mock测试,我们一般使用InjectMocks注解注入依赖,对MyHandler进行测试。同时使用Mock注解创建MyDependency的mock对象。但此时通过verify进行验证时候会产生错误。

@RunWith(MockitoJUnitRunner.class}
class MyHandlerTest {

  @InjectMocks
  private MyHandler myHandler;

  @Mock
  private MyDependency myDependency;

  @Test
  public void testSomeMethod() {
    when(myHandler.anotherMethod()).thenReturn(1);
    assertEquals(myHandler.someMethod() == 1);
  }
}

此时需要结合 @Spy注解与@InjectMocks,实现Mock对象的创建及注解注入,这样即可以实现对含有依赖的void方法进行行为验证。

@RunWith(MockitoJUnitRunner.class)
class MyHandlerTest {

  @Spy  
  @InjectMocks  
  private MyHandler myHandler;  

  @Mock  
  private MyDependency myDependency;  

  @Test  
  public void testSomeMethod() {  
    doReturn(1).when(myHandler).anotherMethod();  
    assertEquals(myHandler.someMethod() == 1);  
    verify(myHandler, times(1)).anotherMethod();  
  }  
}  

2.6 Mockito.any()模拟任意输入

当我们调用方法时,只关注方法被调用,而某个参数具体是什么我们不关注,这时可以使用Mockito.any()方法。

verify(dao).send(eq(user), any()); 

3 参考

[1]https://stackoverflow.com/questions/30774358/how-can-i-mock-methods-of-injectmocks-class
[2]https://www.baeldung.com/mockito-void-methods

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您可以使用Mockito框架来模拟静态方法的行为进行单元测试。Mockito是一个流行的Java测试框架,它可以帮助您创建模拟对象,以替代真实的对象进行测试。 要模拟静态方法,您可以使用PowerMockito框架。PowerMockito是Mockito的扩展,它提供了对静态方法、私有方法和构造函数的模拟支持。 下面是一个示例,展示了如何使用PowerMockito模拟静态方法进行单元测试: ```java import static org.mockito.Mockito.*; // 导入PowerMockito相关的类 import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.junit.Test; // 要模拟的类 class MyClass { public static String staticMethod() { return "Hello, World!"; } } @PrepareForTest(MyClass.class) // 声明需要准备进行测试的类 public class MyTest { @Test public void testStaticMethod() { PowerMockito.mockStatic(MyClass.class); // 使用PowerMockito模拟静态方法 // 模拟静态方法的返回值 when(MyClass.staticMethod()).thenReturn("Mocked!"); // 调用被测试方法 String result = MyClass.staticMethod(); // 验证返回值是否符合预期 assertEquals("Mocked!", result); } } ``` 在上面的示例中,我们使用了`@PrepareForTest`注解来声明需要准备进行测试的类(即包含静态方法的类)。然后,我们使用`PowerMockito.mockStatic()`方法来模拟静态方法。接下来,使用`when().thenReturn()`来指定模拟方法的返回值。最后,调用被测试方法,并通过断言来验证返回值是否与预期相符。 请注意,使用PowerMockito需要对测试类进行适当的配置和依赖管理。您需要在构建工具(如Maven或Gradle)的配置文件中添加PowerMockito和相关依赖。此外,PowerMockito通常与JUnit一起使用,所以您可能需要将JUnit添加为依赖项,并使用`@Test`注解来标记测试方法。 希望这个示例能帮助您开始在Java中模拟静态方法进行单元测试。如果您有其他问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值