@MockBean
@MockBean 注解可用于 Spring容器中,可以用于类级别的注解或者配置类(@Configuration classes)。可以用于Spring 中的 测试类。
Mocks 可以通过 type 或者 bean name 被注册。使用过程中中定义的任何现有的相同类型的单个Bean将被该 Mock 替换。 如果 @MockBean 注解的类不存在,它回自动定义并且添加。对于不是bean的依赖,@MockBean会将它模拟成bean与存在的依赖一起添加到上下问中。
总结
@MockBean 用于加到ApplicationContext中,会替换里面同类型的bean,没有就new一个。而@Mock就是一般使用。 另外@InjectMocks 是一个动词,给它注入依赖的mock。不过在spring中,spring会处理依赖,所以应该用不上。 @Inject就是一般的注入正常的对象
@Spy和 @SpyBean的区别, @Mock 和 @MockBean的区别
1、spy和mock生成的对象不受spring管理
2、spy调用真实方法时,其它bean是无法注入的,要使用注入,要使用SpyBean
3、SpyBean和MockBean生成的对象受spring管理,相当于自动替换对应类型bean的注入,比如@Autowired等注
当你要测试的时候,发现要测试的组件中有第三方的类,
可以用spy处理
控制器的mock测试
package xyz.staffjoy.company.controller.ut;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import xyz.staffjoy.common.api.ResultCode;
import xyz.staffjoy.common.auth.AuthConstant;
import xyz.staffjoy.company.dto.CompanyDto;
import xyz.staffjoy.company.dto.CompanyList;
import xyz.staffjoy.company.dto.ListCompanyResponse;
import xyz.staffjoy.company.dto.GenericCompanyResponse;
import xyz.staffjoy.company.service.CompanyService;
import static org.assertj.core.api.Java6Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Arrays;
import java.util.TimeZone;
import java.util.UUID;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class CompanyControllerUnitTest {
@Autowired
MockMvc mockMvc;
@MockBean
CompanyService companyService;
@Autowired
ObjectMapper objectMapper;
CompanyDto newCompanyDto;
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Before
public void setUp() {
newCompanyDto = CompanyDto.builder()
.archived(false)
.name("test-company")
.defaultDayWeekStarts("Monday")
.defaultTimezone(TimeZone.getDefault().getID())
.build();
}
@Test()
public void testCreateCompanyAuthorizeMissing() throws Exception {
MvcResult mvcResult = mockMvc.perform(post("/v1/company/create")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(newCompanyDto)))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isFalse();
assertThat(genericCompanyResponse.getCode()).isEqualTo(ResultCode.UN_AUTHORIZED);
}
@Test
public void testCreateCompanyPermissionDeniedException() throws Exception {
MvcResult mvcResult = mockMvc.perform(post("/v1/company/create")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_COMPANY_SERVICE)
.content(objectMapper.writeValueAsBytes(newCompanyDto)))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isFalse();
assertThat(genericCompanyResponse.getCode()).isEqualTo(ResultCode.UN_AUTHORIZED);
}
@Test
public void testCreateCompanyInvalidDayWeekStarts() throws Exception {
newCompanyDto.setDefaultDayWeekStarts("mondayday");
MvcResult mvcResult = mockMvc.perform(post("/v1/company/create")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER)
.content(objectMapper.writeValueAsBytes(newCompanyDto)))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isFalse();
assertThat(genericCompanyResponse.getCode()).isEqualTo(ResultCode.PARAM_VALID_ERROR);
assertThat(genericCompanyResponse.getMessage()).startsWith("defaultDayWeekStarts").endsWith("Unknown day of week");
}
@Test
public void testCreateCompanyInvalidTimezone() throws Exception {
newCompanyDto.setDefaultTimezone("nonexisting-tinezone");
MvcResult mvcResult = mockMvc.perform(post("/v1/company/create")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER)
.content(objectMapper.writeValueAsBytes(newCompanyDto)))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isFalse();
assertThat(genericCompanyResponse.getCode()).isEqualTo(ResultCode.PARAM_VALID_ERROR);
assertThat(genericCompanyResponse.getMessage()).startsWith("defaultTimezone").endsWith("Invalid timezone");
}
@Test
public void testCreateCompanyEmptyName() throws Exception {
newCompanyDto.setName(null);
MvcResult mvcResult = mockMvc.perform(post("/v1/company/create")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER)
.content(objectMapper.writeValueAsBytes(newCompanyDto)))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isFalse();
assertThat(genericCompanyResponse.getCode()).isEqualTo(ResultCode.PARAM_VALID_ERROR);
assertThat(genericCompanyResponse.getMessage()).startsWith("name").endsWith("must not be blank");
}
@Test
public void testCreateCompanySuccessfully() throws Exception {
when(companyService.createCompany(any(CompanyDto.class))).thenReturn(newCompanyDto);
MvcResult mvcResult = mockMvc.perform(post("/v1/company/create")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER)
.content(objectMapper.writeValueAsString(newCompanyDto)))
.andExpect(status().isOk())
.andReturn();
verify(companyService, times(1)).createCompany(eq(newCompanyDto));
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isTrue();
assertThat(genericCompanyResponse.getCompany()).isEqualTo(newCompanyDto);
}
@Test
public void testListCompanySuccessfully() throws Exception {
CompanyList expectedCompanyList = new CompanyList(Arrays.asList(newCompanyDto), 1, 1);
when(companyService.listCompanies(anyInt(), anyInt())).thenReturn(expectedCompanyList);
MvcResult mvcResult = mockMvc.perform(get("/v1/company/list?limit=1&offset=1")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER))
.andExpect(status().isOk())
.andReturn();
verify(companyService, times(1)).listCompanies(eq(1), eq(1));
ListCompanyResponse listCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), ListCompanyResponse.class);
assertThat(listCompanyResponse.isSuccess()).isTrue();
assertThat(listCompanyResponse.getCompanyList()).isEqualTo(expectedCompanyList);
}
@Test
public void testListCompanyMissingParam() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/v1/company/list")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER))
.andExpect(status().isOk())
.andReturn();
ListCompanyResponse listCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), ListCompanyResponse.class);
assertThat(listCompanyResponse.isSuccess()).isFalse();
assertThat(listCompanyResponse.getCode()).isEqualTo(ResultCode.PARAM_MISS);
assertThat(listCompanyResponse.getMessage()).startsWith("Missing Request Parameter:").endsWith("offset");
}
@Test
public void testGetCompanySuccessfully() throws Exception {
String id = UUID.randomUUID().toString();
newCompanyDto.setId(id);
CompanyDto expectedCompanyDto = newCompanyDto;
when(companyService.getCompany(id)).thenReturn(expectedCompanyDto);
MvcResult mvcResult = mockMvc.perform(get("/v1/company/get?company_id=" + id)
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_BOT_SERVICE))
.andExpect(status().isOk())
.andReturn();
verify(companyService, times(1)).getCompany(eq(id));
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isTrue();
assertThat(genericCompanyResponse.getCompany()).isEqualTo(expectedCompanyDto);
}
@Test
public void testGetCompanyMissingPathVariable() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/v1/company/get")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isFalse();
assertThat(genericCompanyResponse.getCode()).isEqualTo(ResultCode.PARAM_MISS);
assertThat(genericCompanyResponse.getMessage()).startsWith("Missing Request Parameter:").endsWith("company_id");
}
@Test
public void testUpdateCompanySuccessfully() throws Exception {
String id = UUID.randomUUID().toString();
newCompanyDto.setId(id);
CompanyDto companyDtoToUpdate = newCompanyDto;
when(companyService.updateCompany(eq(companyDtoToUpdate))).thenReturn(companyDtoToUpdate);
MvcResult mvcResult = mockMvc.perform(put("/v1/company/update")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER)
.content(objectMapper.writeValueAsString(companyDtoToUpdate)))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isTrue();
assertThat(genericCompanyResponse.getCompany()).isEqualTo(companyDtoToUpdate);
}
@Test
public void testUpdateCompanyMissingId() throws Exception {
CompanyDto companyDtoToUpdate = newCompanyDto; // no id
when(companyService.updateCompany(eq(companyDtoToUpdate))).thenReturn(companyDtoToUpdate);
MvcResult mvcResult = mockMvc.perform(put("/v1/company/update")
.contentType(MediaType.APPLICATION_JSON)
.header(AuthConstant.AUTHORIZATION_HEADER, AuthConstant.AUTHORIZATION_SUPPORT_USER)
.content(objectMapper.writeValueAsString(companyDtoToUpdate)))
.andExpect(status().isOk())
.andReturn();
GenericCompanyResponse genericCompanyResponse = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), GenericCompanyResponse.class);
assertThat(genericCompanyResponse.isSuccess()).isFalse();
assertThat(genericCompanyResponse.getCode()).isEqualTo(ResultCode.PARAM_VALID_ERROR);
assertThat(genericCompanyResponse.getMessage()).startsWith("id:must not be blank");
}
}
使用webEnvironment = SpringBootTest.WebEnvironment.NONE
@EnableWebMvc注解与SpringBootTest.WebEnvironment.NONE冲突导致,注释掉@EnableWebMvc注解后即可
作用是去掉controller的加载,跑的快
@RunWith(SpringRunner.class)
可以加载数据库等东西
PS:
不管@EnableWebMvc注解在哪个Configuration类中,只要使用了@EnableWebMvc这个注解,就不能随意使用webEnvironment= SpringBootTest.WebEnvironment.NONE这个配置
dao mock 测试
package xyz.staffjoy.account.repo;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import xyz.staffjoy.account.model.Account;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringRunner;
import xyz.staffjoy.account.model.AccountSecret;
import java.time.LocalDateTime;
import java.time.ZoneId;
import static org.junit.Assert.*;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE)
@RunWith(SpringRunner.class)
public class AccountRepoTest {
@Autowired
private AccountRepo accountRepo;
@Autowired
private AccountSecretRepo accountSecretRepo;
private Account newAccount;
@Before
public void setUp() {
newAccount = Account.builder()
.name("testAccount")
.email("test@staffjoy.net")
.memberSince(LocalDateTime.of(2019, 1, 20, 12, 50).atZone(ZoneId.systemDefault()).toInstant())
.confirmedAndActive(false)
.photoUrl("https://staffjoy.xyz/photo/test.png")
.phoneNumber("18001801266")
.support(false)
.build();
// sanity check
accountRepo.deleteAll();
}
@Test//(expected = DuplicateKeyException.class)
public void createSampleAccount() {
accountRepo.save(newAccount);
assertTrue(accountRepo.existsById(newAccount.getId()));
}
@Test
public void getAccountById() {
accountRepo.save(newAccount);
assertEquals(1, accountRepo.count());
Account foundAccount = accountRepo.findById(newAccount.getId()).get();
assertEquals(newAccount, foundAccount);
}
@Test
public void findAccountByEmail() {
// not existing
Account foundAccount = accountRepo.findAccountByEmail("notexisting@staffjoy.net");
assertNull(foundAccount);
accountRepo.save(newAccount);
assertEquals(1, accountRepo.count());
foundAccount = accountRepo.findAccountByEmail(newAccount.getEmail());
assertNotNull(foundAccount);
assertEquals(newAccount.getId(), foundAccount.getId());
}
@Test
public void findAccountByPhoneNumber() {
// not existing
Account foundAccount = accountRepo.findAccountByPhoneNumber("18001800180");
assertNull(foundAccount);
// create new
accountRepo.save(newAccount);
assertEquals(1, accountRepo.count());
foundAccount = accountRepo.findAccountByPhoneNumber(newAccount.getPhoneNumber());
assertEquals(newAccount.getId(), foundAccount.getId());
}
@Test
public void listAccount() {
Pageable pageRequest = PageRequest.of(0, 2);
// test empty
Page<Account> accounts = accountRepo.findAll(pageRequest);
assertEquals(0, accounts.getTotalElements());
// create 1 new
accountRepo.save(newAccount);
assertEquals(1, accountRepo.count());
// create 2 more
newAccount.setId(null);
accountRepo.save(newAccount);
assertEquals(2, accountRepo.count());
newAccount.setId(null);
accountRepo.save(newAccount);
assertEquals(3, accountRepo.count());
accounts = accountRepo.findAll(pageRequest);
assertEquals(2, accounts.getNumberOfElements());
pageRequest = pageRequest.next();
accounts = accountRepo.findAll(pageRequest);
assertEquals(1, accounts.getNumberOfElements());
assertEquals(2, accounts.getTotalPages());
assertEquals(3, accounts.getTotalElements());
}
@Test
public void updateAccount() {
// create new
accountRepo.save(newAccount);
assertEquals(1, accountRepo.count());
Account toUpdateAccount = newAccount;
toUpdateAccount.setName("update");
toUpdateAccount.setEmail("update@staffjoy.xyz");
accountRepo.save(toUpdateAccount);
Account updatedAccount = accountRepo.save(toUpdateAccount);
Account foundAccount = accountRepo.findById(updatedAccount.getId()).get();
assertEquals(updatedAccount, foundAccount);
toUpdateAccount.setConfirmedAndActive(true);
toUpdateAccount.setSupport(true);
toUpdateAccount.setPhoneNumber("19001900190");
toUpdateAccount.setPhotoUrl("http://staffjoy.net/photo/update.png");
updatedAccount = accountRepo.save(toUpdateAccount);
foundAccount = accountRepo.findById(updatedAccount.getId()).get();
assertEquals(updatedAccount, foundAccount);
}
@Test
public void updateEmailAndActivateById() {
// create new
Account account = accountRepo.save(newAccount);
assertEquals(1, accountRepo.count());
assertFalse(account.isConfirmedAndActive());
String toUpdateEmail = "update@staffjoy.xyz";
int result = accountRepo.updateEmailAndActivateById(toUpdateEmail, newAccount.getId());
assertEquals(1, result);
Account updatedAccount = accountRepo.findAccountByEmail(toUpdateEmail);
assertEquals(toUpdateEmail, updatedAccount.getEmail());
assertTrue(updatedAccount.isConfirmedAndActive());
}
@Test
public void updatePasswordById() {
// create new
accountRepo.save(newAccount);
assertEquals(1, accountRepo.count());
String passwordHash = "testhash";
int result = accountSecretRepo.updatePasswordHashById(passwordHash, newAccount.getId());
assertEquals(1, result);
AccountSecret foundAccountSecret = accountSecretRepo.findAccountSecretByEmail(newAccount.getEmail());
assertNotNull(foundAccountSecret);
assertEquals(newAccount.getId(), foundAccountSecret.getId());
assertEquals(newAccount.isConfirmedAndActive(), foundAccountSecret.isConfirmedAndActive());
assertEquals(passwordHash, foundAccountSecret.getPasswordHash() );
}
@After
public void destroy() {
accountRepo.deleteAll();
}
}
why?
dependency
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
模拟数据–测试桩 stub
连续调用
MOCK-MVC
有几篇文章需要深入理解:
mock-mvc
https://blog.csdn.net/blueheart20/article/details/45174399
why
对模块进行集成测试时,希望能够通过输入URL对Controller进行测试,如果通过启动服务器,建立http client进行测试,这样会使得测试变得很麻烦,比如,启动速度慢,测试验证不方便,依赖网络环境等,所以为了可以对Controller进行测试,我们引入了MockMVC。
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。
Post
one 打平参数
接口
@RestController
@RequestMapping(value = "junit")
public class JunitIdTitleController {
// @PostMapping("aa")
@RequestMapping(method =RequestMethod.POST,value = "aa")
public void bb( String title,String nihao){
System.out.println("");
}
mock
@RunWith(SpringRunner.class)
@SpringBootTest
public class JunitIdTitleControllerTest {
//mock测试器
private MockMvc mockMvc;
//容器上下文
@Autowired
private WebApplicationContext context;
@Before
public void step() {
//通过MockMvcBuilders.webAppContextSetup(this.context).build()
//初始化mock测试器
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
`` @Test
public void testSave() throws Exception {
// 创建传递的参数数据
MultiValueMap map = new LinkedMultiValueMap();
map.put("title", Arrays.asList( "我是Controller的测试数据wl"));
map.put("nihao",Arrays.asList("ddd222"));
// 接收发送请求后的返回结果
MvcResult result = mockMvc.perform(
// 设置post请求
MockMvcRequestBuilders.post("/junit/aa")
// 设置消息数据类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
// .params(map)
.param("title","222222")
.param("nihao","111111")
// 设置发送的数据
// .content(JSONObject.toJSONString(map))
)
// 开始模拟发送post请求
.andExpect(MockMvcResultMatchers.status().isOk())
// 设置返回类型为json
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
// 抽取返回结果
.andReturn();
System.out.println(result.getResponse().getContentAsString());
}
备注:上面的方式有别于实体封装类型—(@RequestBody IdTitle idTitle),这里只能使用params或者param传递参数,使用content方法传递参数会报空
结果:
Two封装数据 eg:-(@RequestBody IdTitle idTitle)
接口
@RequestMapping(value = "save", method = RequestMethod.POST)
int save(@RequestBody IdTitle idTitle) {
return junitIdTitleService.saveIdTile(idTitle);
}
测试
@Test
public void testSave() throws Exception {
// 创建传递的参数数据
/* MultiValueMap map = new LinkedMultiValueMap();
map.put("title", Arrays.asList( "我是Controller的测试数据wl"));
map.put("nihao",Arrays.asList("ddd222"));*/
Map<String, Object> ma = new HashMap<>();
ma.put("title", "wwwwww");
ma.put("id", 11111111);
// 接收发送请求后的返回结果
MvcResult result = mockMvc.perform(
// 设置post请求
MockMvcRequestBuilders.post("/junit/save")
// 设置消息数据类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
// .params(map)
/* .param("title","vvvvvvvvv")
.param("id","111111")*/
// 设置发送的数据
.content(JSONObject.toJSONString(ma))
)
// 开始模拟发送post请求
.andExpect(MockMvcResultMatchers.status().isOk())
// 设置返回类型为json
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
// 抽取返回结果
.andReturn();
System.out.println(result.getResponse().getContentAsString());
}
结果:
总结:
get请求非常简单,csdn到处都有demo,重点是要区分实体和打平传入参数的区别,这是这篇博客的核心。
创作不易,转载请您附上原址,谢谢