Mockito的是用来做什么的
Mockito主要用于单元测试过程中模拟被调用方法的
依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.8.0</version>
<scope>test</scope>
</dependency>
版本说明,3.4之前Mockito不能模拟静态方法,所以一般和powermock一起用3.4以后已经不需要powermock,
下面是 powermock的最新版本已 2020年11月1日的版本,所以建议直接使用mockito 3.4以后的版本
<!-- Power Mock -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
创建Mock对象
可以使用 @Mock,@Spy,@InjectMocks 创建模拟对象
@RunWith(MockitoJUnitRunner.class)
public class MockItoTest {
@Mock
GoodsExMapper goodsExMapper;
@Mock
SqlSessionFactory sqlSessionFactory;
@InjectMocks
GoodsServiceImpl goodsServiceImpl;
@Mock
GoodsServiceImpl goodsServiceImplMock;
@Spy
private GoodsServiceImpl goodsServiceImplSpy;
//省略
}
也可以代码里面创建,如果没有在类上写上@RunWith(MockitoJUnitRunner.class,可以使用 MockitoAnnotations.openMocks(this)来让当前teset类里面的@Mock @Spy 等注解生效
/**
* 初始化Mock对象
*/
@Test
public void initMock() {
//如果没有在类上写上@RunWith(MockitoJUnitRunner.class,可以使用 MockitoAnnotations.openMocks(this)来让当前teset类里面的@Mock @Spy 等注解生效
System.out.println( goodsExMapper );
MockitoAnnotations.openMocks(this);
System.out.println( goodsExMapper );
//除了使用@Mock @Spy 以外还能使用对应的方法常见mock对象
Goods goodsMock = Mockito.mock(Goods.class);
Goods goodsSpy = Mockito.spy(Goods.class);
}
@Mock @InjectMocks @Spy 的区别
- @Mock的对象所有方法都不会真实调用,会根据返回值类型的默认值返回,可以通过Mockito.when插桩
- @InjectMocks 总是会调用真的实现方法,可以通过Mockito.when插桩,插桩值影响返回值,真实的方法依旧会被调用
- @Spy 默认调用真实的实现方法,可以通过Mockito.return插桩,插桩后不会调用真实的方法
@Test
public void Mock() {
//@Mock 的对象方法不会被真实调用,放回默认值
System.out.println("goodsServiceImplMock.add(Goods.randomGoods() ) = " + goodsServiceImplMock.add(Goods.randomGoods()));
}
@Test
public void injectMocks() {
//@InjectMocks的对象 方法真实的调用,并且活自动注入mock类(@mock,@spy 标注的成员变量的自动注入)
System.out.println("goodsServiceImpl.add(Goods.randomGoods() ) = " + goodsServiceImpl.add(Goods.randomGoods()));
}
@Test
public void spy() {
//@Spy 的对象方法被真是的调用,但是里面的成员变量不会自动加载,,使用里面的成员变量会报空指针
System.out.println("goodsServiceImplSpy.add(Goods.randomGoods() ) = " + goodsServiceImplSpy.add(Goods.randomGoods()));
}
模拟普通方法
- Mockito.when 的时候被mock方法会调用一次,并且对@InjectMock能用
- Mockito.doReturn 方法不会被调用,并且对@InjectMock不能用
- 被模拟的的时候 @Mock,@Spy 不会调用原来的方法,@InjectMocks总会调用真实的方法,然后修改返回值
@Test
public void mockMethod() {
//@mock Mockito.when 有效
//@mock Mockito.doReturn 有效
//GoodsServiceImpl impl = goodsServiceImplMock;
//@Spy Mockito.when 无效
//@Spy Mockito.doReturn 有效
// GoodsServiceImpl impl = goodsServiceImplSpy;
//@InJect Mockito.when 有效
//@InJect Mockito.doReturn 无效
GoodsServiceImpl impl = goodsServiceImpl;
ReflectionTestUtils.setField(impl,"goodsExMapper",goodsExMapper);
System.out.println("goodsServiceImplSpy.add(new Goods())1 = " + impl.add(new Goods()));
//Mockito.when( impl.add( Mockito.any(Goods.class) ) ).thenReturn( 99 );
Mockito.doReturn(88).when( impl ).add( Mockito.any( Goods.class ));
//给 add方法指定插桩,固定返回99
//这里的方法插桩以后不会真实的调用
System.out.println("goodsServiceImplSpy.add(new Goods())2 = " + impl.add(new Goods()));
}
final方法的模拟
@Test
public void mockFinalMethod() {
GoodsEntity goodsMock = Mockito.mock(GoodsEntity.class);
GoodsEntity goodsSpy = Mockito.spy(GoodsEntity.class);
System.out.println("goodsMock.finalMethd()1 = " + goodsMock.finalMethd());
Mockito.when( goodsMock.finalMethd() ).thenReturn( new GoodsEntity("GoodsMock") );
System.out.println("goodsMock.finalMethd()2 = " + goodsMock.finalMethd());
System.out.println("goodsSpy.finalMethd()1 = " + goodsSpy.finalMethd());
Mockito.when( goodsSpy.finalMethd() ).thenReturn( new GoodsEntity("goodsSpy") );
System.out.println("goodsSpy.finalMethd()2 = " + goodsSpy.finalMethd());
}
final方法的模拟 mock。spy对象对 Mockito.when/Mockito.doReturn都有效
@Test
public void mockFinalMethod2() {
GoodsEntity goodsMock = Mockito.mock(GoodsEntity.class);
GoodsEntity goodsSpy = Mockito.spy(GoodsEntity.class);
System.out.println("goodsMock.finalMethd()1 = " + goodsMock.finalMethd());
Mockito.doReturn(new GoodsEntity("GoodsMock")).when(goodsMock).finalMethd();
System.out.println("goodsMock.finalMethd()2 = " + goodsMock.finalMethd());
System.out.println("goodsSpy.finalMethd()1 = " + goodsSpy.finalMethd());
Mockito.doReturn(new GoodsEntity("GoodsSpy")).when(goodsSpy).finalMethd();
System.out.println("goodsSpy.finalMethd()2 = " + goodsSpy.finalMethd());
}
给私有属性设置值(非私有属性也能用)
@Test
public void setField() throws NoSuchFieldException {
//设置spring的方法
ReflectionTestUtils.setField(goodsServiceImplSpy,"goodsExMapper",goodsExMapper);
//mockito 3.3
//FieldSetter.setField(goodsServiceImplSpy, GoodsServiceImpl.class.getDeclaredField("goodsExMapper"), goodsExMapper);
//mockito 3.5(用法java的反射,不如直接用spring那个)
new InstanceField( GoodsServiceImpl.class.getField("goodsExMapper"), goodsServiceImplSpy).set( goodsExMapper );
System.out.println("goodsServiceImplSpy.add(Goods.randomGoods() ) = " + goodsServiceImplSpy.add(Goods.randomGoods()));
}
模拟静态方法
/**
* 3.4开始支持,3.4以前需要使用powerMock
*
*/
@Test
public void mockStaticMethod() {
Goods goods1 = new Goods();
goods1.setName("good1");
//mock所有静态方法,都是返回默认值
MockedStatic<Goods> goodsMockedStatic = Mockito.mockStatic(Goods.class);
//模拟Goods.randomGoods方法,指定桩
goodsMockedStatic.when(Goods::randomGoods).thenReturn( goods1 );
GoodsEntity goods = GoodsEntity.randomGoods();
GoodsEntity goods2 = GoodsEntity.randomGoods2();
System.out.println(JSONUtil.toJsonStr( goods ));
System.out.println(JSONUtil.toJsonStr( goods2 ));
}
构造方法模拟
@Test
public void mockConstructionMethod() {
MockedConstruction<Goods> goodsMockedConstruction = Mockito.mockConstruction(Goods.class);
//模拟构造方法可以关闭
//goodsMockedConstruction.close();
Goods goods = new Goods();
Goods goods2 = new Goods("1");
System.out.println( goods);
System.out.println( goods2);
System.out.println( JSONUtil.toJsonStr(goods));
System.out.println( JSONUtil.toJsonStr(goods2));
//goodsMockedConstruction.constructed()) 里面记录着通过 mock构造的对象
System.out.println( JSONUtil.toJsonStr(goodsMockedConstruction.constructed()));
}
返回值模拟
@Test
public void retuen() {
GoodsEntity goodsMock = Mockito.mock(GoodsEntity.class);
GoodsEntity goodsSpy = Mockito.spy(GoodsEntity.class);
//指定返回值
Mockito.doReturn("data").when(goodsMock).getData();
System.out.println( goodsMock.getData() );
//指定什么也不做
Mockito.doNothing().when(goodsSpy).setData(Mockito.anyString());
goodsSpy.setData("a");
System.out.println( goodsSpy.getData() );
//调用真实方法
Mockito.doCallRealMethod().when(goodsMock).setData(Mockito.anyString());
Mockito.doCallRealMethod().when(goodsMock).getData();
goodsMock.setData("data2");
System.out.println( goodsMock.getData() );
//doAnswer之定义方法mock 过程,和 doReturn只能改返回值
Mockito.doAnswer(param->{
System.out.println(JSONUtil.toJsonStr( param.getArguments() ));
System.out.println(param.getMock());
return param.getMethod().getName();
}).when(goodsMock).getData();
System.out.println( goodsMock.getData() );
//doAnswer之定义方法mock 过程,和 doReturn只能改返回值
Mockito.doAnswer(param -> {
System.out.println(JSONUtil.toJsonStr(param.getArguments()));
GoodsEntity mock = (GoodsEntity) param.getMock();
ReflectionTestUtils.setField(mock, "data", param.getArgument(0));
return param.getMethod().getName();
}).when(goodsSpy).setData(Mockito.anyString());
goodsSpy.setData("aaa");
System.out.println( goodsSpy.getData() );
//抛出异常
Mockito.doThrow(new RuntimeException("1111")).when( goodsMock ).getDes();
//goodsMock.getDes();
}
参数匹配
@Test
public void argumentMatchers() {
//Mockito 集成了 ArgumentMatchers,里面有很多辅助参数匹配的方法
//参数匹配是用于对指定参数放回不同的返回值
GoodsEntity goodsMock = Mockito.mock(GoodsEntity.class);
GoodsEntity goodsSpy = Mockito.spy(GoodsEntity.class);
//int匹配
Mockito.doReturn(1).when(goodsMock).square(ArgumentMatchers.anyInt());
//空参数匹配
Mockito.doReturn("1100").when(goodsMock).m2(ArgumentMatchers.isNull());
//指定在开头(也提供了指定结尾,或者从包含)
Mockito.doReturn("abc开头").when(goodsMock).m2(ArgumentMatchers.startsWith("abc"));
//自定义匹配,所有的xxxTHat接口都是可以实现ArgumentMatcher接口,然后自定义匹配规则
Mockito.doReturn("123开头").when(goodsMock).m2( ArgumentMatchers.argThat(new ArgumentMatcher<String>() {
@Override
public boolean matches(String arg) {
return arg.startsWith("123");
}
}) );
int square = goodsMock.square(10);
System.out.println( square );
String rt = goodsMock.m2("1234" );
System.out.println("rt = " + rt);
}
验证一个方法是否被调用
@Test
public void verify() {
Goods goodsMock = Mockito.mock(Goods.class);
goodsMock.getDes();
//goodsMock.getDes();
//默认是验证倍调用了一次
//Mockito.verify(goodsMock).getDes();
//没有调用
// Mockito.xxx方法返回 VerificationMode 类型的就是验证用于验证次数的
//Mockito.verify(goodsMock, Mockito.never()).getDes();
//至少多少次
//Mockito.verify(goodsMock, Mockito.atLeastOnce()).getDes();
//Mockito.verify(goodsMock, Mockito.atLeast(2)).getDes();
//至多调用2次
//Mockito.verify(goodsMock, Mockito.atLeast(2)).getDes();
//5秒以后再验证是否被调用(验证调用一次)
Mockito.verify(goodsMock, Mockito.after(5000)).getDes();
}
Mockit可以和TestMe之类的IDEA插件配合生成单元测试代码
插件
可以直接生成
选择使用的是Mockito还是powermock,Junit4还是Junit5
生成的测试代码,生成以后一般需要做一些调整,生成的时候已经对DAO等服务调用模拟了,我们要做的是确认这些方法模拟是我们需要的已经修改对面模拟值。
package com.lomi.service.impl;
import com.lomi.entity.Goods;
import com.lomi.entity.JsonItem;
import com.lomi.entity.in.BatchIn;
import com.lomi.mapper.GoodsExMapper;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.List;
import static org.mockito.Mockito.*;
public class GoodsServiceImplTest {
@Mock
GoodsExMapper goodsExMapper;
@Mock
SqlSessionFactory sqlSessionFactory;
@InjectMocks
GoodsServiceImpl goodsServiceImpl;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testAdd() throws Exception {
when(goodsExMapper.insert(any(Goods.class))).thenReturn(0);
goodsServiceImpl.add(new Goods("name"));
}
@Test
public void testAddBatch() throws Exception {
goodsServiceImpl.addBatch(Arrays.<Goods>asList(new Goods("name")));
verify(goodsExMapper).addBatch(any(List.class));
}
@Test
public void testAddBatchJson() throws Exception {
goodsServiceImpl.addBatchJson(Arrays.<JsonItem>asList(new JsonItem()));
verify(goodsExMapper).addBatchJson(any(List.class));
}
@Test(expected = NullPointerException.class)
public void testAddBatchByExecutorType() throws Exception {
when(goodsExMapper.insert(any(Goods.class))).thenReturn(0);
when(sqlSessionFactory.openSession(any(ExecutorType.class))).thenReturn(null);
goodsServiceImpl.addBatchByExecutorType(new BatchIn());
}
@Test(expected=RuntimeException.class)
public void testTransationKafkaMsg() throws Exception {
when(goodsExMapper.insert(any(Goods.class))).thenReturn(0);
goodsServiceImpl.transationKafkaMsg(new Goods("name"), Boolean.TRUE);
}
@Test(expected = RuntimeException.class)
public void testTestRetryable() throws Exception {
when(goodsExMapper.insert(any(Goods.class))).thenReturn(0);
goodsServiceImpl.testRetryable(new Goods("name"));
}
@Test
public void testRecover() throws Exception {
goodsServiceImpl.recover(new Exception("message", new Throwable("message")), new Goods("name"));
}
}
多次插桩
/**
* 多次插桩,会被覆盖,如果需要依次放回多个值,需要在一次插桩的时候指定
*/
@Test
public void n() {
Goods goodsMock = Mockito.mock(Goods.class);
Mockito.doReturn("1","2").doReturn("3").when(goodsMock).getDes();
Mockito.when( goodsMock.getDes()).thenReturn("a","b").thenReturn("c");
//前三次分别返回a,b,c,之后全是放回c
System.out.println( goodsMock.getDes() );
System.out.println( goodsMock.getDes() );
System.out.println( goodsMock.getDes() );
System.out.println( goodsMock.getDes() );
System.out.println( goodsMock.getDes() );
System.out.println( goodsMock.getDes() );
}
异常处理方法
@Rule
public ExpectedException exception = ExpectedException.none();
/**
* 异常处理方法1
*/
@Test
public void e1() {
exception.expect(RuntimeException.class);
exception.expectMessage("Runtime exception occurred");
throw new RuntimeException("Runtime exception occurred");
}
/**
* 异常处理方法2
*/
@Test(expected = Exception.class)
public void e2() {
ExpectedException exception = ExpectedException.none();
exception.expect(Exception.class);
}
PowerMock静态方法模拟
@RunWith(PowerMockRunner.class)
和相应的@PrepareForTest
注解。
@RunWith(PowerMockRunner.class)
@PrepareForTest(StaticClass.class)
public class StaticMethodTest {
@Test
public void testStaticMethod() throws Exception {
// 配置静态方法的模拟行为
PowerMockito.mockStatic(StaticClass.class);
when(StaticClass.someStaticMethod()).thenReturn("mockedValue");
// 调用依赖于静态方法的代码并验证其行为
MyClass myClass = new MyClass();
String result = myClass.methodUnderTest();
assertEquals("mockedValue", result);
// 验证静态方法是否被正确调用
verifyStatic(StaticClass.class);
StaticClass.someStaticMethod();
}
}
其他的一些说明
- 参数匹配的时候Mockito.anyxxx不包括null
- Mockito.verify方法只是验证方法是否被调用了指定次数,不是调用
- Mockito.whe和Mockito.doReturn用于模拟方法
- Mockito.doThrow用于模拟抛出异常
- void 返回值 使用 Mockito.doNothing 方法
- thenAnswer里面可以自定义模拟方法返回值和调用过程
- Mockito 继承了 ArgumentMatchers,里面有很多辅助参数匹配的方法
- Mockito.xxx方法返回 VerificationMode 类型的就是验证用于验证次数的
- 插桩了,不调用会报错?
貌似有的版本会报错,我用的4.8不会 - @SpyBean 和 @MockBean spring 容器用
如果是@RunWith(SpringRunner.class)方式的spring环境运行,@SpyBean,@MockBean会用模拟对象替换spring容器里面的对象。