如何通过单元测试测试时区方法?
How to test timeZone method via Unit Test?
我使用以下采用时区偏移值的方法,例如GMT-3 和 returns 给定偏移量的时区 ID 列表。
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());
}
然后我从我的数据库中检索与我的区域列表具有相同 zoneId 的相应记录。我在我的服务中使用了类似这样的方法:
public List<Product> getByOffset(int offset) {
final List<String> zones = getTimeZonesByZoneOffset(offset);
final List<Product> products = productRepository.findAllByZone(zones);
return getProductList(products);
}
我想在单元测试中测试这两种方法。但是我不确定应该如何设置测试机制。是否有任何单元测试示例?
由于您的 getTimeZonesByZoneOffset
是私人的,因此没有简单的方法来测试它。不过您可以执行以下操作:
- 静态模拟
getTimeZonesByZoneOffest
的内部结构,以确保你会 return 得到预期的结果。
- 模拟存储库层以便return模拟结果列表。
- 断言
getByOffset
方法的 return 值,从而验证 getProductList
是否正确执行其工作。
对于静态模拟,您可能会锁定 PowerMock
或 Mockito-inline
,具体取决于您使用的 Junit
版本。
不要使用静态方法。创建组件很便宜。
为您的 getTimeZonesByZoneOffset
方法创建一个接口和实现,并创建另一个接口和实现以提供它使用的 Instant
。然后你的测试可以使用一个已知的瞬间。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
import java.util.stream.Collectors;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class UnitTest {
interface TZSource {
List<String> getTimeZonesByZoneOffset(final int offset);
}
interface NowSource {
Instant getNow();
}
static class DefaultTZSource implements TZSource {
private final UnitTest.NowSource nowSource;
public DefaultTZSource(UnitTest.NowSource nowSource) {
this.nowSource = nowSource;
}
@Override
public List<String> getTimeZonesByZoneOffset(int offset) {
return ZoneId.getAvailableZoneIds()
.stream()
.filter(zoneId -> ZoneId.of(zoneId)
.getRules()
.getOffset(nowSource.getNow())
.equals(ZoneOffset.ofHours(offset)))
.sorted()
.collect(Collectors.toList());
}
}
@Test
public void testTZSource() {
NowSource nowSource = mock(NowSource.class);
when(nowSource.getNow()).thenReturn(LocalDateTime.of(2021, 8, 26, 20, 30, 0).toInstant(ZoneOffset.ofHours(0)));
TZSource tzSource = new DefaultTZSource(nowSource);
Assertions.assertEquals(List.of(), tzSource.getTimeZonesByZoneOffset(5)); // this fails, you'll need to add the timezones to the list
}
}
类似地,将 getByOffset
放在单独的 class 中,在其构造函数中传递 TZSource
和 ProductRepository
。这两个都将在其单元测试中被模拟。
你的方法中有两个静态方法调用,在不同的时间点 return 不同的值,我建议在这两种情况下使用依赖注入来 return 模拟结果。
ZoneRules.getOffset(Instant)
returns 不同的值取决于测试是 运行 在夏令时或任何其他时区转换期间。您可以通过测试固定时间来解决此问题。添加对 Clock
的依赖,并在标准代码中使用 Clock.systemUTC()
注入它,并在单元测试中使用 Clock.fixed(Instant, ZoneId)
注入它。
当添加更多 ZoneId
时,ZoneId.getAvailableZoneIds()
可以 return 更多值。虽然可用区域 ID 由 ZoneRulesProvider
摘要 class 提供,但没有简单的方法来禁用标准 ZoneId
s 并注入您自己的。如果你想通过依赖注入来解决这个问题,那么你必须自己制作 return 可用的 ZoneId
服务。
虽然 ZoneId
中的 ZoneRules
会随时间变化(例如,如果政府机构停止使用夏令时),但它们在过去的时间里是固定的,所以它不是只要 Instant.now()
被模拟到过去的某个时间,问题 returning 现有的 ZoneId
s。
示例代码:
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
}
}
}
我使用以下采用时区偏移值的方法,例如GMT-3 和 returns 给定偏移量的时区 ID 列表。
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());
}
然后我从我的数据库中检索与我的区域列表具有相同 zoneId 的相应记录。我在我的服务中使用了类似这样的方法:
public List<Product> getByOffset(int offset) {
final List<String> zones = getTimeZonesByZoneOffset(offset);
final List<Product> products = productRepository.findAllByZone(zones);
return getProductList(products);
}
我想在单元测试中测试这两种方法。但是我不确定应该如何设置测试机制。是否有任何单元测试示例?
由于您的 getTimeZonesByZoneOffset
是私人的,因此没有简单的方法来测试它。不过您可以执行以下操作:
- 静态模拟
getTimeZonesByZoneOffest
的内部结构,以确保你会 return 得到预期的结果。 - 模拟存储库层以便return模拟结果列表。
- 断言
getByOffset
方法的 return 值,从而验证getProductList
是否正确执行其工作。
对于静态模拟,您可能会锁定 PowerMock
或 Mockito-inline
,具体取决于您使用的 Junit
版本。
不要使用静态方法。创建组件很便宜。
为您的 getTimeZonesByZoneOffset
方法创建一个接口和实现,并创建另一个接口和实现以提供它使用的 Instant
。然后你的测试可以使用一个已知的瞬间。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
import java.util.stream.Collectors;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class UnitTest {
interface TZSource {
List<String> getTimeZonesByZoneOffset(final int offset);
}
interface NowSource {
Instant getNow();
}
static class DefaultTZSource implements TZSource {
private final UnitTest.NowSource nowSource;
public DefaultTZSource(UnitTest.NowSource nowSource) {
this.nowSource = nowSource;
}
@Override
public List<String> getTimeZonesByZoneOffset(int offset) {
return ZoneId.getAvailableZoneIds()
.stream()
.filter(zoneId -> ZoneId.of(zoneId)
.getRules()
.getOffset(nowSource.getNow())
.equals(ZoneOffset.ofHours(offset)))
.sorted()
.collect(Collectors.toList());
}
}
@Test
public void testTZSource() {
NowSource nowSource = mock(NowSource.class);
when(nowSource.getNow()).thenReturn(LocalDateTime.of(2021, 8, 26, 20, 30, 0).toInstant(ZoneOffset.ofHours(0)));
TZSource tzSource = new DefaultTZSource(nowSource);
Assertions.assertEquals(List.of(), tzSource.getTimeZonesByZoneOffset(5)); // this fails, you'll need to add the timezones to the list
}
}
类似地,将 getByOffset
放在单独的 class 中,在其构造函数中传递 TZSource
和 ProductRepository
。这两个都将在其单元测试中被模拟。
你的方法中有两个静态方法调用,在不同的时间点 return 不同的值,我建议在这两种情况下使用依赖注入来 return 模拟结果。
ZoneRules.getOffset(Instant)
returns 不同的值取决于测试是 运行 在夏令时或任何其他时区转换期间。您可以通过测试固定时间来解决此问题。添加对Clock
的依赖,并在标准代码中使用Clock.systemUTC()
注入它,并在单元测试中使用Clock.fixed(Instant, ZoneId)
注入它。
-
当添加更多
ZoneId.getAvailableZoneIds()
可以 return 更多值。虽然可用区域 ID 由ZoneRulesProvider
摘要 class 提供,但没有简单的方法来禁用标准ZoneId
s 并注入您自己的。如果你想通过依赖注入来解决这个问题,那么你必须自己制作 return 可用的ZoneId
服务。
ZoneId
时,虽然 ZoneId
中的 ZoneRules
会随时间变化(例如,如果政府机构停止使用夏令时),但它们在过去的时间里是固定的,所以它不是只要 Instant.now()
被模拟到过去的某个时间,问题 returning 现有的 ZoneId
s。
示例代码:
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
}
}
}