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 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
比如一段代码有这样的依赖:
当我们需要测试A类的时候,如果没有 Mock,则我们需要把整个依赖树都构建出来,而使用 Mock 的话就可以将结构分解开,像下面这样:
三、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。
3.2、Mock测试相关注解
@Mock注解
@RunWith(PowerMockRunner.class)
public class AnnouncementManagerImplTest {@MockAnnouncementMapper announcementMapper;@MockAnnouncementRepository announcementRepository;@InjectMocks@SpyAnnouncementManagerImpl announcementManagerImpl = new AnnouncementManagerImpl();@Beforepublic 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);}@Testpublic void save() {}@Testpublic 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 {@MockAnnouncementMapper announcementMapper;@MockAnnouncementRepository announcementRepository;@InjectMocks@SpyAnnouncementManagerImpl announcementManagerImpl = new AnnouncementManagerImpl();@Beforepublic 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);}@Testpublic void save() {}@Testpublic 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;@Beforepublic 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);}@Testpublic void save() {}@Testpublic 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 {@MockAnnouncementMapper announcementMapper;@MockAnnouncementRepository announcementRepository;@InjectMocks@SpyAnnouncementManagerImpl announcementManagerImpl = new AnnouncementManagerImpl();@Beforepublic 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);}@Testpublic void save() {}@Testpublic 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修饰接口类报错
解决方法:
①收到new一个实现类的对象
@InjectMocksAnnouncementManager announcementManager = new AnnouncementManagerImpl();
②可以在@Before修饰的before()方法中添加spy方法注入该实现类对象
@InjectMocksAnnouncementManagerImpl announcementManager;@Beforepublic void before() {announcementManager = PowerMockito.spy(new AnnouncementManagerImpl());}