Spring Boot 2 實踐記錄之 封裝依賴及儘量不建立靜態方法以免在 Service 和 Controller 的單元測試中使用 Powermock

在前面的文章中(Spring Boot 2 實踐記錄之 Powermock 和 SpringBootTest)提到了使用 Powermock 結合 SpringBootTest、WebMvcTest 來 Mock Service、Controller 中的 靜態類和靜態方法。html

但此法有兩個弊端,一是這樣的單元測試運行速度慢,二是時不時會出現測試運行停頓的狀況。java

一個可選的方案就是將這些用在 Service、Controller 中的靜態類和靜態方法的引用,封裝在普通 Bean 中,Service、Controller 使用這些 Bean 來完成相應的功能。這樣一來,針對 Service、Controller 的單元測試中,就可使用 @MockBean 結合 Mockito 直接 Mock 這些封裝類及其方法了。app

例如,在用戶註冊中,使用了 UUID 類來生成 用戶id,使用 Date 插入註冊時間,代碼以下:less

@Service
public class UsersServiceImpl implements UsersServiceInterface {

    @Autowired
    private Users users;

    @Autowired
    private UsersMapper usersMapper;

    /**
     * 用戶註冊 service 方法
     * @param userIn
     * @return
     */
    @Override
    public String signUp(UserIn userIn) {
        String result;
        Date now = new Date();
        users.setUserId(UUID.randomUUID().toString());
        users.setRegTime(now);
        ......
        
        try {
            Integer result = usersMapper.insert(users);
            if (0 == result) {
                result = "fail";
            } else {
                result = "success";
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e.fillInStackTrace());
            result = "fail";
        } finally {
            return result;
        }
    }
}

對應的單元測試:dom

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})
@PrepareForTest({UsersService.class, Date.class, UUID.class})
@SpringBootTest
@Transactional
public class UsersServiceMybatisImplTest {

    @Autowired
    private UsersServiceMybatisImpl usersServiceMybatis;

    @MockBean
    private EncryptInterface encryptInterface;

    @MockBean
    private DateUtils dateUtils;

    @Autowired
    private UsersMapper usersMapper;

    private UserIn userIn;
    @Before
    public void setUp() throws Exception {
        userIn = new UserIn();
        Mockito.when(encryptInterface.passwordGenerator("hello123")).thenReturn("abcdefghijklmn");
    }

    @After
    public void tearDown() throws Exception {
    }

@Test
    public void signUp(){
        Date date = (new GregorianCalendar(2018, 11, 9)).getTime();
        PowerMockito.mockStatic(Date.class);
        PowerMockito.whenNew(Date.class).withNoArgments().thenReturn(date);
        String randomString = "abcdefg";
        UUID uuid = PowerMockito.mock(UUID.class);
        Mockito.when(uuid.toString()).thenReturn(randomString);
        PowerMockito.mockStatic(UUID.class);
        PowerMockito.when(UUID.randomUUID()).thenReturn(uuid);
        userIn.setUserId("admin123");
        userIn.setSign("盤古氏");
        userIn.setEmail("pangu@gushen.com");
        userIn.setPassword("hello123");
        userIn.setRePassword("hello123");
        userIn.setIp("127.0.0.1");
        userIn.setNick("盤古");
        userIn.setSchool("混沌大學");
        assertEquals("success", result);
        Users user = usersMapper.selectByPrimaryKey("abcdefg");  assertEquals(date.toString(), user.getRegTime().toString());
......
} }

 

將靜態類和靜態方法封裝成普通 Bean的示例以下:ide

工具類:工具

@Component
public class DateUtils {
    public Date generateDate() {
        return new Date();
    }
}
@Component
public class UUIDUtils {
    public Date generateUUID() {
        return UUID.randomUUID();
    }
}

Service 類:單元測試

@Service
public class UsersServiceImpl implements UsersServiceInterface {

    @Autowired
    private Users users;

    @Autowired
    private UsersMapper usersMapper;

    @Autowired
    private DateUtils dateUtils;

    @Autowired
    private UUIDUtils uuidUtils;

    /**
     * 用戶註冊 service 方法
     * @param userIn
     * @return
     */
    @Override
    public String signUp(UserIn userIn) {
        String result;
        Date now = dateUtils.generateDate();
        users.setUserId(uuidUtils.generateUUID().toString());
        users.setRegTime(now);
        ......
        
        try {
            Integer result = usersMapper.insert(users);
            if (0 == result) {
                result = "fail";
            } else {
                result = "success";
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e.fillInStackTrace());
            result = "fail";
        } finally {
            return result;
        }
    }
}

單元測試:測試

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class UsersServiceMybatisImplTest {

    @Autowired
    private UsersServiceMybatisImpl usersServiceMybatis;

    @MockBean
    private EncryptInterface encryptInterface;

    @MockBean
    private DateUtils dateUtils;

    @MockBean
    private UUIDUtils uuidUtils;

    @Autowired
    private UsersMapper usersMapper;

    private UserIn userIn;

    @Before
    public void setUp() throws Exception {
       userIn = new UserIn();
        Mockito.when(encryptInterface.passwordGenerator("hello123")).thenReturn("abcdefghijklmn");
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void signUp(){
        Date date = (new GregorianCalendar(2018, 11, 9)).getTime();
        Mockito.when(dateUtils.generateDate()).thenReturn(date);
        UUID uuid = Mockito.mock(UUID.randomUUID());
        Mockito.when(uuid.toString()).thenReturn("abcdefg");
        Mockito.when(uuidUtils.generateUUID()).thenReturn(uuid);
        userIn.setUserId("admin123");
        userIn.setSign("盤古氏");
        userIn.setEmail("pangu@gushen.com");
        userIn.setPassword("hello123");
        userIn.setRePassword("hello123");
        userIn.setIp("127.0.0.1");
        userIn.setNick("盤古");
        userIn.setSchool("混沌大學");
        assertEquals("success", result);
        Users user = usersMapper.selectByPrimaryKey("abcdefg");
        assertEquals(date.toString(), user.getRegTime().toString());        ......
    }
相關文章
相關標籤/搜索