9

I want to unit test some code that calls System.currentTimeMillis(). As this answer points out, a good way to do this is to replace calls to System.currentTimeMillis() with Clock.getInstance().currentTimeMillis(). Then, you can perform dependency injection on Clock.getInstance(), for example to replace it with a mock in unit testing.

So, my question is a follow-up to that. How do you configure Spring Boot to inject Clock.getInstance() at runtime?

If possible, I'd prefer to do this with annotations instead of XML.

Also, if possible, I'd like to do this in such a way that I can simply use @Mock and @InjectMocks with MockitoJUnitRunner to inject a mock clock into a unit test.

mkasberg
  • 16,022
  • 3
  • 42
  • 46
  • 1
    Maybe I'm misunderstanding; what's preventing you from configuring via ```@Configuration public class Config { @Bean public Clock { return Clock.fixed... } }``` And then `@Autowire`ing that into your desired class? – Dovmo Apr 03 '18 at 18:29
  • That might be the best answer. I was wondering if there was anything I didn't know about like `@FactoryMethod(class=Clock.class, method=getInstance())`. But I guess it's really pretty simple if you do it like you suggest. I'd upvote it if you write it as an answer. – mkasberg Apr 03 '18 at 18:45

2 Answers2

6

In your configuration class, you can do:

@Configuration
public class Config { 
    @Bean
    public Clock clock() { 
        return Clock.fixed(...);
    } 
} 

In your class you can just @Autowire it:

public class ClockUser {

private Clock clock;

    public ClockUser(Clock clock, ...) {
        this.clock = clock;
    }
}

(Notice I'm using constructor injection here, see the section entitled Constructor-based or setter-based DI of this article, Oliver Gierke's comment (i.e. head of Spring Data project), and google for more information.)

Then you can create your mock in another test @Configuration class or in your JUnit test:

@Bean
public Clock {
    Clock clock = Mockito.mock(Clock.class);
    .... ("when" rules)
    return clock;
} 
Maksim Sorokin
  • 2,334
  • 3
  • 34
  • 61
Dovmo
  • 8,121
  • 3
  • 30
  • 44
  • 5
    Since fixed means it always returns the same time, you wouldn't use it in the non-test configuration. It should be using something like systemUTC, systemDefaultZone, etc. – Nathan Hughes Mar 27 '20 at 12:46
1

It can be done without using mocks in the unit test.

In you Spring application setup simply add the following. It could either be in your @SpringBootApplication or @Configuration

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

This will make sure that you have a running clock in your production code.

For your test setup you can choose to add the same Clock bean to your test configuration. This will make the tests that does not depend on a specific time, run without modifications.

When you have a test case that needs to rely on a specific time you simply overwrite the test configuration by adding the following as an internal class in your test case.

@RunWith(SpringRunner.class)
@SpringBootTest
public class YourServiceTest {

  @Autowired
  private YourService yourService;

  ...

  @TestConfiguration
  public static class Config {
    @Bean
    public Clock clock() {
      return Clock.fixed(Instant.parse("2021-09-10T12:00:00Z"), ZoneOffset.UTC);
    }
  }
}
homaxto
  • 5,428
  • 8
  • 37
  • 53