Waktu mengejek di Java.time API Java 8

Jawaban:

72

Yang paling dekat adalah Clockobjeknya. Anda dapat membuat objek Jam menggunakan waktu yang Anda inginkan (atau dari waktu Sistem saat ini). Semua objek date.time memiliki nowmetode kelebihan beban yang mengambil objek jam sebagai gantinya untuk waktu saat ini. Jadi Anda dapat menggunakan injeksi ketergantungan untuk menyuntikkan Jam dengan waktu tertentu:

public class MyBean {
    private Clock clock;  // dependency inject
    ...
    public void process(LocalDate eventDate) {
      if (eventDate.isBefore(LocalDate.now(clock)) {
        ...
      }
    }
  }

Lihat Clock JavaDoc untuk detail selengkapnya

dkatzel.dll
sumber
11
Iya. Secara khusus, Clock.fixedberguna dalam pengujian, sementara Clock.systematau Clock.systemUTCmungkin digunakan dalam aplikasi.
Matt Johnson-Pint
8
Malu karena tidak ada jam yang bisa berubah yang memungkinkan saya menyetelnya ke waktu non-ticking, tetapi ubah waktu itu nanti (Anda dapat melakukannya dengan joda). Ini akan berguna untuk menguji kode sensitif waktu, misalnya cache dengan masa berakhir berbasis waktu atau kelas yang menjadwalkan acara di masa mendatang.
bacar
2
@bacar Jam adalah kelas abstrak, Anda dapat membuat implementasi Jam uji Anda sendiri
Bjarne Boström
Saya yakin itulah yang akhirnya kami lakukan.
bacar
23

Saya menggunakan kelas baru untuk menyembunyikan Clock.fixedpembuatan dan menyederhanakan tes:

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();

    ...
}
LuizSignorelli
sumber
4
Bagaimana dengan keamanan thread jika beberapa pengujian dijalankan secara paralel dan mengubah jam TimeMachine?
youri
Anda harus meneruskan jam ke objek yang diuji dan menggunakannya saat memanggil metode terkait waktu. Dan Anda dapat menghapus getClock()metode tersebut dan menggunakan bidang tersebut secara langsung. Metode ini hanya menambahkan beberapa baris kode.
deamon
1
Banktime atau TimeMachine?
Emanuele
11

Saya menggunakan lapangan

private Clock clock;

lalu

LocalDate.now(clock);

dalam kode produksi saya. Lalu saya menggunakan Mockito dalam pengujian unit saya untuk mengejek Jam menggunakan Clock.fixed ():

@Mock
private Clock clock;
private Clock fixedClock;

Mengejek:

fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();

Tuntutan:

assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));
Claas Wilke
sumber
9

Saya menemukan menggunakan Clockkekacauan kode produksi Anda.

Anda dapat menggunakan JMockit atau PowerMock untuk meniru pemanggilan metode statis dalam kode pengujian Anda. Contoh dengan 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);
}

EDIT : Setelah membaca komentar pada jawaban Jon Skeet untuk pertanyaan serupa di sini JADI saya tidak setuju dengan diri saya di masa lalu. Lebih dari segalanya, argumen tersebut meyakinkan saya bahwa Anda tidak dapat memparalelkan pengujian ketika Anda mengejek metode statis.

Anda masih bisa / harus menggunakan ejekan statis jika Anda harus berurusan dengan kode lama.

Stefan Haberl
sumber
1
+1 untuk komentar "menggunakan ejekan statis untuk kode lama". Jadi untuk kode baru, didorong untuk bersandar ke Injeksi Ketergantungan dan menyuntikkan Jam (Jam tetap untuk pengujian, Jam sistem untuk runtime produksi).
David Groomes
1

Saya butuh LocalDatecontoh, bukan LocalDateTime.
Dengan alasan seperti itu saya membuat kelas utilitas berikut:

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

Dan penggunaannya seperti:

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());
    }
}

Keluaran:

2019-01-03
1998-12-12
2019-01-03

Mengganti semua kreasi LocalDate.now()ke Clock.getCurrentDate()dalam proyek.

Karena ini adalah aplikasi boot musim semi . Sebelum testeksekusi profil, cukup setel tanggal yang telah ditentukan untuk semua tes:

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

Dan tambahkan ke spring.factories :

org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer

catch23
sumber
1

Berikut cara kerja untuk mengganti waktu sistem saat ini ke tanggal tertentu untuk tujuan pengujian JUnit di aplikasi web Java 8 dengan EasyMock

Joda Time pasti menyenangkan (terima kasih Stephen, Brian, Anda telah membuat dunia kita menjadi tempat yang lebih baik) tetapi saya tidak diizinkan untuk menggunakannya.

Setelah beberapa percobaan, saya akhirnya menemukan cara untuk meniru waktu ke tanggal tertentu di Java.time API Java 8 dengan EasyMock

  • Tanpa Joda Time API
  • Tanpa PowerMock.

Inilah yang perlu dilakukan:

Apa yang perlu dilakukan di kelas yang diuji

Langkah 1

Tambahkan java.time.Clockatribut baru ke kelas yang diuji MyServicedan pastikan atribut baru akan diinisialisasi dengan benar pada nilai default dengan blok instantiation atau konstruktor:

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
  // (...)
}

Langkah 2

Masukkan atribut baru clockke dalam metode yang memanggil tanggal-waktu saat ini. Misalnya, dalam kasus saya, saya harus melakukan pemeriksaan apakah tanggal yang disimpan dalam database terjadi sebelumnya LocalDateTime.now(), yang saya ganti dengan LocalDateTime.now(clock), seperti:

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();
    }
  }
  // (...) 
}

Apa yang perlu dilakukan di kelas pengujian

LANGKAH 3

Di kelas pengujian, buat objek jam tiruan dan masukkan ke dalam instance kelas yang diuji tepat sebelum Anda memanggil metode yang diuji doExecute(), lalu setel ulang kembali setelahnya, seperti ini:

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(..)
    }
  }

Periksa dalam mode debug dan Anda akan melihat tanggal 3 Feb 2017 telah dimasukkan dengan benar ke dalam myServiceinstance dan digunakan dalam instruksi perbandingan, dan kemudian telah disetel ulang dengan benar ke tanggal saat ini dengan initDefaultClock().

KiriSakow
sumber
0

Contoh ini bahkan menunjukkan cara menggabungkan Instan dan Waktu Lokal ( penjelasan mendetail tentang masalah dengan konversi )

Kelas yang sedang diuji

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

}

Tes 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
    }

}
banterCZ
sumber
0

Dengan bantuan PowerMockito untuk uji boot musim semi, Anda dapat membuat tiruan file ZonedDateTime. Anda membutuhkan yang berikut ini.

Anotasi

Di kelas pengujian, Anda perlu menyiapkan layanan yang menggunakan ekstensi ZonedDateTime.

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
  @Autowired
  private EscalationService escalationService;
  //...
}

Kasus cobaan

Dalam pengujian, Anda dapat menyiapkan waktu yang diinginkan, dan mendapatkannya sebagai respons dari panggilan metode.

  @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 
}
pasquale
sumber