Joda Time มีDateTimeUtils.setCurrentMillisFixed () ที่ดีเพื่อจำลองเวลา
มันใช้งานได้จริงในการทดสอบ
มีเทียบเท่าในJava 8 java.time API ?
Joda Time มีDateTimeUtils.setCurrentMillisFixed () ที่ดีเพื่อจำลองเวลา
มันใช้งานได้จริงในการทดสอบ
มีเทียบเท่าในJava 8 java.time API ?
คำตอบ:
สิ่งที่ใกล้ตัวที่สุดคือClock
วัตถุ คุณสามารถสร้างวัตถุนาฬิกาโดยใช้เวลาใดก็ได้ที่คุณต้องการ (หรือจากเวลาปัจจุบันของระบบ) อ็อบเจ็กต์ date.time ทั้งหมดมีnow
เมธอดที่โอเวอร์โหลดซึ่งใช้อ็อบเจ็กต์นาฬิกาแทนเวลาปัจจุบัน ดังนั้นคุณสามารถใช้การฉีดพึ่งพาเพื่อฉีดนาฬิกาตามเวลาที่กำหนด:
public class MyBean {
private Clock clock; // dependency inject
...
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}
ดูนาฬิกา JavaDocสำหรับรายละเอียดเพิ่มเติม
ฉันใช้คลาสใหม่เพื่อซ่อนการClock.fixed
สร้างและลดความซับซ้อนของการทดสอบ:
public class TimeMachine {
private static Clock clock = Clock.systemDefaultZone();
private static ZoneId zoneId = ZoneId.systemDefault();
public static LocalDateTime now() {
return LocalDateTime.now(getClock());
}
public static void useFixedClockAt(LocalDateTime date){
clock = Clock.fixed(date.atZone(zoneId).toInstant(), zoneId);
}
public static void useSystemDefaultZoneClock(){
clock = Clock.systemDefaultZone();
}
private static Clock getClock() {
return clock ;
}
}
public class MyClass {
public void doSomethingWithTime() {
LocalDateTime now = TimeMachine.now();
...
}
}
@Test
public void test() {
LocalDateTime twoWeeksAgo = LocalDateTime.now().minusWeeks(2);
MyClass myClass = new MyClass();
TimeMachine.useFixedClockAt(twoWeeksAgo);
myClass.doSomethingWithTime();
TimeMachine.useSystemDefaultZoneClock();
myClass.doSomethingWithTime();
...
}
getClock()
เมธอดและใช้ฟิลด์ได้โดยตรง วิธีนี้ไม่เพิ่มอะไรเลยนอกจากโค้ดไม่กี่บรรทัด
ฉันใช้สนาม
private Clock clock;
แล้ว
LocalDate.now(clock);
ในรหัสการผลิตของฉัน จากนั้นฉันใช้ Mockito ในการทดสอบหน่วยของฉันเพื่อจำลองนาฬิกาโดยใช้ Clock.fixed ():
@Mock
private Clock clock;
private Clock fixedClock;
ซึ่งจำลอง:
fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();
การยืนยัน:
assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));
ฉันพบว่าใช้Clock
รหัสการผลิตของคุณถ่วง
คุณสามารถใช้JMockitหรือPowerMockเพื่อจำลองการเรียกใช้เมธอดแบบคงที่ในโค้ดทดสอบของคุณ ตัวอย่างด้วย JMockit:
@Test
public void testSth() {
LocalDate today = LocalDate.of(2000, 6, 1);
new Expectations(LocalDate.class) {{
LocalDate.now(); result = today;
}};
Assert.assertEquals(LocalDate.now(), today);
}
แก้ไข : หลังจากอ่านความคิดเห็นเกี่ยวกับคำตอบของ Jon Skeet สำหรับคำถามที่คล้ายกันที่นี่ฉันไม่เห็นด้วยกับตัวเองในอดีตของฉัน มากกว่าสิ่งอื่นใดที่การโต้แย้งทำให้ฉันเชื่อว่าคุณไม่สามารถแยกส่วนการทดสอบเมื่อคุณเยาะเย้ยวิธีการคงที่
คุณสามารถ / ยังคงใช้การเยาะเย้ยแบบคงที่ได้หากคุณต้องจัดการกับรหัสเดิม
จำเป็นต้องฉันอินสแตนซ์แทนLocalDate
ด้วยเหตุผลดังกล่าวฉันจึงสร้างคลาสยูทิลิตี้ต่อไปนี้:LocalDateTime
public final class Clock {
private static long time;
private Clock() {
}
public static void setCurrentDate(LocalDate date) {
Clock.time = date.toEpochDay();
}
public static LocalDate getCurrentDate() {
return LocalDate.ofEpochDay(getDateMillis());
}
public static void resetDate() {
Clock.time = 0;
}
private static long getDateMillis() {
return (time == 0 ? LocalDate.now().toEpochDay() : time);
}
}
และการใช้งานมันเป็นดังนี้:
class ClockDemo {
public static void main(String[] args) {
System.out.println(Clock.getCurrentDate());
Clock.setCurrentDate(LocalDate.of(1998, 12, 12));
System.out.println(Clock.getCurrentDate());
Clock.resetDate();
System.out.println(Clock.getCurrentDate());
}
}
เอาท์พุต:
2019-01-03
1998-12-12
2019-01-03
แทนที่การสร้างทั้งหมดLocalDate.now()
จะClock.getCurrentDate()
อยู่ในโครงการ
เนื่องจากเป็นโปรแกรมสปริงบูต ก่อนที่จะtest
ดำเนินการโปรไฟล์ให้ตั้งวันที่ที่กำหนดไว้ล่วงหน้าสำหรับการทดสอบทั้งหมด:
public class TestProfileConfigurer implements ApplicationListener<ApplicationPreparedEvent> {
private static final LocalDate TEST_DATE_MOCK = LocalDate.of(...);
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
if (environment.acceptsProfiles(Profiles.of("test"))) {
Clock.setCurrentDate(TEST_DATE_MOCK);
}
}
}
และเพิ่มไปที่spring.factories :
org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer
Joda Time นั้นดีแน่นอน (ขอบคุณ Stephen, Brian คุณทำให้โลกของเราน่าอยู่ขึ้น) แต่ฉันไม่ได้รับอนุญาตให้ใช้
หลังจากการทดลองบางอย่างในที่สุดฉันก็หาวิธีจำลองเวลาไปยังวันที่ที่ระบุใน java.time API ของ Java 8 ด้วย EasyMock
นี่คือสิ่งที่ต้องทำ:
เพิ่มjava.time.Clock
แอ็ตทริบิวต์ใหม่ให้กับคลาสที่ทดสอบMyService
และตรวจสอบให้แน่ใจว่าแอ็ตทริบิวต์ใหม่ถูกเตรียมใช้งานอย่างถูกต้องตามค่าดีฟอลต์ด้วยบล็อกอินสแตนซ์หรือตัวสร้าง:
import java.time.Clock;
import java.time.LocalDateTime;
public class MyService {
// (...)
private Clock clock;
public Clock getClock() { return clock; }
public void setClock(Clock newClock) { clock = newClock; }
public void initDefaultClock() {
setClock(
Clock.system(
Clock.systemDefaultZone().getZone()
// You can just as well use
// java.util.TimeZone.getDefault().toZoneId() instead
)
);
}
{ initDefaultClock(); } // initialisation in an instantiation block, but
// it can be done in a constructor just as well
// (...)
}
ใส่แอตทริบิวต์ใหม่clock
ลงในวิธีการที่เรียกวันที่ - เวลาปัจจุบัน ตัวอย่างเช่นในกรณีของฉันฉันต้องทำการตรวจสอบว่าวันที่ที่เก็บไว้ในฐานข้อมูลเกิดขึ้นก่อนหน้าLocalDateTime.now()
นี้หรือไม่ซึ่งฉันแทนที่ด้วยLocalDateTime.now(clock)
ดังนี้:
import java.time.Clock;
import java.time.LocalDateTime;
public class MyService {
// (...)
protected void doExecute() {
LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
someOtherLogic();
}
}
// (...)
}
ในคลาสทดสอบให้สร้างวัตถุนาฬิกาจำลองและฉีดเข้าไปในอินสแตนซ์ของคลาสทดสอบก่อนที่คุณจะเรียกใช้วิธีการทดสอบdoExecute()
จากนั้นรีเซ็ตกลับทันทีในภายหลังดังนี้
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;
public class MyServiceTest {
// (...)
private int year = 2017; // Be this a specific
private int month = 2; // date we need
private int day = 3; // to simulate.
@Test
public void doExecuteTest() throws Exception {
// (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot
MyService myService = new MyService();
Clock mockClock =
Clock.fixed(
LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
);
myService.setClock(mockClock); // set it before calling the tested method
myService.doExecute(); // calling tested method
myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method
// (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
}
}
ตรวจสอบในโหมดการแก้ปัญหาและคุณจะเห็นวันที่ 3 กุมภาพันธ์ 2017 ที่ได้รับการฉีดอย่างถูกต้องลงในอินสแตนซ์และนำมาใช้ในการเรียนการสอนการเปรียบเทียบและจากนั้นได้รับอย่างถูกต้องรีเซ็ตวันที่ปัจจุบันด้วยmyService
initDefaultClock()
ตัวอย่างนี้ยังแสดงวิธีการรวม Instant และ LocalTime ( คำอธิบายโดยละเอียดเกี่ยวกับปัญหาเกี่ยวกับการแปลง )
ชั้นเรียนที่อยู่ระหว่างการทดสอบ
import java.time.Clock;
import java.time.LocalTime;
public class TimeMachine {
private LocalTime from = LocalTime.MIDNIGHT;
private LocalTime until = LocalTime.of(6, 0);
private Clock clock = Clock.systemDefaultZone();
public boolean isInInterval() {
LocalTime now = LocalTime.now(clock);
return now.isAfter(from) && now.isBefore(until);
}
}
การทดสอบ Groovy
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.time.Clock
import java.time.Instant
import static java.time.ZoneOffset.UTC
import static org.junit.runners.Parameterized.Parameters
@RunWith(Parameterized)
class TimeMachineTest {
@Parameters(name = "{0} - {2}")
static data() {
[
["01:22:00", true, "in interval"],
["23:59:59", false, "before"],
["06:01:00", false, "after"],
]*.toArray()
}
String time
boolean expected
TimeMachineTest(String time, boolean expected, String testName) {
this.time = time
this.expected = expected
}
@Test
void test() {
TimeMachine timeMachine = new TimeMachine()
timeMachine.clock = Clock.fixed(Instant.parse("2010-01-01T${time}Z"), UTC)
def result = timeMachine.isInInterval()
assert result == expected
}
}
ด้วยความช่วยเหลือของ PowerMockito สำหรับการทดสอบสปริงบูตคุณสามารถเยาะเย้ยไฟล์ ZonedDateTime
. คุณต้องการสิ่งต่อไปนี้
ในชั้นเรียนทดสอบคุณต้องเตรียมบริการที่ใช้ไฟล์ZonedDateTime
.
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
@Autowired
private EscalationService escalationService;
//...
}
ในการทดสอบคุณสามารถเตรียมเวลาที่ต้องการและตอบสนองการเรียกใช้เมธอดได้
@Test
public void escalateOnMondayAt14() throws Exception {
ZonedDateTime preparedTime = ZonedDateTime.now();
preparedTime = preparedTime.with(DayOfWeek.MONDAY);
preparedTime = preparedTime.withHour(14);
PowerMockito.mockStatic(ZonedDateTime.class);
PowerMockito.when(ZonedDateTime.now(ArgumentMatchers.any(ZoneId.class))).thenReturn(preparedTime);
// ... Assertions
}
Clock.fixed
มีประโยชน์ในการทดสอบในขณะที่Clock.system
หรือClock.systemUTC
อาจใช้ในแอปพลิเคชัน