SpringMock测试总结

/ 测试Java / 没有评论 / 2253浏览

1.常用注解

执行顺序:

@BeforeClass -> @Before -> @Test -> @After -> @AfterClass

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());
        });
    }
}