如何在不引发 org.threeten.bp.zone.ZoneRulesException 的情况下对使用 ZoneId.systemDefault 的代码进行单元测试

How to unit test code which makes use of ZoneId.systemDefault without raising org.threeten.bp.zone.ZoneRulesException

我有以下应用程序代码。

申请代码

public static long advanceByMax1HourWithoutOverflow(long currentTimeMillis) {
    ZoneId zoneId = ZoneId.systemDefault();

    ZonedDateTime zonedDateTime = Instant.ofEpochMilli(currentTimeMillis).atZone(zoneId);

    // 0-23
    int hour = zonedDateTime.getHour();

    if (hour < 23) {
        zonedDateTime = zonedDateTime.plusHours(1);
        return zonedDateTime.toInstant().toEpochMilli();
    }

    // 0-59
    int minute = zonedDateTime.getMinute();

    if (minute < 59) {
        zonedDateTime = zonedDateTime.plusMinutes(1);
        return zonedDateTime.toInstant().toEpochMilli();
    }

    return currentTimeMillis;
}

我用下面的内容为它写了一个单元测试。

单元测试代码

@Test
public void advanceByMax1HourWithoutOverflow() {
    /*
    27 October 2018
    1540648800000 -> 1540652400000
    22:00 -> 23:00
    */
    long currentTimeMillis = 1540648800000L;
    long expected = 1540652400000L;
    long output = Utils.advanceByMax1HourWithoutOverflow(currentTimeMillis);
    assertEquals(expected, output);

我收到以下错误

org.threeten.bp.zone.ZoneRulesException: No time-zone data files registered

我尝试使用

修复它

单元测试代码

@Test
public void advanceByMax1HourWithoutOverflow() {
    Context context = mock(Context.class);
    AndroidThreeTen.init(context);

    /*
    27 October 2018
    1540648800000 -> 1540652400000
    22:00 -> 23:00
    */
    long currentTimeMillis = 1540648800000L;
    long expected = 1540652400000L;
    long output = Utils.advanceByMax1HourWithoutOverflow(currentTimeMillis);
    assertEquals(expected, output);

我得到

java.lang.ExceptionInInitializerError
    at org.threeten.bp.ZoneRegion.ofId(ZoneRegion.java:143)
    at org.threeten.bp.ZoneId.of(ZoneId.java:358)
    at org.threeten.bp.ZoneId.of(ZoneId.java:286)
    at org.threeten.bp.ZoneId.systemDefault(ZoneId.java:245)

有什么方法可以在不更改当前应用程序方法签名的情况下通过单元测试?我想保留我当前的应用程序方法签名而不做任何修改。

long advanceByMax1HourWithoutOverflow(long currentTimeMillis)

注意,我用于单元测试的gradle是

implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'

testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.19.0'

ThreeTen-Android Backport 提供 Java SE 8 日期时间 classes 到不支持 Java 的 Android 系统的反向移植8.这个问题的原因是单元测试运行在JVM上,而不是Android根据android developer:

Unit tests that run on your local machine only. These tests are compiled to run locally on the Java Virtual Machine (JVM) to minimize execution time. If your tests depend on objects in the Android framework, we recommend using Robolectric. For tests that depend on your own dependencies, use mock objects to emulate your dependencies' behavior.

Jake Wharton(ThreeTen-Android Backport 所有者)关于 Robolectric has said:

没有创建资源

This is a Robolectric bug. Either report to them or switch to use the normal ThreeTenBP in your unit tests. Or both!

因此我们应该使用原始的 ThreeTen Backport 库来解决这个问题。所以我们要把它的依赖添加到build.gradle文件中,供单元测试使用。

在 build.gradle 文件中:

dependencies {

    implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'

    testImplementation ('org.threeten:threetenbp:1.3.2'){
        exclude group:'com.jakewharton.threetenabp', module:'threetenabp'
    }
}

在单元测试中class:

@Test
public void advanceByMax1HourWithoutOverflow() {
    /*
    27 October 2018
    1540648800000 -> 1540652400000
    22:00 -> 23:00
    */
    long currentTimeMillis = 1540648800000L;
    long expected = 1540652400000L;
    long output = Utils.advanceByMax1HourWithoutOverflow(currentTimeMillis);
    assertEquals(expected, output);
}

结果: