概述
Mockito是根据MIT许可证发布的Java开源测试框架。该框架允许在自动化单元测试中创建测试双重对象(模拟对象),以达到测试驱动开发(TDD)或行为驱动开发(BDD)的目的,框架的名称和徽标是mojitos(一种饮料)的一种玩法。
搭建mock测试环境
三种方式:(MockitoAnnotations、RunWith、Rule)
- 使用MockitoAnnotations
public class MockByAnnotationTest {
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private AccountDao accountDao;
@Test
public void testMock() {
Account account = accountDao.findAccount("x", "123");
System.out.println(account);
}
}
- 使用RunWith
import static org.mockito.Mockito.mock;
@RunWith(MockitoJUnitRunner.class)
public class MockByRunnerTest {
@Test
public void testMock() {
AccountDao accountDao = mock(AccountDao.class, Mockito.RETURNS_SMART_NULLS);
Account account = accountDao.findAccount("x", "123");
System.out.println(account);
}
}
- 使用Rule
public class MockByRuleTest {
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testMock() {
AccountDao accountDao = Mockito.mock(AccountDao.class);
Account account = accountDao.findAccount("x", "123");
System.out.println(account);
}
}
SpringBoot测试
//@Transactional
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
public class ArticleRestControllerTest {
@Resource
private MockMvc mockMvc;
// 无法注入成功
@Resource
private HelloController helloController;
@Resource
public ArticleRestService articleRestService;
// @Before
// public void init() {
// mockMvc = MockMvcBuilders.standaloneSetup(new ArticleRestController()).build();
// }
@Test
public void saveArticle() throws Exception {
String article = "{\n" +
" \"id\": 1,\n" +
" \"author\": \"zimug\",\n" +
" \"title\": \"测试spring boot\",\n" +
" \"content\": \"c\",\n" +
" \"createTime\": \"2017-07-16 05:23:34\",\n" +
" \"reader\":[{\"name\":\"Pioneer\",\"age\":18},{\"name\":\"kobe\",\"age\":37}]\n" +
"}";
MvcResult result = mockMvc.perform(
MockMvcRequestBuilders.request(HttpMethod.POST, "/rest/article")
.contentType("application/json").content(article))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.data.author").value("zimug"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.reader[0].age").value(18))
.andDo(print())
.andReturn();
log.info(result.getResponse().getContentAsString(StandardCharsets.UTF_8));
}
}
@SpringBootTest
//@Transactional
@Slf4j
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@WebMvcTest(value={HelloController.class, ArticleRestController.class})
public class ArticleRestControllerTest3 {
@Resource
private MockMvc mockMvc;
@MockBean
ArticleRestService articleRestService;
// 无法注入成功
@Resource
private HelloController helloController;
// @Before
// public void init() {
// mockMvc = MockMvcBuilders.standaloneSetup(new ArticleRestController()).build();
// }
@Test
public void saveArticle() throws Exception {
String article = "{\n" +
" \"id\": 1,\n" +
" \"author\": \"Pioneer\",\n" +
" \"title\": \"测试spring boot\",\n" +
" \"content\": \"c\",\n" +
" \"createTime\": \"2017-07-16 05:23:34\",\n" +
" \"reader\":[{\"name\":\"Pioneer\",\"age\":18},{\"name\":\"kobe\",\"age\":37}]\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
Article articleObj = objectMapper.readValue(article, Article.class);
Mockito.when(articleRestService.saveArticle(articleObj)).thenReturn("OK");
// Mockito.doReturn("OK").when(articleRestService).saveArticle(articleObj);
Assert.assertThat(articleRestService.saveArticle(articleObj), equalTo("OK"));
MvcResult result = mockMvc.perform(
MockMvcRequestBuilders.request(HttpMethod.POST, "/rest/article")
.contentType("application/json").content(article))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.data.author").value("Pioneer"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.reader[0].age").value(18))
.andDo(print())
.andReturn();
log.info(result.getResponse().getContentAsString(StandardCharsets.UTF_8));
}
}
区别
- @Mock与@Spy的区别:
- @Mock默认不执行任何方法,有返回值得方法就默认返回null。
- @Spy在未打桩的情况下会默认调用真实方法。
- @Mock与@MockBean的区别(@Spy与@SpyBean是同样的意思):
-
@Mock的作用类似于Mockito.mock(xxx.class),只是要在类上加上@RunWith(MockitoJUnitRunner.class)
-
@MockBean主要是在SpringBoot的测试类中将Mock的对象添加到Spring的上下文中,完成对象的依赖注入。
使用场景:为了SpringBoot的Controller层,测试类上有注解@RunWith(SpringRunner.class),但为了避免注入所有的Bean,缩短测试时间,没有使用@SpringBootTest注解,而是使用@WebMvcTest。这种情况下,Service层的对象不能通过@Resource的方式注入,只能通过@MockBean将使用的Service类添加到上下文中。那为什么不能用@Mock呢?那是因为注解上面是@RunWith(SpringRunner.class)而不是@RunWith(MockitoJUnitRunner.class)。
- Mockito.when(ccc.xxx).thenReturn(…)和doReturn(…)when(ccc).xxx的区别
- 如果是用@Mock的对象,使用这两者没有区别。
- 如果是用@Spy的对象,doReturn会强行返回doReturn中的数据。而thenReturn这种模式,会调用spy对象的原有方法。当然,如果spy对象本身没有这个方法,那就会返回thenReturn中的值。
- @SpringBootTest、@AutoConfigureMockMvc、@RunWith(SpringRunner.class)和@WebMvcTest的使用场景
-
只要是基于SpringFramwork测试都需要加上**@RunWith(SpringRunner.class)**。
-
@SpringBootTest、@AutoConfigureMockMvc,例如:对controller层进行测试,xxxController这个类里面通过@Resource注入了xxxService。在单元测试时,需要用@SpringBootTest使得所有的bean都注入到上下文中,然后通过@AutoConfigureMockMvc将所有controller层构建完成,其过程就包括从上下文中取得xxxService以装载到xxxController中。
-
@WebMvcTest是为了加快初始化时间,比如此时只是针对FirstController进行测试,就没必要将SecondController,ThirdController都构建完成。所以此时可通过WebMvcTest来指定具体的一个或者几个进行Contrller进行初始化,取代@SpringBootTest、@AutoConfigureMockMvc。但是在接口测试中,推荐使用@SpringBootTest、@AutoConfigureMockMvc、@RunWith(SpringRunner.class)这三个的组合。
-
如果不依赖上下文,单元测试类上什么都可以不加,直接用MockMvc模拟HTTP请求进行测试。前提是,xxxController类里面不要嵌套注入了xxxService,这样是没办法成功构建xxxController的。即使现在用@InjectMocks来模拟一个xxxController,它也不是MockMvc针对的上下文中的xxxController。
- @InjectMocks
- 针对直接调用某个对象的方法时有效,例如xxxController这个类里面通过@Resource注入了xxxService,单元测试时@Mock了xxxService,然后@InjectMock了xxxController,此时测试代码只能用xxxController.xxxMethod(xxx)进行测试。容易混淆:@InjectMocks并不能在做SpringBoot的单元测试时将xxxController注入到上下文中,注入还是通过@SpringBootTest、@AutoConfigureMockMvc、@SpringBootTest、@WebMvcTest等管理的。