java单元测试之mock篇

  • 一、什么是mock?
  • 二、为什么要进行mock?
  • 三、IDEA中使用Mock
  • 3.1、引入mock所需依赖
  • 3.1、IDEA单元测试必备快捷键
  • 3.2、Mock测试相关注解
  • @Mock注解
  • @InjectMocks注解
  • 调用PowerMockito.spy()方法
  • Mock使用方式或者技巧
  • 静态方法mockStatic
  • @PrepareForTest的使用场景
  • 阻止代码初始化,包括static{}静态代码块或者static变量的初始化
  • 3.3、问题


一、什么是mock?

Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取的比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。

二、为什么要进行mock?

Mock是为了解决不同的单元之间由于耦合而难于开发、测试的问题。所以,Mock既能出现在单元测试中,也会出现在集成测试、系统测试过程中。Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。

比如一段代码有这样的依赖:

java mock void函数 java mock使用_intellij-idea


当我们需要测试A类的时候,如果没有 Mock,则我们需要把整个依赖树都构建出来,而使用 Mock 的话就可以将结构分解开,像下面这样:

java mock void函数 java mock使用_java_02

三、IDEA中使用Mock

3.1、引入mock所需依赖

<dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>3.9.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-core</artifactId>
      <version>2.0.9</version>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>2.0.9</version>
    </dependency>

    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito2</artifactId>
      <version>2.0.9</version>
      <scope>test</scope>
    </dependency>

3.1、IDEA单元测试必备快捷键

新建UT——Ctrl + Shift + T

双击所要进行单元测试的类,点击Ctrl + Shift + T,新建或者跳转到已创建的UT,这里我们点击Create New Test…新建UT,选择JUnit4,选择好需要测试的方法后点击OK。

java mock void函数 java mock使用_单元测试_03


java mock void函数 java mock使用_intellij-idea_04

3.2、Mock测试相关注解

@Mock注解

@RunWith(PowerMockRunner.class)
public class AnnouncementManagerImplTest {

    @Mock
    AnnouncementMapper announcementMapper;

    @Mock
    AnnouncementRepository announcementRepository;

    @InjectMocks
    @Spy
    AnnouncementManagerImpl announcementManagerImpl = new AnnouncementManagerImpl();

    @Before
    public void before() {
        List<Announcement> res = new ArrayList<>();
        Announcement announcement = new Announcement();
        announcement.setId("dsfsdf");
        announcement.setAnnouncementContent("dsfbuiashf");
        res.add(announcement);

        PowerMockito.when(announcementMapper.queryByParams(Mockito.any(QueryWrapper.class), Mockito.any(Page.class))).thenReturn(res);
        PowerMockito.when(announcementMapper.selectCountByParams(Mockito.any(QueryWrapper.class))).thenReturn(1l);
    }

    @Test
    public void save() {
    }

    @Test
    public void findByPage() {
        QueryParams<AnnouncementDTO> params = new QueryParams<>();
        params.setPage(new Page(1,10));
        List<SortInfo> sort = new ArrayList<>();
        params.setSortInfo(sort);
        List<InputField> fields = new ArrayList<>();
        params.setParams(fields);

        IPage<Announcement> page = announcementManagerImpl.findByPage(params);
        Assert.assertNotNull(page.getTotal());
        Assert.assertNotNull(page.getRecords());

    }
}

从上面代码可以看出,被@Mock修饰后的变量announcementMapper则会产生一个AnnouncementMapper的mock类,不用@mock注解用mock方法来产生一个announcementMapper是一样的效果。

@InjectMocks注解

@RunWith(PowerMockRunner.class)
public class AnnouncementManagerImplTest {

    @Mock
    AnnouncementMapper announcementMapper;

    @Mock
    AnnouncementRepository announcementRepository;

    @InjectMocks
    @Spy
    AnnouncementManagerImpl announcementManagerImpl = new AnnouncementManagerImpl();

    @Before
    public void before() {
        List<Announcement> res = new ArrayList<>();
        Announcement announcement = new Announcement();
        announcement.setId("dsfsdf");
        announcement.setAnnouncementContent("dsfbuiashf");
        res.add(announcement);

        PowerMockito.when(announcementMapper.queryByParams(Mockito.any(QueryWrapper.class), Mockito.any(Page.class))).thenReturn(res);
        PowerMockito.when(announcementMapper.selectCountByParams(Mockito.any(QueryWrapper.class))).thenReturn(1l);
    }

    @Test
    public void save() {
    }

    @Test
    public void findByPage() {
        QueryParams<AnnouncementDTO> params = new QueryParams<>();
        params.setPage(new Page(1,10));
        List<SortInfo> sort = new ArrayList<>();
        params.setSortInfo(sort);
        List<InputField> fields = new ArrayList<>();
        params.setParams(fields);

        IPage<Announcement> page = announcementManagerImpl.findByPage(params);
        Assert.assertNotNull(page.getTotal());
        Assert.assertNotNull(page.getRecords());

    }
}

@InjectMocks注解修饰的类不能修饰抽象或接口类,被它修饰后,其被mock后的成员变量会注入
到@InjectMocks的类中。
比如上面的announcementMapper则会被注入到announcementManagerImpl中,当调用announcementManagerImpl时,原代码里的announcementMapper.selectCountByParams(queryWrapper)方法则会执行mock方法

调用PowerMockito.spy()方法

通过spy,Whilebox.setInternelState方法设置内部依赖

public class AnnouncementManagerImplTest {

    AnnouncementMapper announcementMapper;

    AnnouncementRepository announcementRepository;

    AnnouncementManagerImpl announcementManagerImpl;

    @Before
    public void before() {
        MockitoAnnotations.openMocks(this);
        announcementManagerImpl = PowerMockito.spy(new AnnouncementManagerImpl());
        announcementMapper = PowerMockito.mock(AnnouncementMapper.class);
        Whitebox.setInternalState(announcementManagerImpl,"announcementMapper",announcementMapper);

        List<Announcement> res = new ArrayList<>();
        Announcement announcement = new Announcement();
        announcement.setId("dsfsdf");
        announcement.setAnnouncementContent("dsfbuiashf");
        res.add(announcement);

        PowerMockito.when(announcementMapper.queryByParams(Mockito.any(QueryWrapper.class), Mockito.any(Page.class))).thenReturn(res);
        PowerMockito.when(announcementMapper.selectCountByParams(Mockito.any(QueryWrapper.class))).thenReturn(1l);
    }

    @Test
    public void save() {
    }

    @Test
    public void findByPage() {
        QueryParams<AnnouncementDTO> params = new QueryParams<>();
        params.setPage(new Page(1,10));
        List<SortInfo> sort = new ArrayList<>();
        params.setSortInfo(sort);
        List<InputField> fields = new ArrayList<>();
        params.setParams(fields);

        IPage<Announcement> page = announcementManagerImpl.findByPage(params);
        Assert.assertNotNull(page.getTotal());
        Assert.assertNotNull(page.getRecords());

    }
}

Mock使用方式或者技巧

在测试类上加注解@RunWith(PowerMockRunner.class)
等同于在@Before修饰的before方法中添加MockitoAnnotations.openMocks(this);

@Spy等同于PowerMockito.spy()方法

@Mock等同于PowerMockito.mock()方法

但是调用PowerMockito的方法时,必须要调用Whitebox.setInternalState()设置内部依赖

静态方法mockStatic

@RunWith(PowerMockRunner.class)
@PrepareForTest(IdUtils.class)
public class AnnouncementManagerImplTest {

    @Mock
    AnnouncementMapper announcementMapper;

    @Mock
    AnnouncementRepository announcementRepository;

    @InjectMocks
    @Spy
    AnnouncementManagerImpl announcementManagerImpl = new AnnouncementManagerImpl();

    @Before
    public void before() {
        PowerMockito.mockStatic(IdUtils.class);

        List<Announcement> res = new ArrayList<>();
        Announcement announcement = new Announcement();
        announcement.setId("dsfsdf");
        announcement.setAnnouncementContent("dsfbuiashf");
        res.add(announcement);

        PowerMockito.when(IdUtils.randomUUID()).thenReturn("sahduiashdi");
        PowerMockito.when(announcementMapper.queryByParams(Mockito.any(QueryWrapper.class), Mockito.any(Page.class))).thenReturn(res);
        PowerMockito.when(announcementMapper.selectCountByParams(Mockito.any(QueryWrapper.class))).thenReturn(1l);
    }

    @Test
    public void save() {
    }

    @Test
    public void findByPage() {
        QueryParams<AnnouncementDTO> params = new QueryParams<>();
        params.setPage(new Page(1,10));
        List<SortInfo> sort = new ArrayList<>();
        params.setSortInfo(sort);
        List<InputField> fields = new ArrayList<>();
        params.setParams(fields);

        IPage<Announcement> page = announcementManagerImpl.findByPage(params);
        Assert.assertNotNull(page.getTotal());
        Assert.assertNotNull(page.getRecords());

    }
}

当需要mock静态方法的时候,必须要加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。
添加完注解再使用PowerMockito.mockStatic()方法,mock静态类。

@PrepareForTest的使用场景

当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。

当需要mock final方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是final方法所在的类。

当需要mock静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。

当需要mock私有方法的时候, 只是需要加注解@PrepareForTest,注解里写的类是私有方法所在的类

当需要mock系统类的静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解里写的类是需要调用系统方法所在的类

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

@SuppressStaticInitializationFor("cn.com.jc.utils.IdUtils")

3.3、问题

1、使用@InjectMocks修饰接口类报错

java mock void函数 java mock使用_mock_05


解决方法:

①收到new一个实现类的对象

@InjectMocks
    AnnouncementManager announcementManager = new AnnouncementManagerImpl();

②可以在@Before修饰的before()方法中添加spy方法注入该实现类对象

@InjectMocks
    AnnouncementManagerImpl announcementManager;

    @Before
    public void before() {
        announcementManager = PowerMockito.spy(new AnnouncementManagerImpl());
    }