使用 Clock.fixed() 模拟 LocalDate.now(clock)

Posted

技术标签:

【中文标题】使用 Clock.fixed() 模拟 LocalDate.now(clock)【英文标题】:Mock LocalDate.now(clock) using Clock.fixed() 【发布时间】:2020-10-30 04:22:30 【问题描述】:

当我设置特定日期时,我正在努力测试我的端点。

我不想使用 PowerMock 来模拟静态方法,而是决定更改我的服务的实现并以更容易测试的方式使用 LocalDate.now(Clock clock) 实现。

我添加到我的 SpringBootApplication 类中:

@Bean
public Clock clock() 
    return Clock.systemDefaultZone();

并将其自动连接到我的服务

@Autowired
private Clock clock;

并在我的实现中使用它:

LocalDateTime localDate = LocalDateTime.now(clock);

在测试方面我嘲笑了时钟

private final static LocalDate WEEKEND = LocalDate.of(2020, 07, 05);

@Mock
private Clock clock;
private Clock fixedClock;

并使用它:

MockitoAnnotations.initMocks(this);

//tell your tests to return the specified LOCAL_DATE when calling LocalDate.now(clock)
fixedClock = Clock.fixed(WEEKEND.atTime(9, 5).toInstant(ZoneOffset.UTC), ZoneId.of("CET"));
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();

ResponseEntity<String> response = restTemplate.postForEntity(base.toString(), request, String.class);

当我调试它时,fixedClock 具有我预期的值FixedClock[2020-07-05T09:05:00Z,CET]。相反,如果我在服务实现上设置断点,localDate 变量的值是 2020-07-09 - .now()

我的问题是:为什么localDate 变量没有fixedClock 变量的值?

非常感谢您的宝贵时间!

后期编辑:

这里是Service的构造函数:

@Autowired
  public SavingAccountService(
      SavingAccountRepository savingAccountRepository, UserRepository userRepository, Clock clock) 
    this.savingAccountRepository = savingAccountRepository;
    this.userRepository = userRepository;
    this.clock = clock;
  

我的 TestClass 上的注释:

RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, classes = ChallengeApplication.class)
@ActiveProfiles("test")
public class SavingAccountTest 

    @Mock
    private Clock clock;
    private Clock fixedClock;

    @InjectMocks
    private SavingAccountService savingAccountService;

    @Autowired
    private TestRestTemplate restTemplate;
    private URL base;

    @LocalServerPort
    int port;

我还想提一下,在我的测试中,我调用的是控制器而不是服务。

private final SavingAccountService savingAccountService;   

public SavingAccountRestController(SavingAccountService savingAccountService) 
    this.savingAccountService = savingAccountService;
   

@Override   
@PostMapping   
public ResponseEntity<?> newSavingAccount(@RequestBody SavingAccount savingAccount) 
     EntityModel<SavingAccount> newSavingAccount = savingAccountService.newSavingAccount(savingAccount);
     return new ResponseEntity<>(newSavingAccount, HttpStatus.CREATED);

【问题讨论】:

很可能,您以错误的方式注入您的模拟。向我们展示:服务的构造函数、测试类上的注释以及如何在测试中实例化服务(注释或手动实例化?)。最重要的是:我会尝试在这种情况下消除模拟时钟。 感谢您的回答,@Lesiak!我在第一篇文章中添加了请求信息。 【参考方案1】:

问题

您在使用注入模拟的测试中创建了 SavingAccountService。

@InjectMocks
private SavingAccountService savingAccountService;

问题在于这不是您的控制器使用的服务。 Spring boot test 创建应用程序上下文中定义的 bean,自动装配它们,并愉快地忽略 test 中定义的服务的存在。

解决方案

你必须让 Spring boot 知道固定时间的 Clock bean

选项 1:模拟豆

你定义

@MockBean
private Clock clock;
private Clock fixedClock;

你应该很高兴。

我仍然觉得这个方法很复杂,我想将固定时钟作为 bean 传递给 Spring Boot 上下文,而不是创建模拟。

选项 2:指定用于加载 ApplicationContext 的组件类。

在您的测试目录中创建一个新的配置类

@Configuration
public class FakeClockConfig 

    private final static LocalDate WEEKEND = LocalDate.of(2020, 07, 05);

    @Bean
    public Clock clock() 
        return Clock.fixed(WEEKEND.atTime(9, 5).toInstant(ZoneOffset.UTC), ZoneId.of("CET"));
    

让 Spring Boot 测试知道这个额外的配置

@SpringBootTest(webEnvironment = RANDOM_PORT, 
                classes = ChallengeApplication.class, FakeClockConfig.class)

我觉得这种方法更可取,您已经自己指定了一个组件类。

恒定时钟将取代您原来的时钟

选项 3:@TestConfiguration

见Spring boot – @TestConfiguration

@TestConfiguration 是 @Configuration 的特殊形式,可用于为测试定义额外的 bean 或自定义。

在 Spring Boot 中,任何配置在带有 @TestConfiguration 注释的***类中的 bean 都不会通过组件扫描被拾取。我们必须向包含测试用例的类显式注册@TestConfiguration 类。

有两种方法可以为测试添加这个额外的测试配置:

1.1。 @Import 注解

1.2。静态嵌套类

让我们采用后一种方法:

@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
public class ProjetRepositoryTest 

    private static final LocalDate WEEKEND = LocalDate.of(2020, 07, 05);

    @TestConfiguration
    static class FakeClockConfig 

        @Bean
        public Clock clock() 
            return Clock.fixed(WEEKEND.atTime(9, 5).toInstant(ZoneOffset.UTC), ZoneId.of("CET"));
        
    

请注意,此方法会创建额外的 bean,因此我需要允许 bean 覆盖。

见Spring-Boot 2.1.x and overriding bean definition

其他说明

您似乎正在使用 TestRestTemplate 进行后端测试。 您可能更喜欢使用 MockMvc。

见Difference between MockMvc and RestTemplate in integration tests

【讨论】:

非常感谢您的回答!对于第一种方法,它仍然使用 Clock.now()。我会尝试第二种方法。 第二种方法有效,但问题是。要在测试配置类上创建 Bean,我需要从 SpringBootApplication 类中删除 bean,然后项目不再启动 Parameter 2 of constructor in SavingAccountService required a bean of type 'Clock' that could not be found。如果我尝试将 @Configuration 注释添加到 SpringBootClass 那么项目可以运行但测试不能再运行,因为Invalid bean definition with name 'clock' defined in com.ingtech.challenge.MockClockConfig: Cannot register bean definition 我在当前项目中使用方法 2。我在 src 树中有一个单独的 ClockConfig,在测试树中有一个 FakeClockConfig。因此,时钟可用于应用程序和测试 我刚刚在 src 树中创建了一个 @Configuration ClockConfig ,内容为 @Bean public Clock clock() return Clock.systemDefaultZone(); 并从 SpringBootApplication 中删除了 bean,但错误仍然存​​在。 你是如何发现你的配置的?手动还是默认?如果默认,你是否将它放在包含应用程序类的包的子包中?

以上是关于使用 Clock.fixed() 模拟 LocalDate.now(clock)的主要内容,如果未能解决你的问题,请参考以下文章

UCF Local Programming Contest 2012(Practice)

安卓模拟器可访问电脑ip配置

如何使用命令行(CMD)冷启动模拟器

用 Jest 模拟 Lerna 范围模块

xcode8 模拟器编译webrtc找不到openssl

Firestore/Firebase 模拟器未运行