前言
本文为 Spring Boot 单元测试实践系列文章第三篇。本文主要内容为多种 Spring 容器初始化方式、参数捕获以及 Answer 的基本介绍及用法。
前文:
Spring Boot 单元测试实践:单元测试的基本方式
Spring Boot 单元测试实践(二):常用断言,及业务方法编写单元测试示例
实践
Spring 容器初始化方式
方式一:@Import + @ExtendWith(@RunWith)
此方式为个人习惯使用,在前文也多次提及。
@Import({OtherService.class})
@ExtendWith(SpringExtension.class)
public class ServiceInit1Test {
@MockBean
private UserRepository userRepository;
@SpyBean
private DummyRepository dummyRepository;
@MockBean
private MockRepository mockRepository;
@Resource
private OtherService service;
@Test
public void init() {
Assertions.assertNotNull(userRepository);
Assertions.assertNotNull(dummyRepository);
Assertions.assertNotNull(mockRepository);
service.doSomething();
}
}
方式二:@InjectMocks
此方式不会启动 Spring 容器,更像是一种脱离了 Spring 的普通的单元测试,在其它需要进行依赖注入的框架也能使用。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
public class ServiceInit2Test {
@Mock
private UserRepository userRepository;
@Spy
private DummyRepository dummyRepository;
@Mock
private MockRepository mockRepository;
@InjectMocks
private OtherService service;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void init() {
Assertions.assertNotNull(userRepository);
Assertions.assertNotNull(dummyRepository);
Assertions.assertNotNull(mockRepository);
service.doSomething();
}
}
方式三:TestContextManager
在 Junit4 中想要执行参数化测试,又想要启动 Spring 容器则可以通过TestContextManager实现
@RunWith 注解只支持一种 Runner,而在 Junit4 参数化测试又得单独选择 Parameterized
在 Junit5 通过方法注解(@ParameterizedTest, @ValueSource)即可实现参数化测试
@RunWith(Parameterized.class)
@Import({URLRedirectSelector.class})
@ActiveProfiles("test")
public class MixURLRedirectTest extends BaseTest {
@Autowired
private URLRedirectSelector selector;
@Before
public void setUp() throws Exception {
TestContextManager testContextManager = new TestContextManager(getClass());
testContextManager.prepareTestInstance(this);
}
}
TestContextManager 和 TestContext 是 SpringTest 框架的核心,也是整个测试框架的入口
TestContextManager 会创建并管理 TestContext,而测试上下文中缓存了各种实例信息,同时可以获取到 Spring ApplicationContext
参数捕获
在业务方法中可能会调用到返回值为 void 的方法,而该方法的入参可能是经过一些逻辑处理得到的,且该逻辑无法单独作为一个方法处理(如果可以单独封装为方法,则只需要单独编写用例断言返回值即可),此时可以选择对该方法的核心参数进行断言。
ArgumentCaptor 参数捕获器,作为参数与 doNothing().when() 结合使用,不执行方法逻辑,仅用于捕获参数,并通过
getValue() or getAllValues() 返回单个参数或者多个参数(比如 for 循环后)
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
@Import(CaptureService.class)
@ExtendWith(SpringExtension.class)
public class CaptureServiceTest {
@Resource
private CaptureService service;
@Test
public void captor() {
CaptureService spy = spy(service);
ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
doNothing().when(spy).print(captor.capture());
spy.add(1,2);
Integer value = captor.getValue();
Assertions.assertThat(value).isEqualTo(3);
}
}
Answer
Answer 是一个配置 mock 结果的通用接口。
使用 Answer 可以做两件事,一是如 ArgumentCaptor 一样捕获参数,二是同于 thenReturn(),可以自定义返回值,通过不同的条件返回不同的值。如当 ArgumentCaptor 和 thenReturn() 都不满足时,可以考虑下使用 Answer。
其它的 stubebr 方法其底层实现其实都是使用 Answer,只是进一步封装了一些通用的方法便于使用。
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
@Import(AnswerService.class)
@ExtendWith(SpringExtension.class)
public class AnswerServiceTest {
@Resource
private AnswerService service;
@Test
public void answer() {
AnswerService spy = spy(service);
doAnswer(invocationOnMock ->{
Integer i = invocationOnMock.getArgument(0, Integer.class);
Assertions.assertThat(i).isEqualTo(3);
Integer result = (Integer) invocationOnMock.callRealMethod();
Assertions.assertThat(result).isEqualTo(4);
return -1;
}).when(spy).doSomething(anyInt());
spy.add(1,2);
}
}
ArgumentCaptor 和 Answer 的用例中使用 spy 的原因是 when、thenReturn、doAnswer 等方法的参数需要为 stubber 对象才能生效,即 mock 或者 spy 对象