概述
单元测试在 Java 开发中的重要性不言而喻,但有些时候,面对高高的屎山,复杂的依赖,在本地根本无法执行单元测试,这个时候,就需要 Mockito 来救急了。
Mockito是一个流行的Java库,用于创建测试中的模拟对象(mock objects)。它是在单元测试中用于隔离被测试的代码,以便对其进行独立测试。Mockito的主要功能是简化和增强测试的可读性和可维护性。
基本概念
-
模拟对象(Mock Object): 模拟对象是一个虚拟的对象,它模拟了实际对象的行为。通过模拟对象,你可以控制被测对象的依赖行为。
-
桩(Stubbing): 这是指为模拟对象的方法设置预定义的行为。例如,当调用某个方法时,返回指定的值。
-
验证(Verification): 验证是指检查模拟对象的方法是否按照预期被调用。
依赖说明
本文章主要用的依赖如下:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>3.6.28</version><scope>test</scope>
</dependency>
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.6.28</version><scope>test</scope>
</dependency>
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>3.6.28</version><scope>test</scope>
</dependency>
这些依赖项是用于Java项目的单元测试框架和库,主要涉及JUnit 5和Mockito。以下是对每个依赖项的详细讲解:
1. JUnit Jupiter API
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><scope>test</scope>
</dependency>
-
功能:
junit-jupiter-api
是JUnit 5的核心模块之一,提供了编写测试的API。它包含了JUnit 5中所有的测试注解和断言功能,如@Test
、@BeforeEach
、@AfterEach
、@BeforeAll
、@AfterAll
等。 -
使用场景: 这个依赖是编写JUnit 5测试用例的基础,提供了定义和组织测试的基本框架。
-
测试范围: 由于其
scope
被设置为test
,它仅在测试编译和执行时可用,不会被打包到生产代码中。
2. Mockito Core
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.12.4</version><scope>test</scope>
</dependency>
-
功能:
mockito-core
是Mockito的核心库,提供了创建和管理模拟对象的功能。它支持模拟对象、设置桩行为、验证方法调用等。 -
使用场景: 在单元测试中使用Mockito来隔离被测对象的依赖,确保测试的独立性和可控性。它使得测试只关注被测对象的逻辑,而不是其依赖的复杂实现。
-
版本: 版本号
3.12.4
表明这是一个与Java 8兼容的版本,适合在Java 8环境下使用。 -
测试范围: 同样由于其
scope
设置为test
,它仅在测试期间可用。
3. Mockito JUnit Jupiter
<dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><scope>test</scope>
</dependency>
-
功能:
mockito-junit-jupiter
是一个集成模块,专门用于将Mockito与JUnit 5结合使用。它提供了MockitoExtension
,使得Mockito的注解(如@Mock
、@InjectMocks
)能够在JUnit 5环境中自动初始化。 -
使用场景: 当你使用JUnit 5进行测试时,这个模块可以简化Mockito的使用,自动处理注解的初始化,减少手动调用
MockitoAnnotations.initMocks(this)
的需要。 -
测试范围: 由于其
scope
设置为test
,它仅在测试期间可用。
总结
这些依赖项共同构成了一个强大的测试环境:
- JUnit Jupiter API提供了测试框架的基础。
- Mockito Core提供了模拟和验证功能。
- **
Mockito JUnit Jupiter**
简化了Mockito
与JUnit 5
的集成。
通过组合使用这些库,开发者可以编写高效、可维护的单元测试,确保代码的质量和稳定性。
基本使用步骤
- 第一步:创建模拟对象,使用
Mockito.mock()
方法可以创建一个模拟对象。 - 第二步:模拟对象行为,使用
when()
和thenReturn()
方法可以设置方法的返回值。 - 第三步:使用模拟对象,可以对结果进行验证或执行其他行为。
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.List;
import static org.mockito.Mockito.when;
public class MockTest { @Test public void test(){ // 1.创建模拟对象 List<String> list = Mockito.mock(List.class); // 2.模拟对象行为 when(list.get(0)).thenReturn("the first element"); when(list.get(1)).thenReturn("the two element"); when(list.get(2)).thenThrow(new IndexOutOfBoundsException()); // 3.使用对象 System.out.println(list.get(0)); System.out.println(list.get(1)); System.out.println(list.get(2)); }
}
@Mock注解模拟对象
在上面,我们展示了通过Mockito.mock()
方法创建模拟对象的一种方式,除此方式之外,还可以通过@Mock
注解的形式进行模拟。
@Mock
注解是Mockito框架中用于简化模拟对象创建的一种方式。它通过注解的方式来声明一个模拟对象,而不是通过显式调用Mockito.mock()
方法。这种方式使得测试代码更加简洁和易于维护。
使用步骤
- 对要模拟的对象加上
@Mock
注解 - 注解初始化
- 在
JUnit 4
中,必须在测试方法运行之前调用MockitoAnnotations.initMocks(this)
来初始化带有@Mock
注解的字段. - 在
JUnit 5
中,通过MockitoExtension
自动处理。
JUnit 5
使用示例
- 在
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class MockTest { // 1.创建模拟对象 @Mock List<String> list ; @Test public void test(){ // 2.模拟对象行为 when(list.get(0)).thenReturn("the first element"); when(list.get(1)).thenReturn("the two element"); when(list.get(2)).thenThrow(new IndexOutOfBoundsException()); // 3.使用对象 System.out.println(list.get(0)); System.out.println(list.get(1)); System.out.println(list.get(2)); }
}
@InjectMocks
注解依赖注入
@InjectMocks
注解是Mockito框架中的一个强大工具,用于自动将模拟对象(通常由@Mock
注解创建的)注入到被测对象的实例中。它帮助简化依赖注入的过程,使得测试代码更加简洁和易于维护。以下是对@InjectMocks
注解的详细讲解:
基本概念
-
自动注入:
@InjectMocks
用于创建一个类的实例,并自动将标记为@Mock
的模拟对象注入到该实例的字段中。它支持构造函数注入、setter方法注入以及字段注入。 -
依赖管理: 通过使用
@InjectMocks
,你可以在测试中管理被测对象的依赖,而不需要手动编写注入逻辑。
使用步骤
-
声明被测对象: 使用
@InjectMocks
注解声明一个类的实例(被测对象)。 -
声明依赖对象: 使用
@Mock
注解声明被测对象的依赖。 -
初始化注解: 使用
MockitoAnnotations.initMocks(this)
(在JUnit 4
中)或MockitoExtension
(在JUnit 5
中)来初始化这些注解。
使用示例
示例一:普通属性注入
这里以解析坐标为例,输入是地址,输出是坐标,坐标的解析需要依赖高德服务。
坐标解析类
/** * 获取坐标服务,依赖高德地图获取坐标 */
public class LocationService { private GMapLocationService gMapLocationService = new GMapLocationService(); public String getLocation(String address) { return gMapLocationService.getLocation(address); }
}
高德获取坐标服务类
public class GMapLocationService { public String getLocation(String address) { return "0,0"; }
}
测试类
@InjectMocks
标记的类表示这个类成员需要使用@Mock
注解修饰的对象。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class)
public class LocationServiceTest { @Mock private GMapLocationService gMapLocationService; @InjectMocks private LocationService locationService; @Test public void test(){ // 设置 mock 行为 when(gMapLocationService.getLocation("北京市通州区万达广场")).thenReturn("116.304642,39.978442"); // 调用方法 String location = locationService.getLocation("北京市通州区万达广场"); System.out.println(location); }
}
示例二:Spring Bean 对象注入
DemoTableService
依赖DemoTableDao#selectAll
方法。
@ExtendWith(MockitoExtension.class)
public class MockDaoTest { @Mock private DemoTableDao demoTableDao; @InjectMocks private DemoTableService demoTableService; @Test public void test(){ // 创建示例数据 List<DemoTable> mockDatas = new ArrayList<>(); DemoTable demoTable = new DemoTable(); demoTable.setId(1L); demoTable.setName("xiaoming"); demoTable.setAge(18); mockDatas.add(demoTable); // 模拟对象行为 when(demoTableDao.selectAll()).thenReturn(mockDatas); // 方法调用 List<DemoTable> demoTables = demoTableService.selectAll(); System.out.println(demoTables); }
}
结语
使用Mockito
我们可以:
-
隔离测试: 在测试一个类时,使用Mockito来模拟该类的依赖对象,以便将测试的重点放在该类本身的逻辑上。
-
依赖复杂性: 当依赖对象的创建或行为复杂时,通过模拟对象可以避免复杂的设置。
-
边界条件测试: 模拟对象可以轻松地创建异常或边界条件,以测试代码的鲁棒性。
到这里,Mockito
的基础用法就讲完了,其还有一些高级用法:
- 验证行为: 使用
verify()
方法可以验证某个方法是否被调用,以及调用的次数。例如:
verify(mockedList).get(0);
verify(mockedList, times(1)).get(0);
- 参数匹配: 通过
anyInt()
、anyString()
等参数匹配器,可以验证方法调用时的参数。例如:
when(mockedList.get(anyInt())).thenReturn("element");
可以自己尝试去探索啦!
以上,祝你今天愉快!