15

I have a question with regards to the question Time dependent unit tests

Let's say I build Spring application which contains service interface and its implementation

If I want to change clock in test, I would have to "pollute" production code and interface with e.g. setClock method as follows:

public interface MyService {
    void heavyBusinessLogic();
    void setClock(Clock clock);
}

@Service
public class MyServiceImpl implements MyService {

    private Clock clock = Clock.systemDefaultZone();

    @Override
    public void heavyBusinessLogic() {
        if (LocalDate.now(clock)...) {
            ... 
        }
    }

    @Override
    public void setClock(Clock clock) {
        this.clock = clock;
    }
}   

In test, I can invoke, e.g.:

service.setClock(Clock.fixed(Instant.parse("2017-02-14T01:22:00Z"), ZoneOffset.UTC));

How can I abstract away such cross-cutting concern in Spring?

I want to stick with java.time.Clock (I don't want to use Joda)

Patrik Mihalčin
  • 3,341
  • 7
  • 33
  • 68

2 Answers2

26

Personally, I would simply add the clock in the constructor...

public MyServiceImpl(Clock clock) {
  this.clock = clock;
}

...and perhaps add a nice default constructor...

public MyServiceImpl() {
  this(Clock.systemDefaultZone());
}

This way you can get the default thing via spring and create a custom clock version manually, for example in your tests.

Of course, you could also forgo the default constructor and simply add a Clock bean in your productive configuration, for example like this...

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

...which allows you to use a mocked Clock as a bean in your test configuration, automatically allowing Spring to @Autowire it via constructor injection.

Florian Schaetz
  • 10,454
  • 5
  • 32
  • 58
  • I just came across another problem. I'd like to use clock for time sensitive comparisons in entity itself and I think injecting Clock bean is not good idea. What do you suggest? – Patrik Mihalčin May 05 '17 at 07:45
  • 1
    Why do you think it isn't a good idea? I don't quite see the problem with that... – Florian Schaetz May 05 '17 at 08:12
  • It requires either aspectj load time weaving or Hibernate LoadEventListener. I thought there is a more standardized way to do it :) – Patrik Mihalčin May 05 '17 at 08:16
0

Good way how to model clock is to use ThreadLocal, especially when you need it in JPA entity, e.g.:

@Entity
public class MyEntity {
   public static final ThreadLocal<Clock> CLOCK =
            ThreadLocal.withInitial(Clock::systemDefaultZone);
}
Patrik Mihalčin
  • 3,341
  • 7
  • 33
  • 68