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 {
@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修饰接口类报错
解决方法:
①收到new一个实现类的对象
@InjectMocks
AnnouncementManager announcementManager = new AnnouncementManagerImpl();
②可以在@Before修饰的before()方法中添加spy方法注入该实现类对象
@InjectMocks
AnnouncementManagerImpl announcementManager;
@Before
public void before() {
announcementManager = PowerMockito.spy(new AnnouncementManagerImpl());
}