-
-
我用的spring版本是2.2.2。
其实引入一个就行:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
里面就会引入单元测试相关的一系列jar,包括junit4和jupiter(junit5)都都引入了。
比如
spring-boot-test、spring-boot-test-autoconfigure、spring-test,
还有
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.2</version>
<scope>compile</scope>
</dependency>【里面包括jupiter一系列的jar包,可以看到junit是4,jupiter是5】
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.13.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>【mockito-core-3.1.0.jar,核心mock相关都在里面】
等等。
另外看使用情况可能需要再单独引入:
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>【junit-4.12.jar】
<dependency>
<groupId>io.gitee.ForteScarlet</groupId>
<artifactId>mock.java</artifactId>
<version>1.7.1</version>
</dependency>
引入的jar是mock.java-1.7.1.jar(MockObject、Mock等)
-
test文件夹下是有自己独立的resources的:
bootstrap.yml
application-junit.yml
有了这些配置才能在test类上面加上注解:
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = RrmApplication.class, properties = {"system.data-auth.enabled=true"})
这样跑test时就会启动spring并支持spring的注解。
当然也可以不加这两个注解,那就跑普通test不启动spring相关。
-
在pom.xml中加入:
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>mongodb-plus-spring-boot-starter</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
另外还要在test下的application-junit.yml加上:
spring:
data:
mongodb:
uri: mongodb://localhost:27017/fms_rrm
-
在pom.xml中加入:
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.3</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
在test下的application-junit.yml加上:
spring:
redis:
host: localhost
port: 6380
-
0.@Test 修饰在方法上
1.@DisplayName 说明,运行时会看到,修饰在类上和方法上都可以
2.@before 只跑一次
3. @beforeEach每个test方法前都会跑一次
@BeforeAll
@AfterEach
@AfterAll
一般:
@BeforeEach
void init() {mongodb构造数据等}
@AfterEach
void clear() {mongodb清理数据等}
4. JUnit 4到5迁移吗?从@RunWith到@ExtendWith的过渡(修饰在类上)
当涉及Spring时:
如果您想在测试中使用Spring测试框架功能(例如)@MockBean,则必须使用@ExtendWith(SpringExtension.class)。它取代了不推荐使用的JUnit4@RunWith(SpringJUnit4ClassRunner.class)
当不涉及Spring时:
例如,如果您只想涉及Mockito而不必涉及Spring,那么当您只想使用@Mock/ @InjectMocks批注时,可写成这样:@ExtendWith(MockitoExtension.class),因为它不会加载到很多不需要的Spring东西中。它替换了不推荐使用的JUnit4 @RunWith(MockitoJUnitRunner.class)。
5. @MockBean
@Mock
@InjectMocks
For1:
@InjectMocks
private SomeHandler someHandler;
@Mock
private OneDependency oneDependency; // 此mock将被注入到someHandler
InjectMocks,就好比是service层的对象。Mock好比是dao层对象。
当然也可以用代码的方式将私有属性注入对象中:
public static void setProperty(Object target, String name, Object value) throws NoSuchFieldException {
Field field = target.getClass().getDeclaredField(name);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, target, value);
}
6.@SpringBootTest(classes = RrmApplication.class, properties = {"system.data-auth.enabled=true"})
修饰在类上。
如果单元测试要启动spring相关,一个例子如下:
@ExtendWith({SpringExtension.class})
@SpringBootTest(classes = {RrmApplication.class})
@DisplayName("清除机器人位置坐标信息")
class ClearRobotLocationInfoTest {...}
7.@Ignore 修饰类,忽略该单元测试类(修饰方法貌似没用)
8.启用内置kafka: @EmbeddedKafka(topics = {"fms.rrm.clearPositionInfo"})
-
-
示例:
@Test
@DisplayName("查询数据服务日志审计-总条数为0")
void test_pageCallLog_countIsZero() {
PageCallLogReq req = buildPageCallLogReq(LocalDate.now(), LocalDate.now(), null);
Mockito.when(repository.countCallLog(req.getParams())).thenReturn(0L);
PageResp<List<PageCallLogDTO>> rest = dataServiceImpl.pageCallLog(req);
assertEquals(rest.getTotal(), 0L);
assertNull(rest.getData());
}
测试方法命名规范:test_[要测试的方法]_[要测试的分支情况]
-
一般是不建议单独针对protected与private方法去做单元测试,单元测试应该是只针对public方法,在public方法中调用的protected与private方法也会被同时测试到。
如果实在要测试protected与private方法,则要采用反射方式去调用。
For:
@InjectMocks
private IccidPosListener iccidPosListener = new IccidPosListener();
@Mock
private AsyncIccidPosDeal asyncIccidPosDeal;
@Test
@DisplayName("handleIccid方法正常执行")
void test_handleIccid_normal(){
try {
Method method = iccidPosListener.getClass().getDeclaredMethod("handleIccid", ConsumerRecord.class);
ConsumerRecord<String, String> record = new ConsumerRecord("topic", 1, 2, "key", "{'deviceCode': 'deviceCode2', 'iccid': 'iccid2'}");
Mockito.doNothing().when(asyncIccidPosDeal).handlePosRecord(Mockito.any(RobotIccidDto.class), Mockito.any(IccidUpdateResult.class));
method.setAccessible(true);
method.invoke(iccidPosListener, record);
Mockito.verify(asyncIccidPosDeal).handlePosRecord(Mockito.any(RobotIccidDto.class), Mockito.any(IccidUpdateResult.class));
} catch (Exception e) {
e.printStackTrace();
assertTrue(false);
}
}
-
在类上加上注解:
@TestMethodOrder (MethodOrderer.Alphanumeric.class ) //字母数字自然顺序
然后单元测试方法命名如test1、test2.。。。
在运行该类单元测试时就会按方法命名顺序去执行各个方法。
当然还有其他方法指定顺序。
-
这个是单元测试的核心知识点。
-
repository = Mockito.mock(DataServiceRepository.class);
或者直接用@InjectMocks 与 @Mock
-
-
1.方法有返回值:Mockito.when(repository.countCallLog(req.getParams())).thenReturn(12L);
还可以这样写:
Mockito.doReturn(future).when(kafkaTemplate).send(any(), any());
2.方法无返回值:
Mockito.doNothing().when(bIPMsgService.sendBip(Mockito.any()));
但在junit5这样写貌似会报错,改成如下:
Mockito.doNothing().when(repository).exportCallLog(req, Mockito.any(Consumer.class));
Ps:对象本身就是mock出来的,那这个doNothing语句可以不用写,因为mock出来的对象默认就是跳过其所有方法的。
3.如果无返回值的方法模拟抛出异常也要修改写法:
Mockito.when(fileConfigurationRepository.delete(GlobalConfiguration.CUSTOM_LOGO_ID)).thenThrow(new Exception());
改为:
Mockito.doThrow(new RuntimeException()).when(fileConfigurationRepository).delete(GlobalConfiguration.CUSTOM_LOGO_ID);
-
Jdk8以前可以用PowerMockito来mock静态方法,但jdk8及以后就不能用了。
要用Mockito 3.4以上的版本就可直接支持。
所以需要额外引入:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.1.0</version>
<scope>test</scope>
</dependency>
其实如果springboot本身的版本在2.5以上的话mockito也在3.4以上了(会使得spring-boot-starter-test也在2.5以上,可以直接单独指定spring-boot-starter-test版本为2.5.3以上),这样就不用引入mockito-core,但mockito-inline还是要引入的。
1.对于无返回值void的静态方法:
try(MockedStatic<ExcelExport> mock = Mockito.mockStatic(ExcelExport.class)){
dataServiceImpl.exportCallLog(req, response);//里面会调用ExcelExport.export静态方法
}
注意要test的方法要写在try作用域里面。
因为这里已经mock了静态方法所在的类,默认就会跳过其下所有静态方法,所有不用再mock静态方法。
Ps: 目前问题,对于返回void的静态方法如何模拟抛出异常???
2.对于有返回值但入参为空的静态方法:
try(MockedStatic<ExcelExport> mock = Mockito.mockStatic(ExcelExport.class)){
mock.when(ExcelExport::export).thenReturn(1);
dataServiceImpl.exportCallLog(req, response);
}
3.对于有返回值且有入参为空的静态方法:
try(MockedStatic<ExcelExport> mock = Mockito.mockStatic(ExcelExport.class)){
mock.when(() -> ExcelExport.export(PoiExcelInfo.builder().build(), response)).thenReturn(1);
dataServiceImpl.exportCallLog(req, response);
}
这里面也有个要点:静态方法export的入参是一个PoiExcelInfo对象,我写成Mockito.any(PoiExcelInfo.class)是会报错的,随意new一个对象反而不会报错,也能正常跳过该静态方法。
-
待补充
-
对于无返回值的:
Mockito.doThrow(new RuntimeException("抛出异常")).when(asyncIccidPosDeal).handlePosRecord(Mockito.any(RobotIccidDto.class), Mockito.any(IccidUpdateResult.class));
貌似不能直接抛new Exception()的,需要指定一种具体的异常类,如RuntimeException。
对于有返回值的:
Mockito.when(robotLocationService.queryDeviceLocation(Mockito.anyString())).thenThrow(new RuntimeException("异常哈哈"));
ps:抛的指定异常必须是这个方法真的有throws这种异常,否则只能模拟抛出运行时异常,要不就会报错:
Checked exception is invalid for this method!
-
·任意字符串:Mockito.anyString()
·任意指定对象:Mockito.any(UserDTO.class)
·如果入参是一个函数式接口,比如Consumer,好像不能用Mockito.any(Consumer.class),要试下用doc -> {} 是否可以,比如:
Mockito.doNothing().when(repository).exportCallLog(req, doc -> {});这个不确定,待定。
当然还有很多Mockito.anyInt()之类的,但非必要统一用Mockito.any()就好。
-
如果是启动spring的单元测试,但又想跳过redis的相关操作,可以这样配置:
在test下面创建类:
@Configuration
public class JunitTestConfig{
@Bean
public RedisTemplate redisTemplate() {
RedisTemplate redisTemplate = Mockito.mock(RedisTemplate.class);
ValueOperations valueOperations = Mockito.mock(ValueOperations.class);
SetOperations setOperations = Mockito.mock(SetOperations.class);
HashOperations hashOperations = redisTemplate.opsForHash();
ListOperations listOperations = redisTemplate.opsForList();
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
when(redisTemplate.opsForSet()).thenReturn(setOperations);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(redisTemplate.opsForList()).thenReturn(listOperations);
when(redisTemplate.opsForZSet()).thenReturn(zSetOperations);
RedisOperations redisOperations = Mockito.mock(RedisOperations.class);
RedisConnection redisConnection = Mockito.mock(RedisConnection.class);
RedisConnectionFactory redisConnectionFactory = Mockito.mock(RedisConnectionFactory.class);
when(redisTemplate.getConnectionFactory()).thenReturn(redisConnectionFactory);
when(valueOperations.getOperations()).thenReturn(redisOperations);
when(redisTemplate.getConnectionFactory().getConnection()).thenReturn(redisConnection);
return redisTemplate;
}
}
-
单元测试的规范之一就是必须要写断言,而且不能写成assertTrue(true);这样绝对正确的断言。针对各种情况的断言这里一一介绍。
Assert是junit4的,而Assertions是junit5(jupiter5)的。
-
就断言方法返回值等于预期值就好,如:
assertEquals(rest.getTotal(), 12L);
assertNull(rest.getData());
Assertions.assertTrue(robots1.size() > 0);
-
断言某对象有执行某方法:
对于返回void函数,测试时可以这样断言:(这个例子不对,verify要求robotExceptionReq对象也要是mock对象才行,所以找个mock对象就行啦!)
RobotExceptionReq robotExceptionReq = RobotExceptionReq.builder().build();
exceptionMessageHelper.handleMessage(robotExceptionReq);
Mockito.verify(robotExceptionReq).setCategory(Mockito.anyString());
证明robotExceptionReq执行了setCategory方法。
还可以写成断言某对象调用了某方法几次:
Mockito.verify(studentRepository, Mockito.times(2)).save(studentEntity);
-
For1:
Assertions.assertThrows(ServiceException.class,() -> controller.getExceptionCodeCount(req));
For2:
MyException thrown =
assertThrows(MyException.class,
() -> myObject.doThing(),
"Expected doThing() to throw, but it didn't");
assertTrue(thrown.getMessage().contains("Stuff"));
-
-
对着类Ctrl + Shift + T
或者:
在需要测试的类或接口(推荐基于接口创建)名称上使用Alt+Enter,然后选择创建测试。
-
Idea: Ctrl+Alt+F6 查看测试覆盖率
或者直接在类上面运行覆盖率:
执行后: