Spring Boot 2 实践记录之 封装依赖及尽可能不创建静态方法以避免在 Service 和 Controller 的单元测试中使用 Powermock

在前面的文章中(Spring Boot 2 实践记录之 Powermock 和 SpringBootTest)提到了使用 Powermock 结合 SpringBootTest、WebMvcTest 来 Mock Service、Controller 中的 静态类和静态方法。

但此法有两个弊端,一是这样的单元测试运行速度慢,二是时不时会出现测试运行停顿的情况。

一个可选的方案就是将这些用在 Service、Controller 中的静态类和静态方法的引用,封装在普通 Bean 中,Service、Controller 使用这些 Bean 来完成相应的功能。这样一来,针对 Service、Controller 的单元测试中,就可以使用 @MockBean 结合 Mockito 直接 Mock 这些封装类及其方法了。

例如,在用户注册中,使用了 UUID 类来生成 用户id,使用 Date 插入注册时间,代码如下:

@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;
        }
    }
}

对应的单元测试:

@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的示例如下:

工具类:

@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());
...... }
原文地址:https://www.cnblogs.com/matchless/p/10442372.html