Mock LocalDate.now(clock) using Clock.fixed()

Viewed 4037

I'm struggling on testing my endpoint when I set a specific date.

I don't want to use PowerMock to mock a static method instead I decided to change the implementation of my service and use the LocalDate.now(Clock clock) implementation in the way to be easier to test it.

I added to my SpringBootApplication class:

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

and autowired it to my Service

@Autowired
private Clock clock;

and used it in my implementation as that:

LocalDateTime localDate = LocalDateTime.now(clock);

On the test side I mocked the Clock

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

@Mock
private Clock clock;
private Clock fixedClock;

and used it as that:

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

When I debuged it, the fixedClock has the value which I expected FixedClock[2020-07-05T09:05:00Z,CET]. Instead if I put a breakpoint on the service implementantion, the localDate variable has the value 2020-07-09 - the .now().

My issue is that: why the localDate variable hasn't the value of fixedClock variable?

Thank you very much for your time!

Later edit:

Here is the constructor of the Service:

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

The annotations on my 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;

I want also to mention that from my test I'm calling the Controller and not the Service.

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);
}
1 Answers

The problem

You created a SavingAccountService in your test that uses injected mocks.

@InjectMocks
private SavingAccountService savingAccountService;

The problem is that this is not the service used by your controller. Spring boot test creates the beans defined in the application context, autowires them, and happily ignores the existence of the service defined in test.

Solution

You must make Spring boot aware of the fixed-time Clock bean

Option 1: Mock bean

You define

@MockBean
private Clock clock;
private Clock fixedClock;

and you should be good to go.

I still find this method convoluted, I'd like to pass the fixed clock as a bean to Spring Boot context, instead of creating mocks.

Option 2: Specify the component classes to use for loading an ApplicationContext.

Create a new config class in your test directories

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

Let Spring Boot test know about this additional config

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

I find this method preferable, you are already specifying one component class yourself.

Constant time clock will replace your original Clock

Option 3: @TestConfiguration

See Spring boot – @TestConfiguration

@TestConfiguration is specialized form of @Configuration that can be used to define additional beans or customizations for a test.

In spring boot, any beans configured in a top-level class annotated with @TestConfiguration will not be picked up via component scanning. We must explicitly register the @TestConfiguration class with the class that contains the test cases.

There are two ways to include this additional test configuration for tests:

1.1. @Import annotation

1.2. Static nested classes

Let's go with the latter approach:

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

Note that this method creates additional beans, so I needed to allow bean overriding.

See Spring-Boot 2.1.x and overriding bean definition

Other remarks

You seem to be using TestRestTemplate for backend tests. You may prefer to use MockMvc instead.

See Difference between MockMvc and RestTemplate in integration tests

Related