【资源】
官网:https://site.mockito.org/
【本文内容】
介绍了Mockito的重要注解@Mock
,并介绍了Mockito的一些重要方法,如:
-
Mockito.when()
/thenReturn()
Mockito.lenient()
Mockito.doNothing()
-
Mockito.doThrow()
/thenThrow()
-
Mockito.doAnswer()
/thenAnswer()
更多Mockito的方法,参考:https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
并且介绍了与JUnit5的集成:@ExtendWith(MockitoExtension.class)
。
以及与Spring Boot的集成,使用注解@MockBean
。
写了一个用Mockito mock的用来测试自定义Filter的单元测试。
最后,则介绍了BDD(行为驱动测试)风格的类BDDMockito
。
1. 为什么是Mockito
关于Java的Mock框架,市面上也有很多流行的框架,最流行的当属Mockito,而它还天然的支持Spring Boot,在Spring Boot的官方文档中有提到:
image.png列举了Mockito与其它框架在Hithub上的star数:
EasyMock与Mockito的对比文章:https://code.google.com/archive/p/mockito/wikis/MockitoVSEasyMock.wiki
Mockito官网的与EasyMock对比文章:https://github.com/mockito/mockito/wiki/Mockito-vs-EasyMock
而Powermock的侧重点并不是独立的mock框架,而是作为EasyMock、Mockito或是其它Mock框架的扩展,比如支持static, final, private的测试。
以下是EasyMock
的Contributors:
以下是Mockito
的Contributors:
总结就是Mockito的社区相对于其它Mock框架还是比较活跃的。
2. Mockito与JUnit5的集成
参考:https://www.baeldung.com/mockito-junit-5-extension
除了Mockito本身的jar以及JUnit5本身的jar外,还需要依赖:mockito-junit-jupiter
,简单起见,可直接引入Spring Boot的test,它已经帮我们解决了依赖问题,并且天然的支持Mockito和JUnit5(需要Spring Boot 2.4.0+,否则支持的是JUnit4):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
image.png
我们先创建一个UserService接口,以及它的实现UserServiceImpl,并且有一个方法,version(),返回1.0:
@AllArgsConstructor
public class UserServiceImpl implements UserService {
UserRepository userRepository;
public int count() {
return userRepository.count();
}
}
使用Mockito mock UserService中的userRepository:
- 首先要声明扩展器类:
MockitoExtension.class
,正是位于上述的mockito-junit-jupiter
包中。 - 其次,
@Mock
注解位于mockito-core
包中,是Mockito重要的注解之一,被它声明的类,则会自动创建Mock实例。可以通过when()/given()来模拟它的行为。 - 下述的测试方法中使用了
when
(userRepository的version方法被调用),则thenReturn
(返回预期值)来模拟userRepository的行为。
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
private UserService userService;
@BeforeEach
public void before() {
userService = new UserServiceImpl(userRepository);
}
@Test
public void countTest() {
Mockito.when(userRepository.count()).thenReturn(5);
Assertions.assertEquals(5, userService.count());
}
}
使用@Mock注解生成的userRepository:
image.png
3. Mockito的其它功能
3.1 使用Mockito.lenient()
来避免因为Mock方法没有被调用而抛出的UnnecessaryStubbingException
参考:https://www.baeldung.com/mockito-unnecessary-stubbing-exception
比如userRepository有两个方法:version()和count(),我们在测试userService.count()的时候,顺道也Mock了userRepository的version()方法:
@Test
public void countTest() {
Mockito.when(userRepository.count()).thenReturn(5);
Mockito.when(userRepository.version()).thenReturn(5);
Assertions.assertEquals(5, userService.count());
}
运行后发现报错了:
image.png
即如果Mock的方法没有被调用,那么Mockito会主动抛出错误:UnnecessaryStubbingException
,这时候可以用Mock.lenient(),来表明可以忽略Mock方法没有被调用:
Mockito.lenient().when(userRepository.version()).thenReturn(5);
3.2 使用Mockito.doNothing()
来Mock void 方法
参考:https://www.baeldung.com/mockito-void-methods
@Test
public void updateTest() {
Mockito.doNothing().when(userRepository).update(Mockito.any());
Assertions.assertTrue(userService.update(new UserEntity(1, "test")));
}
3.3 Mock抛错
UserRepository两个方法,一个是有返回值的,一个没有:
void validate(Integer userId);
boolean check(Integer userId);
UserService两个方法:
public boolean validate(Integer userId) {
userRepository.validate(userId);
return true;
}
public boolean check(Integer userId) {
return userRepository.check(userId);
}
写测试方法:
- 使用Mockito.doThrow来Mock void方法的抛错。
- 使用thenThrow来Mock有返回值方法的抛错。
@Test
public void validateTest() {
Mockito.doThrow(new IllegalArgumentException()).doNothing().when(userRepository).validate(Mockito.any());
Assertions.assertThrows(IllegalArgumentException.class, () -> userService.validate(1));
}
@Test
public void checkTest() {
Mockito.when(userRepository.check(Mockito.any())).thenThrow(IllegalArgumentException.class);
Assertions.assertThrows(IllegalArgumentException.class, () -> userService.check(1));
}
3.4 使用Mockito.doAnswer()
/ thenAnswer(Answer)
来Mock更为复杂的result
参考:
- https://blog.csdn.net/hbmovie/article/details/80940538
- https://blog.csdn.net/b1480521874/article/details/102638581
thenAnswer通过接收参数,实现一个通用的接口,而这个通用接口基本上可以返回任何想要的东西。相比于常用的thenReturn、thenThrow方法(只返回固定的值),Answer可以计算返回值。
我们在UserRepository中定义一个方法,这个方法的意思是接收两个user,然后将他们的id相加返回:
int addId(UserEntity user1, UserEntity user2);
测试,可以看到通过thenAnswer
可以拦截入参,然后进行具体的计算,从来达到更为复杂的Mock:
@Test
public void addIdTest() {
Mockito.when(userRepository.addId(Mockito.any(), Mockito.any())).thenAnswer(invocation -> {
UserEntity user1 = invocation.getArgument(0);
UserEntity user2 = invocation.getArgument(1);
return user1.getId() + user2.getId();
});
Assertions.assertEquals(5, userService.addId(new UserEntity(2), new UserEntity(3)));
}
对于void的方法,不能用thenAnswer来Mock,但可以使用Mockito.doAnswer(),如我们可以Mock Filter中的filterChain.doFilter方法:
Mockito.doAnswer((Answer) invocation -> {
Object arg0 = invocation.getArgument(0);
Object arg1 = invocation.getArgument(1);
System.out.println(MDC.get("test"));
// to more logic
return null;
}).when(filterChain).doFilter(Mockito.any(), Mockito.any());
4. Mockito与Spring Boot的集成
Spring Boot官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.mocking-beans
因为要和Spring Boot集成,那么首先项目是Spring Boot项目:
- 创建Spring Boot启动类,有
@SpringBootApplication
注解。 - 创建BookServiceImpl,有
@Service
注解。 - BookServiceImpl中有bookRepository,有
@Autowired
注解。
我们对BookServiceImpl进行测试,创建BookServiceTest
:
这里使用的@MockBean创建的bookRepository可以将这个成员变量以mock的方式成为Spring ApplicationContext中的bean。
@SpringBootTest
public class BookServiceTest {
@Autowired
private BookService bookService;
@MockBean
BookRepository bookRepository;
@Test
public void countTest() {
Mockito.when(bookRepository.count()).thenReturn(5);
Assertions.assertEquals(5, bookService.count());
}
}
可以看到通过@MockBean,可以直接将Mock的bookRepository注入到BookService中:
image.png
5. 实例,通过Mockito.doAnswer()
测试自定义的Filter
定义一个UserFilter
,在这个filter中,如果request header中有userId
,那么就用它,没有就自己定义一个叫testUser
。最终放入request attribute中:
public class UserFilter implements Filter {
public static final String USER_ID = "userId";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String userId = request.getHeader(USER_ID);
if (!StringUtils.hasText(userId)) {
userId = "testUser";
}
request.setAttribute(USER_ID, userId);
filterChain.doFilter(servletRequest, servletResponse);
}
}
以下是单元测试,除了要用到spring-test
中的MockHttpServletRequest
以及MockHttpServletResponse
,那么要怎么验证呢?我们可以用Mockito.doAnswer
来验证filterChain
的doFilter
方法:
@ExtendWith(MockitoExtension.class)
public class UserFilterTest {
@Mock
FilterChain filterChain;
@Test
public void userId_should_get_from_parameter() throws Exception {
UserFilter filter = new UserFilter();
MockHttpServletRequest req = new MockHttpServletRequest();
req.addHeader(UserFilter.USER_ID, "user1");
MockHttpServletResponse res = new MockHttpServletResponse();
Mockito.doAnswer(invocation -> {
HttpServletRequest request = invocation.getArgument(0);
Assertions.assertEquals("user1", request.getAttribute(UserFilter.USER_ID));
return null;
}).when(filterChain).doFilter(Mockito.any(), Mockito.any());
filter.init(new MockFilterConfig());
filter.doFilter(req, res, filterChain);
}
}
6. BDDMockito
参考:https://www.baeldung.com/bdd-mockito
BDDMockito官网文档:https://javadoc.io/static/org.mockito/mockito-core/4.8.0/org/mockito/BDDMockito.html
BDD的意思是Behavior-driven development
,即行为驱动开发(BDD中比较有名的自动化测试工具,Cucumber,具体可以查看文章: 【测试相关】Cucumber与Junit4集成示例,以及与Spring Boot+Junit5集成示例。
对于Mockito
来说,它提供的BDDMockito
类则是实现的BDD风格。
传统的Mockito
是when(...)/thenReturn(...),而BDDMockito
则是使用given(...)/willReturn(...)这样的方法来Mock对象。
具体来说,Mockito
的Mock如:
Mockito.when(userRepository.count()).thenReturn(5);
用BDDMockito
来Mock的话,则会是:
BDDMockito.given(userRepository.count()).willReturn(5);
又比如对于void的方法进行throw exception的Mock,用Mockito
来写的话会是:
Mockito.doThrow(new IllegalArgumentException()).doNothing().when(userRepository).validate(Mockito.any());
换成BDDMockito
则是:
BDDMockito.willThrow(new IllegalArgumentException()).willDoNothing().given(userRepository).validate(BDDMockito.any());
参考:
网友评论