Joda Time có DateTimeUtils.setCurrentMillisFixed () để mô phỏng thời gian.
Nó rất thực tế trong các thử nghiệm.
Có tương đương trong API java.time của Java 8 không?
Joda Time có DateTimeUtils.setCurrentMillisFixed () để mô phỏng thời gian.
Nó rất thực tế trong các thử nghiệm.
Có tương đương trong API java.time của Java 8 không?
Câu trả lời:
Cái gần nhất là Clock
đồ vật. Bạn có thể tạo đối tượng Đồng hồ bằng cách sử dụng bất kỳ lúc nào bạn muốn (hoặc từ thời gian hiện tại của Hệ thống). Tất cả các đối tượng date.time đều có now
các phương thức nạp chồng lấy một đối tượng đồng hồ thay thế cho thời gian hiện tại. Vì vậy, bạn có thể sử dụng phương pháp tiêm phụ thuộc để tiêm Đồng hồ với thời gian cụ thể:
public class MyBean {
private Clock clock; // dependency inject
...
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}
Xem Clock JavaDoc để biết thêm chi tiết
Tôi đã sử dụng một lớp mới để ẩn việc Clock.fixed
tạo và đơn giản hóa các bài kiểm tra:
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()
phương thức và sử dụng trường trực tiếp. Phương pháp này không thêm gì ngoài một vài dòng mã.
Tôi đã sử dụng một lĩnh vực
private Clock clock;
và sau đó
LocalDate.now(clock);
trong mã sản xuất của tôi. Sau đó, tôi sử dụng Mockito trong các bài kiểm tra đơn vị của mình để giả lập Đồng hồ bằng Clock.fixed ():
@Mock
private Clock clock;
private Clock fixedClock;
Chế giễu:
fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();
Quả quyết:
assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));
Tôi thấy việc sử dụng Clock
mã sản xuất của bạn làm lộn xộn.
Bạn có thể sử dụng JMockit hoặc PowerMock để giả lập các lệnh gọi phương thức tĩnh trong mã thử nghiệm của mình. Ví dụ với 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);
}
CHỈNH SỬA : Sau khi đọc các bình luận về câu trả lời của Jon Skeet cho một câu hỏi tương tự ở đây trên VẬY, tôi không đồng ý với quá khứ của mình. Hơn bất cứ điều gì khác, lập luận thuyết phục tôi rằng bạn không thể làm song song các bài kiểm tra khi bạn mô phỏng các phương thức tĩnh.
Tuy nhiên, bạn có thể / vẫn phải sử dụng tính năng mô phỏng tĩnh nếu bạn phải xử lý mã kế thừa.
Tôi cần LocalDate
ví dụ thay vì LocalDateTime
.
Với lý do như vậy, tôi đã tạo lớp tiện ích sau:
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);
}
}
Và cách sử dụng nó giống như:
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());
}
}
Đầu ra:
2019-01-03
1998-12-12
2019-01-03
Thay thế mọi tạo vật LocalDate.now()
đến Clock.getCurrentDate()
trong dự án.
Bởi vì nó là ứng dụng khởi động mùa xuân . Trước khi test
thực hiện hồ sơ, chỉ cần đặt một ngày được xác định trước cho tất cả các thử nghiệm:
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);
}
}
}
Và thêm vào spring.factories :
org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer
Joda Time chắc chắn rất hay (cảm ơn Stephen, Brian, bạn đã làm cho thế giới của chúng ta trở nên tốt đẹp hơn) nhưng tôi không được phép sử dụng nó.
Sau một số thử nghiệm, cuối cùng tôi đã nghĩ ra một cách để mô phỏng thời gian đến một ngày cụ thể trong API java.time của Java 8 với EasyMock
Đây là những gì cần phải làm:
Thêm java.time.Clock
thuộc tính mới vào lớp đã thử nghiệm MyService
và đảm bảo thuộc tính mới sẽ được khởi tạo đúng cách ở các giá trị mặc định bằng khối khởi tạo hoặc phương thức khởi tạo:
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
// (...)
}
Đưa thuộc tính mới clock
vào phương thức gọi ngày-giờ hiện tại. Ví dụ: trong trường hợp của tôi, tôi phải thực hiện kiểm tra xem ngày được lưu trữ trong cơ sở dữ liệu có xảy ra trước LocalDateTime.now()
đó hay không, ngày mà tôi đã thay thế bằng LocalDateTime.now(clock)
, như sau:
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();
}
}
// (...)
}
Trong lớp thử nghiệm, hãy tạo một đối tượng đồng hồ giả và đưa nó vào cá thể của lớp được thử nghiệm ngay trước khi bạn gọi phương thức được thử nghiệm doExecute()
, sau đó đặt lại nó ngay sau đó, như sau:
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(..)
}
}
Kiểm tra nó ở chế độ gỡ lỗi và bạn sẽ thấy ngày 3 tháng 2 năm 2017 đã được nhập chính xác vào myService
ví dụ và được sử dụng trong hướng dẫn so sánh, và sau đó đã được đặt lại đúng cách về ngày hiện tại với initDefaultClock()
.
Ví dụ này thậm chí còn cho thấy cách kết hợp Instant và LocalTime ( giải thích chi tiết về các vấn đề với chuyển đổi )
Một lớp học đang được kiểm tra
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);
}
}
Một bài kiểm tra 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
}
}
Với sự trợ giúp của PowerMockito cho bài kiểm tra khởi động mùa xuân, bạn có thể thử ZonedDateTime
. Bạn cần những thứ sau đây.
Trong lớp thử nghiệm, bạn cần chuẩn bị dịch vụ sử dụng ZonedDateTime
.
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
@Autowired
private EscalationService escalationService;
//...
}
Trong thử nghiệm, bạn có thể chuẩn bị một thời gian mong muốn và lấy nó để phản hồi lại lời gọi phương thức.
@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
rất hữu ích trong thử nghiệm, trong khiClock.system
hoặcClock.systemUTC
có thể được sử dụng trong ứng dụng.