Spring 应用进行Mockito 单元测试详解

个人理解

通过mockito给程序设定一个预期值,然后通过mockito仿真执行程序,看执行逻辑输出是否符合预期的结果。主要用于检测逻辑是否正确。由于不是真的执行,因此会隔离真实环境。无法测试底层调用或者sql是否存在问题。

mockito 资源

官网: http://mockito.org
API文档:http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html
项目源码:https://github.com/mockito/mockito

依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.3.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.6.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito</artifactId>
    <version>1.6.5</version>
    <scope>test</scope>
</dependency>

注解

注解 作用 例子
@PowerMockIgnore 忽略一些模块 @PowerMockIgnore("javax.management.*")
@PrepareForTest mock静态类 @PrepareForTest({NumberUtils.class})
@RunWith 启动注解,使用什么来运行程序 @RunWith(MockitoJUnitRunner.class)

注意事项

  • @PowerMockIgnore("javax.management.*")

由于PowerMock的工做原理便是使用自定义的类加载器来加载被修改过的类,从而达到打桩的目的,使用Powermock后会提示classloader错误,所以待测试类中使用到了XML解析相关的包和类,那么测试类前一样须要增长@PowerMockIgnore({"org.xml.", "javax.xml."}),消除类加载器引入报错。

  • @PrepareForTest({NumberUtils.class})

把静态方法mock掉,模拟调用静态方法,返回一个给定的值。

PowerMockito.mockStatic(NumberUtils.class);
NumberUtils numberUtils = PowerMockito.mock(NumberUtils.class);
when(numberUtils.change()).thenReturn("123");
  • 调用无返回的方法
    PowerMockito.doNothing().when(casService).addSupplier(anyLong(), any(ServiceKey.class));

连续调用

@Test(expected = RuntimeException.class)
public void continuousCallTest() {
    // 模拟连续调用返回指望值,若是分开,则只有最后一个有效
    PowerMockito.when(exampleServiceMock.aTest()).thenReturn("1");
    PowerMockito.when(exampleServiceMock.aTest()).thenReturn("2");
    PowerMockito.when(exampleServiceMock.aTest()).thenReturn("3");
    PowerMockito.when(exampleServiceMock.aTest()).thenReturn("4");
    PowerMockito.when(exampleServiceMock.aTest()).thenReturn("1").thenReturn("2").thenThrow(new RuntimeException());
    Assert.assertEquals("1", exampleServiceMock.aTest());
    Assert.assertEquals("2", exampleServiceMock.aTest());
    Assert.assertEquals("3", exampleServiceMock.aTest());
    Assert.assertEquals("4", exampleServiceMock.aTest());
    // 第三次或更多调用都会抛出异常
    exampleServiceMock.aTest();
}

ExampleServiceImplTest

import com.jd.work.example.service.ExampleService;
import com.jd.work.example.utils.RedisCache;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;

@Slf4j
@RunWith(MockitoJUnitRunner.class)
//@RunWith(PowerMockRunner.class)
@PrepareForTest()
@PowerMockIgnore("javax.management.*")
public class ExampleServiceImplTest {

    /**
     * 待测试的具体实现类
     */
    @InjectMocks
    private ExampleServiceImpl exampleService;

    @Mock
    private RedisCache redisCache;

    /**
     * 调用了自身接口的其他方法
     */
    @Mock
    private ExampleService exampleServiceMock;

    @Before
    public void setUp() {
        // mock注解初始化,不加会报错
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void example() {
        PowerMockito.when(exampleServiceMock.bTest()).thenReturn("ok-b");
        String s = exampleService.aTest();
        Assert.assertEquals("ok-b", s);
    }

   @Test(expected = RuntimeException.class)
    public void continuousCallTest() {
        // 模拟连续调用返回指望值,若是分开,则只有最后一个有效
        PowerMockito.when(exampleServiceMock.aTest()).thenReturn("1");
        PowerMockito.when(exampleServiceMock.aTest()).thenReturn("2");
        PowerMockito.when(exampleServiceMock.aTest()).thenReturn("3");
        PowerMockito.when(exampleServiceMock.aTest()).thenReturn("4");
        PowerMockito.when(exampleServiceMock.aTest()).thenReturn("1").thenReturn("2").thenThrow(new RuntimeException());
        Assert.assertEquals("1", exampleServiceMock.aTest());
        Assert.assertEquals("2", exampleServiceMock.aTest());
        Assert.assertEquals("3", exampleServiceMock.aTest());
        Assert.assertEquals("4", exampleServiceMock.aTest());
        // 第三次或更多调用都会抛出异常
        exampleServiceMock.aTest();
    }

    @Mock
    private List<String> list;

    @Test
    public void test() {
        list.add("test");
        verify(list).add("test");
        verify(list, atLeastOnce()).add("test");
        verify(list, atLeast(1)).add("test");
        verify(list, atMost(2)).add("test");
        verify(list, never()).add("test111");
        assertThat(0, equalTo(list.size()));
    }

    @Test
    public void test3() {
        list.add("test");
        verify(list).add("test");
        // open will fail
        // list.clear();
        // 代表上一次verify之后再无与list的交互
        verifyNoMoreInteractions(list);
    }

    @Test
    public void test4() {
        list.add("test");
        // 自始至终都与list无任何交互
        verifyZeroInteractions(list);
    }
}

Mock静态方法

测试类头部需要加@PrepareForTest({StaticClass.class})


@RunWith(PowerMockRunner.class)
@PrepareForTest({StaticClass.class})
public class ExampleTest {

    @Test
    public void test() {
        PowerMockito.mockStatic(StaticClass.class);
        // 静态有返
        when(StaticClass.getStaticName(any())).thenReturn("hi");
        // 静态无返
        doNothing().when(StaticClass.getStaticName(any()));
    }
}

Mock私有方法1

@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
public class ExampleTest {

    @InjectMocks
    private ExampleServiceImpl exampleService;

    @Test
    public void testPrivateMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?>[] params = new Class<?>[]{String.class, Map.class};
        Map<String, Object> paramsMap = new HashMap<>(3);
        paramsMap.put("asf_id", "123");
        paramsMap.put("asf_urge_time", "22222");
        paramsMap.put("asf_apply_time", "24141211");
        Method method = exampleService.getClass().getDeclaredMethod("handleMessageTemplate", params);
        //making private method accessible
        method.setAccessible(true);
        assertEquals("ASF_URGE_SECOND_TEMPLATE_DEFAULT", method.invoke(exampleService, "ASF_URGE_SECOND_TEMPLATE_DEFAULT", paramsMap));

        method.invoke(exampleService, "", paramsMap);
    }
}

Mock无返回方法1

@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
public class ExampleTest {

    @Mock
    private RedisCache redisCache;

    @Test
    public void methodNoReturnTest(){
        PowerMockito.doAnswer(invocation -> {
            Object[] args = invocation.getArguments();
            return "called with arguments: " + Arrays.toString(args);
        }).when(redisCache).zAdd("key", "xx", 30);
    }
}

Mock抛出异常

// 有返回值抛异常
Mockito.when(mockitoTestModel.returnString()).thenThrow(new MyException());

// 无返回值抛异常
Mockito.doThrow(new MyException("TEST")).when(mockitoTestModel).noReturn();

Mock私有方法2

// 私有方法测试,无参 
Method method = PowerMockito.method(TestService.class, "methodName");
method.invoke(testService);
// 私有方法测试,传参
Method method = PowerMockito.method(TestService.class, "methodName", Test1.class, Test2.class);
method.invoke(testService, test1, test2);
// 白盒,有参
Whitebox.invokeMethod(testService, "methodName", "arg1", "agr2", "...");
// 静态私有,有参
assertThat(Whitebox.invokeMethod(NumberUtil.class, "xxx"), is(false));
// 白盒,无参
Whitebox.invokeMethod(humanBotCombinationService, "trackData");

@Test
public void testPrivateMethod2() {
    try {
        Map<String, Object> paramsMap = new HashMap<>(3);
        paramsMap.put("asf_id", "123");
        paramsMap.put("asf_urge_time", "22222");
        paramsMap.put("asf_apply_time", "24141211");
        Method method = PowerMockito.method(ExampleServiceImpl.class, "handleAnnotation", String.class, Map.class);
        method.invoke(exampleService, "ASF_URGE_SECOND_TEMPLATE_DEFAULT", paramsMap);
        //making private method accessible
        assertEquals("ASF_URGE_SECOND_TEMPLATE_DEFAULT", method.invoke(exampleService, "ASF_URGE_SECOND_TEMPLATE_DEFAULT", paramsMap));

        method.invoke(exampleService, "", paramsMap);
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

Mock无返回方法2

@Test
public void whenAddCalledVerfied() {
    List myList = mock(List.class);
    Mockito.doNothing().when(myList).add(isA(Integer.class), isA(String.class));
    myList.add(0, "");

    verify(myList, times(1)).add(0, "");
}

Mock父类

// 当前正在测试ExampleServiceImpl的example方法,ExampleServiceImpl继承BaseServiceImpl类,从而获得一下公共的通用方法。
// 其中getCurrent()为私有方法,getUser()为protected方法。
// ExampleServiceImpl的example方法调用了getUser方法。
BaseServiceImpl underTest = spy(BaseServiceImpl.class);
LoginContext loginContext = new LoginContext();
loginContext.setUser("1233");
underTest.setCurrent(loginContext);

其他

PowerMockito.doNothing().when(executor).start(anyInt()); //给普通方法打桩
PowerMockito.doReturn(configuration).when(HBaseConfiguration.class, "create"); //给静态方法打桩
posted @ 2022-03-26 23:34  itwetouch  阅读(472)  评论(0编辑  收藏  举报