如何通过单元测试测试 timeZone 方法?

Hen*_*nry 2 java testing timezone unit-testing timezone-offset

我使用以下方法获取时区偏移值,例如 GMT-3,并返回给定偏移量的 time zoneId 列表。

private static List<String> getTimeZonesByZoneOffset(final int offset) {
    return ZoneId.getAvailableZoneIds()
            .stream()
            .filter(zoneId -> ZoneId.of(zoneId)
                    .getRules()
                    .getOffset(Instant.now())
                    .equals(ZoneOffset.ofHours(offset)))
            .sorted()
            .collect(Collectors.toList());
}
Run Code Online (Sandbox Code Playgroud)

然后我从我的数据库中检索与我的区域列表具有相同 zoneId 的相应记录。我在我的服务中使用了这样的方法:

public List<Product> getByOffset(int offset) {
    final List<String> zones = getTimeZonesByZoneOffset(offset);
    final List<Product> products = productRepository.findAllByZone(zones);
    return getProductList(products);
}
Run Code Online (Sandbox Code Playgroud)

我想在单元测试中测试这两种方法。但我不确定我应该如何设置测试机制。是否有任何单元测试示例?

小智 6

您的方法中有两个静态方法调用,而不是在不同的时间点返回不同的值,我建议在这两种情况下使用依赖注入来返回模拟结果。

  1. ZoneRules.getOffset(Instant)根据测试是在夏令时还是任何其他时区转换期间运行,返回不同的值。您可以通过测试固定时间来解决此问题。在标准代码和单元测试中添加依赖项Clock并将其注入。Clock.systemUTC()Clock.fixed(Instant, ZoneId)
  1. ZoneId.getAvailableZoneIds()ZoneId添加更多s时可以返回更多值。虽然ZoneRulesProvider抽象类提供了可用的区域 ID ,但没有简单的方法来禁用标准ZoneIds 并注入您自己的。如果你想通过依赖注入来解决这个问题,那么你必须创建自己的服务来返回可用的ZoneIds。

虽然ZoneRulesinZoneId可以随时间改变(例如,如果政府机构停止夏令时),它们在过去的时间是固定的,因此只要将现有的ZoneIdsInstant.now()模拟到过去的时间就没有问题.

示例代码:

import org.assertj.core.api.Assertions;
import org.junit.Test;

import java.time.Clock;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ProductProvider {

    private final Clock clock;
    private final ZoneIdProvider zoneIdProvider;

    public ProductProvider(Clock clock, ZoneIdProvider zoneIdProvider) {
        this.clock = clock;
        this.zoneIdProvider = zoneIdProvider;
    }

    private List<String> getTimeZonesByZoneOffset(final int offset) {
        return zoneIdProvider.getAvailableZoneIds()
                .stream()
                .filter(zoneId -> ZoneId.of(zoneId)
                        .getRules()
                        .getOffset(clock.instant())
                        .equals(ZoneOffset.ofHours(offset)))
                .sorted()
                .collect(Collectors.toList());
    }

    public List<Product> getByOffset(int offset) {
        final List<String> zones = getTimeZonesByZoneOffset(offset);
        final List<Product> products = productRepository.findAllByZone(zones);
        return getProductList(products);
    }

    public interface ZoneIdProvider {
        Set<String> getAvailableZoneIds();
    }

    public static class ProductProviderTest {
        @Test
        public void testTimezone() {
            OffsetDateTime testTime = OffsetDateTime.of(2021, 8, 26, 11, 51, 4, 0, ZoneOffset.UTC);
            Clock clock = Clock.fixed(testTime.toInstant(), testTime.getOffset());
            ZoneIdProvider zoneIdProvider = Mockito.mock(ZoneIdProvider.class);
            Mockito.when(zoneIdProvider.getAvailableZoneIds()).thenReturn(Set.of(
                    ZoneId.of("America/Argentina/Buenos_Aires"), // UTC-3 year-round
                    ZoneId.of("America/Nuuk"), // UTC-3 as Standard Time only
                    ZoneId.of("America/Halifax"), // UTC-3 as Daylight Savings Time only
                    ZoneId.of("Europe/Paris"))); // Never UTC-3
            ProductProvider productProvider = new ProductProvider(clock, zoneIdProvider);

            Assertions.assertThat(productProvider.getByOffset(-3))
                    .isEmpty(); // Add your expected products here
        }
    }

}
Run Code Online (Sandbox Code Playgroud)