问题描述:
??在写单元测试的时候,需要对LocalDateTime.now() 进行mock,发现如果在当前类中,直接进行mock 测试是可以的,示例如下:
@RunWith(PowerMockRunner.class)
@PrepareForTest({LocalDateTime.class})
@PowerMockIgnore("javax.management.*")
public class TimeTest {
@Before
public void readTest() throws Exception {
PowerMockito.mockStatic(LocalDateTime.class);
LocalDateTime dateTime = LocalDateTime.parse("20210826100000",
DateTimeFormatter.ofPattern(DateFormatEnum.YYYYMMDDHHMMSS.getFormat()));
when(LocalDateTime.now()).thenReturn(dateTime);
}
@Test
public void test() {
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
}
}
??这里打印出来时间为:20210826 。但是当我们想要mock 调用方法中的LocalDateTime 时,却始终为当前时间,示例如下:
# 测试调用对象
public class Time {
public String getTime() {
LocalDateTime dateTime = LocalDateTime.now();
return dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);
}
}
# 测试
@RunWith(PowerMockRunner.class)
@PrepareForTest({LocalDateTime.class})
@PowerMockIgnore("javax.management.*")
public class TimeTest {
@InjectMocks
private Time time;
@Before
public void readTest() {
PowerMockito.mockStatic(LocalDateTime.class);
LocalDateTime dateTime = LocalDateTime.parse("20210826100000",
DateTimeFormatter.ofPattern(DateFormatEnum.YYYYMMDDHHMMSS.getFormat()));
when(LocalDateTime.now()).thenReturn(dateTime);
}
@Test
public void test() {
String time = time.getTime();
System.out.println(time);
}
}
??结果打印出来的是当前时间:20211207 ,并不是我们mock 的数据。
问题分析:
??查了很多资料,始终无法正常的对LocalDateTime.now() 这个方法进行mock ,只能另辟蹊径。发现大家的解决方法分为几种。
- 抽离出时间,将其作为入参传入或抽离单独的方法,这样可以随意修改时间,从而对测试方法进行测试
- 修改获取当前时间方法为
LocalDateTime.now(clock) ,通过对clock 进行mock ,从而达到我们的目的
??方法1很明显是属于代码设计层面的问题解决,也是最快解决问题的方法。但是如果代码已经固话或者是参数改动比较复杂,在方法中存在多个时间的处理等,需要用到方法2。
问题解决:
??方法1:
public class Time {
public String getTime(LocalDateTime dateTime) {
return dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);
}
}
??或:
public class TimeUtil {
public static LocalDateTime getLocalDateTime() {
return LocalDateTime.now();
}
}
public class Time {
public String getTime() {
LocalDateTime dateTime = TimeUtil.getLocalDateTime();
return dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({TimeUtil.class})
@PowerMockIgnore("javax.management.*")
public class TimeTest {
@InjectMocks
private Time timeTest;
@Before
public void readTest() {
PowerMockito.mockStatic(TimeUtil.class);
LocalDateTime dateTime = LocalDateTime.parse("20210826100000",
DateTimeFormatter.ofPattern(DateFormatEnum.YYYYMMDDHHMMSS.getFormat()));
when(TimeUtil.getLocalDateTime()).thenReturn(dateTime);
}
@Test
public void test() {
String time = timeTest.getTime();
System.out.println(time);
}
}
??使用上述的方法2,对于Clock 的mock 也分两种,一种是通过方法获取,我们mock 掉这个方法,从而达到mock Clock 的目的。还有一种方法是将Clock 作为类的私有静态属性,我们通过反射注入的方式对Clock 进行mock 注入。
??注入Clock 和上面的传参类似这里就不写了,关键在于如何通过反射注入静态属性:
public class Time {
private static final Clock clock = Clock.systemDefaultZone();
public String getTime() {
LocalDateTime dateTime = LocalDateTime.now(clock);
return dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({Clock.class})
@PowerMockIgnore("javax.management.*")
public class TimeTest {
@InjectMocks
private Time timeTest;
@Before
public void readTest() throws Exception {
Clock clockMock = Clock.fixed(Instant.parse("2021-08-26T10:00:00Z"), ZoneId.of("UTC"));
Clock clock = PowerMockito.mock(Clock.class);
when(clock.instant()).thenReturn(clockMock.instant());
when(clock.getZone()).thenReturn(clockMock.getZone());
setFinalStatic(Time.class.getDeclaredField("clock"), clock);
}
static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
@Test
public void test() {
String time = timeTest.getTime();
System.out.println(time);
}
}
|