JUnit 5 单元测试实践
Mock
使用 MockitoExtension
,即可以使用 @Mock
注解。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class MyTest {
@Mock
private TaggingService taggingService;
@Test
public void test() {
when(taggingService.tagging(eq("A"), any())).thenReturn(2);
Assertions.assertEquals(taggingService.tagging("A"), 2);
}
}
Spring
使用 SpringExtension
,配合@MockBean
,可以在需要注入的地方使用 mock bean。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { NodeMapperImpl.class })
public class UnitSerializeTest {
@Autowired
NodeMapper nodeMapper;
@MockBean
TaggingService taggingService;
@Test
void test() {
when(taggingService.tagging(any())).thenReturn(2);
NodeDo nodeDo = new NodeDo();
Node node = nodeMapper.toEntity(nodeDo);
}
Embedded Redis
如果依赖了中间件或数据库,做单元测试时,有两种思路:
- embedded service (基于内存)
- TestsContainers(基于容器)
这里以 redis 为例,先探讨 embedded 的方式。需要添加如下测试依赖。
it.ozimov
embedded-redis
0.7.2
test
然后定义一个 RedisServer
,并用TestConfiguration
注解,仅作用于测试。
@TestConfiguration
public class TestRedisConfig {
private final RedisServer redisServer = new RedisServer(6379);
@PostConstruct
public void postConstruct() {
redisServer.start();
}
@PreDestroy
public void preDestroy() {
redisServer.stop();
}
}
单元测试如下,classes 注意要添加上述类 TestRedisConfig
。
@Import(RedisConfig.class)
@SpringBootTest(classes = {
ProductFieldServiceImpl.class,
ProductFieldConverterImpl.class,
LettuceConnectionFactory.class,
TestRedisConfig.class
})
@ExtendWith(SpringExtension.class)
public class ProductFieldTest {
@Autowired
ProductFieldService productFieldService;
@Test
void getValue() {
String value = productFieldService.getFormattedValue(ProductFieldValueQuery.of("WWW3037", "XZ_MF10"));
System.out.println(value);
}
}
Feign
对Feign接口进行单元测试时,需要 import 两个 configuration,因此可以抽一个基类出来,如下所示:
@ExtendWith(SpringExtension.class)
@Import({FeignAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class})
public class BaseFeignTest {
}
然后继承这个基类,并 enable 要测试的 client 即可。
@EnableFeignClients(clients = {ExampleFeign.class})
public class ProductFieldTest extends BaseFeignTest {
@Autowired
ExampleFeign feign;
@Test
void testFeign() {
TagValuesDto result = feign.getProductFieldValue("WWW3037", "XZ_MF10").getData();
System.out.println(result);
}
}
DevOps
有了单元测试之后,我们希望每次编译部署都执行一次。此时只需将 maven -DskipTest=true 去掉,则会在每个发布的生命周期内执行单元测试。
一旦有测试用例不通过,就会阻断在自动化编译阶段。
对于屎山代码而言,可能会有些乱七八糟的test。此时如果我们只希望执行部分单元测试,要怎么做呢?
这个时候就需要在 pom.xml
的build
里面添加 surefire plugin,然后补充 excludes,即可跳过不希望执行的单元测试。示例如下。
org.apache.maven.plugins
maven-surefire-plugin
2.22.2
com/cmb/lr20/zxb/dialog/infrastructure/**/*.java
com/cmb/lr20/zxb/dialog/infrastructure/*.java
com/cmb/lr20/zxb/dialog/application/*.java
com/cmb/lr20/zxb/dialog/external/**/*.java
com/cmb/lr20/zxb/dialog/utils/*.java
com/cmb/lr20/zxb/dialog/*ApplicationTest.java