如何在不引发 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

提问人:Cheok Yan Cheng 提问时间:10/28/2018 最后编辑:aminographyCheok Yan Cheng 更新时间:2/5/2019 访问量:3044

问:

我有以下应用程序代码。

应用程序代码

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'
Java Android 单元测试 threetenbp

评论


答:

18赞 aminography 10/31/2018 #1

ThreeTen-Android 向后移植提供了 Java SE 8 日期时间类到不支持 Java 8 的 Android 系统的向后移植。根据 android 开发人员的说法,此问题的原因是单元测试在 JVM 上运行,而不是 Android:

仅在本地计算机上运行的单元测试。这些测试被编译为在 Java 虚拟机 (JVM) 上本地运行,以最大程度地减少执行时间。如果您的测试依赖于 Android 框架中的对象,我们建议使用 Robolectric。对于依赖于你自己的依赖项的测试,请使用模拟对象来模拟依赖项的行为。

Jake Wharton(ThreeTen-Android Backport 的所有者)谈到了 Robolectric 没有建立资源的问题,他说

这是一个 Robolectric 错误。要么向他们报告,要么切换到使用 单元测试中的正常 ThreeTenBP。或两者兼而有之!

因此,我们应该使用原始的 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'
    }
}

在单元测试类中:

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

结果:

enter image description here