使用 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)的主要内容,如果未能解决你的问题,请参考以下文章