1.常用注解
@Before:初始化方法,在任何一个测试方法执行之前,必须执行的代码。
@After: 释放资源,在任何一个测试方法执行之后,需要进行的收尾工作。
@Ignore:忽略的测试方法,标注的含义就是“某些方法尚未完成,咱不参与此次测试”;这样的话测试结果就会提示你有几个测试被忽略,而不是失败
@BeforeClass:针对所有测试,也就是整个测试类中,在所有测试方法执行前,都会先执行由它注解的方法,而且只执行一次。当然,需要注意的是,修饰符必须是public static void xxxx ;此 Annotation 是 JUnit 4 新增的功能。
@AfterClass:针对所有测试,也就是整个测试类中,在所有测试方法都执行完之后,才会执行由它注解的方法,而且只执行一次。需要注意的是,修饰符也必须是 public static void xxxx ; JUnit 4 新增的功能,与 @BeforeClass 是一对。
执行顺序:
2.示例
2.1 Controller层
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ArticleControllerTest extends AbstractTransactionalJUnit4SpringContextTests {
private MockMvc mockMvc;
@Value("${api.secret-token}")
private String secretToken;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).alwaysDo(print()).build();
}
// 1.模拟上传Excel
@Test
@Sql("/db/test/test_import_loan_data.sql")
public void testImportLoanData() throws Exception {
URL resource = this.getClass().getResource("/file/upload_excel.xls");
Path path = Paths.get(resource.toURI());
byte[] data = Files.readAllBytes(path);
MockMultipartFile file = new MockMultipartFile("excel", "upload_excel.xls",
MediaType.IMAGE_JPEG_VALUE, data);
ResultActions perform = mockMvc.perform(
MockMvcRequestBuilders.fileUpload("/admin/import_loan").file(file)
);
perform.andExpect(status().isOk());
perform.andExpect(jsonPath("status").value("200"));
}
/**
* 2. 测试翻页
* @throws Exception 异常
*/
@Test
@Sql(scripts = {"/db/test/admin_init.sql", "/db/test/exchange_rest_controller_test.sql", "/db/180411_dictionary_init.sql"})
public void testGetExchangeList1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/admin/exchange/get_exchange_list") // 构建请求
.param("page_size", "10") // 构建传参
.param("page_num", "1")
.param("status", "2")
.accept(MediaType.APPLICATION_JSON_UTF8)) // 设置请求类型
.andExpect(status().isOk()) // 期望借口调用状态断言
.andExpect(jsonPath("status").value(200)) // 期望借口返回值断言
.andExpect(jsonPath("data.total_record").value(261))
.andExpect(jsonPath("data.data").isNotEmpty())
.andReturn();
}
}
2.2 Service层
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.test.annotation.Commit;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertThat;
@RunWith(MockitoJUnitRunner.class)
public class IfcertAccessCoreServiceImplTest {
@Mock
private AccountCheckConfig config;
@Mock
private IfcertAccessDataService accessDataService;
@Mock
private IfcertAccessClientService accessClientService;
@Mock
private QueryTaskService queryTaskService;
@Mock
private PushTaskService pushTaskService;
@Mock
private IfcertDailyCheckHistoryMapper dailyCheckHistoryMapper;
@Mock
private IfcertDailyFailRepushHistoryMapper failRepushHistoryMapper;
@Mock
private MailService mailService;
private IfcertAccessCoreServiceImpl service;
// 此处必须要求service实现类要有构造方法
@Before
public void setup() {
service = new IfcertAccessCoreServiceImpl(config, accessDataService, accessClientService, queryTaskService,
pushTaskService, dailyCheckHistoryMapper, failRepushHistoryMapper, mailService);
}
/**
* 对账接口1:对账入库流程测试
*/
@Test
public void getBatchMessage() {
String[] params = new String[]{"123", "456"};
AccountCheckToDbRequest request = new AccountCheckToDbRequest();
request.setApiKey("apikey");
request.setEndPoint("endpoint");
request.setSourceCode("sourcecode");
request.setVersion("version");
request.setDataType("0");
request.setInfType("81");
request.setBatchNum("323223");
when(accessDataService.fillCheckRequestData(AccountCheckTypeEnum.DATA_TO_DB.getCode(), params))
.thenReturn(request);
BatchMessageResponseWrapper response = new BatchMessageResponseWrapper();
List<BatchMessageResponse> result = Lists.newArrayList();
BatchMessageResponse batchResponse = new BatchMessageResponse();
batchResponse.setBatchNum("23232332");
batchResponse.setDataType("0");
batchResponse.setErrorMsg("errormsg");
result.add(batchResponse);
response.setResult(result);
response.setCode("0000");
response.setMessage("查询成功");
when(accessClientService.getBatchMessage(request)).thenReturn(response);
service.getBatchMessage("123", "456");
verify(accessDataService, times(1))
.fillCheckRequestData(AccountCheckTypeEnum.DATA_TO_DB.getCode(), params);
verify(accessClientService, times(1)).getBatchMessage(request);
}
// 解决方法反复时void的处理
@Test
public void updateReading() throws Exception {
doNothing().when(taskModificationService).changeReadingStatus(anyList(),anyByte());
}
}
/**
* @author lei.liu
* @version 1.0.0
* @date 2017年12月29日 下午3:45:23
*/
@Service
public class IfcertAccessCoreServiceImpl implements IfcertAccessCoreService {
private static final Logger LOGGER = LoggerFactory.getLogger(IfcertAccessCoreServiceImpl.class);
/**
* 对账接口2接口返回的数据分页显示数
*/
private static final int PAGE_SIZE = 3000;
private AccountCheckConfig config;
private IfcertAccessDataService accessDataService;
private IfcertAccessClientService ifcertAccessClientService;
private QueryTaskService queryTaskService;
private PushTaskService pushTaskService;
private IfcertDailyCheckHistoryMapper dailyCheckHistoryMapper;
private IfcertDailyFailRepushHistoryMapper failRepushHistoryMapper;
private MailService mailService;
/**
* 构造注入,避免测试案例mock调用关联bean为null
*
* @param config
* @param accessDataService
* @param ifcertAccessClientService
* @param queryTaskService
* @param pushTaskService
* @param dailyCheckHistoryMapper
* @param failRepushHistoryMapper
* @param mailService
*/
@Autowired
public IfcertAccessCoreServiceImpl(AccountCheckConfig config, IfcertAccessDataService accessDataService,
IfcertAccessClientService ifcertAccessClientService, QueryTaskService queryTaskService,
PushTaskService pushTaskService, IfcertDailyCheckHistoryMapper dailyCheckHistoryMapper,
IfcertDailyFailRepushHistoryMapper failRepushHistoryMapper, MailService mailService) {
this.config = config;
this.accessDataService = accessDataService;
this.ifcertAccessClientService = ifcertAccessClientService;
this.queryTaskService = queryTaskService;
this.pushTaskService = pushTaskService;
this.dailyCheckHistoryMapper = dailyCheckHistoryMapper;
this.failRepushHistoryMapper = failRepushHistoryMapper;
this.mailService = mailService;
}
...
...
}
2.3 Mapper层
import com.htouhui.bonus.coupon.entity.Account;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringRunner;
import java.math.BigDecimal;
@RunWith(SpringRunner.class)
@SpringBootTest
public class AccountMapperTest extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
private AccountMapper accountMapper;
@Test
@Sql("/db/test/account_mapper_test.sql") // 调用方法前执行sql脚本
public void testGetByUserId() {
Account account = accountMapper.getByUserId("h5602154779");
Assert.assertNotNull(account);
Assert.assertEquals(new BigDecimal("888.00"), account.getGrantMoney());
}
@Test
@Sql("/db/test/account_mapper_test.sql")
public void testUpdate() {
Account account = Account.builder()
.id(370941)
.grantMoney(new BigDecimal("444.00"))
.build();
int num = accountMapper.update(account);
Assert.assertEquals(1, num);
Account byId = accountMapper.findById(370941L);
Assert.assertNotNull(byId);
Assert.assertEquals(new BigDecimal("444.00"), byId.getGrantMoney());
}
}
3.对特殊框架的测试mock
3.1 针对shiro模拟用户登录进行mock
在某些场景下,对于shiro用户登录后产生的用户信息,需要用到
shiro官方文档提供了一个测试工具类AbstractShiroTest,需要用到
public abstract class AbstractShiroTest {
private static ThreadState subjectThreadState;
public AbstractShiroTest() {
}
/**
* Allows subclasses to set the currently executing {@link Subject} instance.
*
* @param subject the Subject instance
*/
protected void setSubject(Subject subject) {
clearSubject();
subjectThreadState = createThreadState(subject);
subjectThreadState.bind();
}
protected Subject getSubject() {
return SecurityUtils.getSubject();
}
protected ThreadState createThreadState(Subject subject) {
return new SubjectThreadState(subject);
}
/**
* Clears Shiro's thread state, ensuring the thread remains clean for future test execution.
*/
protected void clearSubject() {
doClearSubject();
}
private static void doClearSubject() {
if (subjectThreadState != null) {
subjectThreadState.clear();
subjectThreadState = null;
}
}
protected static void setSecurityManager(SecurityManager securityManager) {
SecurityUtils.setSecurityManager(securityManager);
}
protected static SecurityManager getSecurityManager() {
return SecurityUtils.getSecurityManager();
}
@AfterClass
public static void tearDownShiro() {
doClearSubject();
try {
SecurityManager securityManager = getSecurityManager();
LifecycleUtils.destroy(securityManager);
} catch (UnavailableSecurityManagerException e) {
//we don't care about this when cleaning up the test environment
//(for example, maybe the subclass is a unit test and it didn't
// need a SecurityManager instance because it was using only
// mock Subject instances)
}
setSecurityManager(null);
}
}
模拟shiro测试
package com.micecs.erp.group.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.micecs.erp.module.admin.Admin;
import com.micecs.erp.module.approval.entity.GroupAudit;
import com.micecs.erp.module.approval.exception.GroupAuditException;
import com.micecs.erp.module.approval.mapper.GroupAuditMapper;
import com.micecs.erp.module.common.CommonException;
import com.micecs.erp.module.enums.*;
import com.micecs.erp.module.meeting.dto.ReserveFundDto;
import com.micecs.erp.module.meeting.entity.AuditRequire;
import com.micecs.erp.module.meeting.entity.GroupBaseInfo;
import com.micecs.erp.module.meeting.entity.ReserveFund;
import com.micecs.erp.module.meeting.exception.DataInitException;
import com.micecs.erp.module.meeting.exception.RfpException;
import com.micecs.erp.module.meeting.mapper.AuditRequireMapper;
import com.micecs.erp.module.meeting.mapper.GroupBaseInfoMapper;
import com.micecs.erp.module.meeting.mapper.ReserveFundMapper;
import com.micecs.erp.module.meeting.service.ReserveManagerService;
import com.micecs.erp.module.meeting.service.impl.ReserveManagerServiceImpl;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.subject.Subject;
import org.junit.*;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.micecs.erp.admin.ShiroTestUtils.setSubject;
import static org.mockito.Mockito.*;
/**
* @author liulei, lei.liu@htouhui.com
* @version 1.0
*/
@RunWith(MockitoJUnitRunner.class)
public class ReserveManagerServiceTest {
@Mock
private ReserveFundMapper reserveFundMapper;
@Mock
private GroupBaseInfoMapper groupBaseInfoMapper;
@Mock
private GroupAuditMapper groupAuditMapper;
@Mock
private AuditRequireMapper auditRequireMapper;
private ReserveManagerService service;
@Before
public void init() {
// 模拟shiro登录
Subject subjectUnderTest = mock(Subject.class);
setSubject(subjectUnderTest);
Admin admin = new Admin();
admin.setId(1L);
admin.setAdministrator(true);
when(subjectUnderTest.getPrincipal()).thenReturn(admin);
service = new ReserveManagerServiceImpl(reserveFundMapper, groupBaseInfoMapper, groupAuditMapper, auditRequireMapper);
}
//该测试案例,有用到shiro登录后的一些用户信息取值静态方法
@Test
public void testGetReserveFundListByGroupId() {
Long groupId = 1L;
List<ReserveFund> list = Lists.newArrayList();
ReserveFund fund = new ReserveFund();
fund.setId(1L);
fund.setGroupId(groupId);
fund.setMoney(new BigDecimal(1000));
fund.setFundType(CostDetailEnum.TRAVEL);
fund.setDataType(DataTypeEnum.BUDGET.getValue());
fund.setCreateTime(LocalDateTime.now());
ReserveFund fund2 = new ReserveFund();
fund2.setId(1L);
fund2.setGroupId(groupId);
fund2.setMoney(new BigDecimal(1000));
fund2.setFundType(CostDetailEnum.TRAVEL);
fund2.setDataType(DataTypeEnum.BUDGET.getValue());
fund2.setCreateTime(LocalDateTime.now());
list.add(fund);
list.add(fund2);
when(reserveFundMapper.selectListByGroupId(isA(LambdaQueryWrapper.class))).thenReturn(list);
try {
service.getReserveFundListByGroupId(groupId, DataTypeEnum.BUDGET);
} catch (UnavailableSecurityManagerException e) {
System.out.println("用户未登录");
}
verify(reserveFundMapper, times(1)).selectListByGroupId(isA(LambdaQueryWrapper.class));
}
}
3.2 针对redisTemplate进行mock
package com.micecs.erp.group.service;
import com.google.common.collect.Lists;
import com.micecs.erp.common.Settings;
import com.micecs.erp.module.approval.entity.AuditArchive;
import com.micecs.erp.module.enums.*;
import com.micecs.erp.module.meeting.entity.GroupBaseInfo;
import com.micecs.erp.module.meeting.entity.InvoiceInfo;
import com.micecs.erp.module.meeting.entity.RequireDetail;
import com.micecs.erp.module.meeting.exception.CustomerBaseException;
import com.micecs.erp.module.meeting.mapper.GroupBaseInfoMapper;
import com.micecs.erp.module.meeting.mapper.InvoiceInfoMapper;
import com.micecs.erp.module.meeting.service.InvoiceInfoService;
import com.micecs.erp.module.meeting.service.impl.InvoiceInfoServiceImpl;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.mockito.Mockito.*;
/**
* @author liulei, lei.liu@htouhui.com
* @version 1.0
*/
@RunWith(MockitoJUnitRunner.class)
public class InvoiceInfoServiceTest {
@Mock
private InvoiceInfoMapper invoiceInfoMapper;
@Mock
private GroupBaseInfoMapper groupBaseInfoMapper;
@Mock
private RedisTemplate<String, Object> collectionRedisTemplate;
@Mock
private ValueOperations valueOperations;
private InvoiceInfoService service;
@Before
public void init() {
when(collectionRedisTemplate.opsForValue()).thenReturn(valueOperations);
service = new InvoiceInfoServiceImpl(invoiceInfoMapper, groupBaseInfoMapper, collectionRedisTemplate);
}
@Test
public void testGetInvoiceOfDefined() {
when(invoiceInfoMapper.getInvoiceByTypes(anyList())).thenReturn(Lists.newArrayList());
service.getInvoiceOfDefined(InvoiceInfoEnum.MEETING);
verify(invoiceInfoMapper, times(1)).getInvoiceByTypes(anyList());
}
@Test
public void testGetInvoiceInfoByType() {
InvoiceInfoEnum type = InvoiceInfoEnum.TRAVEL;
List<InvoiceInfo> itemLists = Lists.newArrayList();
InvoiceInfo item1 = new InvoiceInfo();
item1.setName("款项1");
item1.setValue(1);
InvoiceInfo item2 = new InvoiceInfo();
item2.setName("款项2");
item2.setValue(1);
itemLists.add(item1);
itemLists.add(item2);
List<InvoiceInfo> taxLists = Lists.newArrayList();
InvoiceInfo iv1 = new InvoiceInfo();
iv1.setName("发票1");
iv1.setValue(1);
InvoiceInfo iv2 = new InvoiceInfo();
iv2.setName("发票2");
iv2.setValue(2);
InvoiceInfo iv3 = new InvoiceInfo();
iv3.setName("发票3");
iv3.setValue(3);
InvoiceInfo iv4 = new InvoiceInfo();
iv4.setName("发票4");
iv4.setValue(4);
taxLists.add(iv1);
taxLists.add(iv2);
taxLists.add(iv3);
taxLists.add(iv4);
TeamNatureEnum teamType = TeamNatureEnum.TRAVEL;
String redisKey = Settings.ENV + ":INVOICE:DEFINE:" + type.getValue() + "-" + teamType.getValue() + "-";
when(valueOperations.get(redisKey)).thenReturn(null);
when(invoiceInfoMapper.getInvoiceByTypes(anyList())).thenReturn(itemLists).thenReturn(taxLists);
doNothing().when(valueOperations).set(isA(String.class), anyList(), anyLong(), isA(TimeUnit.class));
List<InvoiceInfo> info = service.getInvoiceInfoByType(type, teamType, null);
verify(valueOperations, times(1)).get(argThat(argument -> {
Assert.assertEquals(redisKey, argument);
return true;
}));
verify(invoiceInfoMapper, times(2)).getInvoiceByTypes(anyList());
info.forEach(vo -> {
// 断言发票税率
Assert.assertTrue(new BigDecimal("0").compareTo(vo.getChildren().get(0).getTaxRate()) == 0);
Assert.assertEquals(4, vo.getChildren().size());
});
}
}