Unit testing a class with a Java 8 Clock
Asked Answered
E

8

86

Java 8 introduced java.time.Clock which can be used as an argument to many other java.time objects, allowing you to inject a real or fake clock into them. For example, I know you can create a Clock.fixed() and then call Instant.now(clock) and it will return the fixed Instant you provided. This sounds perfect for unit testing!

However, I'm having trouble figuring out how best to use this. I have a class, similar to the following:

public class MyClass {
    private Clock clock = Clock.systemUTC();

    public void method1() {
        Instant now = Instant.now(clock);
        // Do something with 'now'
    }
}

Now, I want to unit test this code. I need to be able to set clock to produce fixed times so that I can test method() at different times. Clearly, I could use reflection to set the clock member to specific values, but it would be nice if I didn't have to resort to reflection. I could create a public setClock() method, but that feels wrong. I don't want to add a Clock argument to the method because the real code shouldn't be concerned with passing in a clock.

What is the best approach for handling this? This is new code so I could reorganize the class.

Edit: To clarify, I need to be able to construct a single MyClass object but be able to have that one object see two different clock values (as if it were a regular system clock ticking along). As such, I cannot pass a fixed clock into the constructor.

Elevenses answered 21/11, 2014 at 17:30 Comment(2)
Now, I want to unit test this code. You should indicate what kind of behaviour you expect from MyClass. That would inform the approach to follow here.Industrial
Following up on this: I think what it basically comes down to is that you cannot really use the Clock.fixed for unit testing in the way I hoped. Normal mocking approaches will be required.Elevenses
B
72

I don't want to add a Clock argument to the method because the real code shouldn't be concerned with passing in a clock.

No... but you might want to consider it as a constructor parameter. Basically you're saying that your class needs a clock with which to work... so that's a dependency. Treat it as you would any other dependency, and inject it either in a constructor or via a method. (I personally favour constructor injection, but YMMV.)

As soon as you stop thinking of it as something you can easily construct yourself, and start thinking of it as "just another dependency" then you can use familiar techniques. (I'm assuming you're comfortable with dependency injection in general, admittedly.)

Bellbella answered 21/11, 2014 at 17:34 Comment(11)
Yes, I considered this. However, the problem is that passing a clock through the constructor lets me set it to a single fixed clock. I guess I didn't make it clear, but I need to be able to set the clock to multiple values within a single test without constructing a new object.Elevenses
@Elevenses Please update your sample to make clear what your use case is.Frizzell
@Elevenses Then either have a clock setter, mock Clock, or use reflection. What other options do you see based on your requirements?Fauch
No, those seem to be the options. I suppose I was wondering if I was missing another option that maybe was particular to Clock.Elevenses
In particular, it would have been nice if I could pass a Clock to the constructor and then set that clock to different values. Unfortunately (at least in this case), Clock is immutable.Elevenses
@Elevenses You should be able to mock the Clock your passing as an argument (e.g. using Mockito) instead of providing a fixed Clock. Like this you can return different values at different calls.Frizzell
@Mike: Or instead of a mock, you could write your own reusable fake clock, with whatever behaviour you want - such as incrementing by 1 second per call. At that point, however, you'll be dependent on the number of calls you make to the clock within your production code, which isn't ideal.Bellbella
On top of Puce's answer, you should be able to make the mock return different values per mockito.googlecode.com/svn/tags/1.8.5/javadoc/org/mockito/…Roath
Actually, why shouldn't it be an argument? If it is important for the computation, you might as well be explicit about it. No reliance on assumed state. Not saying it is always the right solution, but I wouldn't assume it's 'wrong' from the get-go either.Gynaecocracy
@Eelco: Not sure what you mean by "assumed" state here, but if the class is responsible for timing things, it makes sense for that to know how it's going to time things, rather than the caller saying how to do it.Bellbella
If that's a core responsibility of the class, sure. If it isn't, or you want to approach things a bit more functional, you could pull it outside. Just saying that imho having this as an argument isn't wrong per se unless you are going for ultimate OO purity :-) FWIW, I agree that DI is in many cases the most elegant solution.Gynaecocracy
R
67

Let me put Jon Skeet's answer and the comments into code:

class under test:

public class Foo {
    private final Clock clock;
    public Foo(Clock clock) {
        this.clock = clock;
    }

    public void someMethod() {
        Instant now = clock.instant();   // this is changed to make test easier
        System.out.println(now);   // Do something with 'now'
    }
}

unit test:

public class FooTest() {

    private Foo foo;
    private Clock mock;

    @Before
    public void setUp() {
        mock = mock(Clock.class);
        foo = new Foo(mock);
    }

    @Test
    public void ensureDifferentValuesWhenMockIsCalled() {
        Instant first = Instant.now();                  // e.g. 12:00:00
        Instant second = first.plusSeconds(1);          // 12:00:01
        Instant thirdAndAfter = second.plusSeconds(1);  // 12:00:02

        when(mock.instant()).thenReturn(first, second, thirdAndAfter);

        foo.someMethod();   // string of first
        foo.someMethod();   // string of second
        foo.someMethod();   // string of thirdAndAfter 
        foo.someMethod();   // string of thirdAndAfter 
    }
}
Roath answered 21/11, 2014 at 18:42 Comment(7)
you might want to change first, second etc to suit your needs (e.g. the time needs to be in the past), but you get the idea.Roath
In practice, the example test above will run into NPEs, because user code will most likely call LocalDateTime.now(clock) rather than clock.instant(); a NullPointerException will get thrown the moment LocalDateTime calls clock.getZone().getRules(). Instead, a real Clock object should be created with a call to Clock.fixed(Instant,ZoneId).Crucible
@Rogério my interpretation of the question is "how do i mock to return different values on consecutive calls", thus the answer. I don't know enough about java 8 time for the LocalDateTime.now() or Instant.now() part, but if static method is needed here, it may need to be wrapped into a class, because PowerMock/EasyMock does not support Stubbing consecutive calls in MockitoRoath
The problem with this approach is that it makes an assumption on how many times clock.millis() or clock.instant() have been called exactly during execution of foo.someMethod() which is not always possible or convenient. This makes me feel that java.time.Clock is not really suitable for time-based unit testing.Peshitta
As @Rogério pointed out, this works by the assumption that class under tests queries Clock.instant(). If it's changed to query Clock.millis(), that will return always 0. This is no good. There is a need mocked clock object that returns mocked time regardless of method actually called.Loeffler
@Rogério Instead of a Clock object, I would only constructor-inject a Supplier<Instant>...Industrial
Use real clock with Clock.fixed instead of mocking!Cantilever
C
31

I'm a bit late to the game here, but to add to the other answers suggesting using a Clock - this definitely works, and by using Mockito's doAnswer you can create a Clock which you can dynamically adjust as your tests progress.

Assume this class, which has been modified to take a Clock in the constructor, and reference the clock on Instant.now(clock) calls.

public class TimePrinter() {
    private final Clock clock; // init in constructor

    // ...

    public void printTheTime() {
        System.out.println(Instant.now(clock));
    }
}

Then, in your test setup:

private Instant currentTime;
private TimePrinter timePrinter;

public void setup() {
   currentTime = Instant.EPOCH; // or Instant.now() or whatever

   // create a mock clock which returns currentTime
   final Clock clock = mock(Clock.class);
   when(clock.instant()).doAnswer((invocation) -> currentTime);

   timePrinter = new TimePrinter(clock);
}

Later in your test:

@Test
public void myTest() {
    myObjectUnderTest.printTheTime(); // 1970-01-01T00:00:00Z

    // go forward in time a year
    currentTime = currentTime.plus(1, ChronoUnit.YEARS);

    myObjectUnderTest.printTheTime(); // 1971-01-01T00:00:00Z
}

You're telling Mockito to always run a function which returns the current value of currentTime whenever instant() is called. Instant.now(clock) will call clock.instant(). Now you can fast-forward, rewind, and generally time travel better than a DeLorean.

Copycat answered 14/7, 2016 at 22:36 Comment(4)
This answer is perfect.Reseta
My use case is to fast-forward some seconds in some tests to verify some time-based validation rules. This seems to be the easiest and most elegant solution to me. Perfect, thanks a lot. Another +1 for the DeLorean.Abbeyabbi
Really elegant solution :) +1 for the DeLorean reference.Carob
I'm using Mockito 1.10.19 and doAnswer does not exist. I replaced it with thenAnswerFootpoundal
P
28

Create a Mutable Clock Instead of Mocking

To start, definitely inject a Clock into your class under test, as recommended by @Jon Skeet. If your class only requires one time, then simply pass in a Clock.fixed(...) value. However, if your class behaves differently across time e.g. it does something at time A, and then does something else at time B, then note that the clocks created by Java are immutable, and thus cannot be changed by the test to return time A at one time, and then time B at another.

Mocking, as per the accepted answer, is one option, but does tightly couple the test to the implementation. For example, as one commenter points out, what if the class under test calls LocalDateTime.now(clock) or clock.millis() instead of clock.instant()?

An alternate approach that is a bit more explicit, easier to understand, and may be more robust than a mock, is to create a real implementation of Clock that is mutable, so that the test can inject it and modify it as necessary. This is not difficult to implement, or here are several ready-made implementations:

And here is how one might use something like this in a test:

MutableClock c = new MutableClock(Instant.EPOCH, ZoneId.systemDefault());
ClassUnderTest classUnderTest = new ClassUnderTest(c);

classUnderTest.doSomething()
assertTrue(...)

c.instant(Instant.EPOCH.plusSeconds(60))

classUnderTest.doSomething()
assertTrue(...)
Poon answered 7/5, 2019 at 15:47 Comment(4)
From the documented implementation requirements of the Clock abstract class: "This abstract class must be implemented with care to ensure other classes operate correctly. All implementations that can be instantiated must be final, immutable and thread-safe."Oceangoing
@Oceangoing Given that the purpose of the implementation is not a general purpose Clock but as a limited-purpose time source for a unit test, I would feel no guilt in violating that requirement.Poon
ThreeTen-extra is also available at Maven CentralFootpoundal
Mocking is fine but limited; The test has special knowledge how the Clock is used (see many comments to this effect). A windable/mutable Clock is a general purpose test tool and the ThreeTen version is written by the author of java.time.Clock !Wolgast
L
5

As others have noted, you need to mock it somehow - however it's pretty easy to roll your own:

    class UnitTestClock extends Clock {
        Instant instant;
        public void setInstant(Instant instant) {
            this.instant = instant;
        }

        @Override
        public Instant instant() {
            return instant;
        }

        @Override
        public ZoneId getZone() {
            return ZoneOffset.UTC;
        }

        @Override
        public Clock withZone(ZoneId zoneId) {
            throw new UnsupportedOperationException();
        }
    }
Laddy answered 9/12, 2019 at 12:5 Comment(2)
There is already a class for that, provided in JDK, see Clock.fixed()Poppy
@KrzysztofWolny, I think you've missed the point - this is effectively creating a mutable clock as in Raman's answer.Misbehavior
P
2

I faced the same issue and could not the existing solution that is simple and working so I ended up following code. Of course, it can be better, has configurable TZ, etc, but I needed something easy to use in tests, where I want to check how my under-test class is dealing with clock:

  1. I don't want to care what particular method of the Clock is being called so mocking is not a way to go.
  2. I wanted to have millies defined upfront

Note: this class is Groovy class, to be used in Spock tests, but it's easily translatable into Java.

class FixedTicksClock extends Clock {
    private ZoneId systemDefault = ZoneId.systemDefault()
    private long[] ticks
    private int current = 0

    FixedTicksClock(long ... ticks) {
        this.ticks = ticks
    }

    @Override
    ZoneId getZone() {
        systemDefault
    }

    @Override
    Clock withZone(final ZoneId zone) {
        systemDefault = zone
        this
    }

    @Override
    Instant instant() {
        ofEpochMilli(getNextTick())
    }

    @Override
    long millis() {
        getNextTick()
    }

    private long getNextTick() {
        if (current >= ticks.length) {
            throw new IllegalStateException('No more ticks provided')
        } else {
            ticks[current++]
        }
    }
}
Poppy answered 5/10, 2020 at 15:35 Comment(0)
R
0

Starting with Java 17, the JDK provides a somewhat built-in feature for this.

You can implement InstantSource and then use its default method withZone(ZoneId) to obtain a Clock instance that always returns the time that is provided via InstantSource.instant() and that point in time.

Example:

// This is injected as singleton. If that's not an option, you can also make its state static.
public class TestClockFactory
{
  private Instant testClockInstant = Instant.ofEpochMilli( 1000000000L );

  public void setClock( long epochMilli )
  {
    testClockInstant = Instant.ofEpochMilli( epochMilli );
  }

  public Instant getClockInstant()
  {
    return testClockInstant;
  }

  public InstantSource newInstantSource()
  {
    return new TestInstantSource();
  }

  public class TestInstantSource
      implements InstantSource
  {
    @Override
    public Instant instant()
    {
      return getClockInstant();
    }
  }
}

and then

Clock testClock = testClockFactory.newInstantSource().withZone(ZoneId.of("UTC"));

This way you can still update the current Instant for the testClock afterwards via TestClockFactory.setClock().

Roosevelt answered 3/11, 2023 at 11:31 Comment(0)
T
-1

I've created TimeFlow library which simplifies dealing with time in production code and tests. It provides Java Clock which can be used in production code and easily adjusted/changed in tests. No need for passing Clock as a dependency anymore.

Production code:

class SomeService {

    void doSomething() {
        var now = Time.instance().now(); // or var now = Instant.now(Time.instance().clock());
        // do something
    }

}

Tests:

final class Time_Scenarios {

    private static final ZoneId ZONE_ID = TimeZone.getTimeZone("Europe/Warsaw").toZoneId();
    private static final Clock
        FIXED_CLOCK = Clock.fixed(LocalDateTime.of(1983, 10, 23, 9, 15).atZone(ZONE_ID).toInstant(), ZONE_ID);

    @AfterEach
    void afterEach() {
        TestTime.testInstance().resetClock();
    }

    @Test
    void Test_time_jump_for_something() {
        // Given
        TestTime.testInstance().setClock(FIXED_CLOCK);
        var duration = Duration.of(10, ChronoUnit.MINUTES);

        // setup time dependent code under test

        // When
        TestTime.testInstance().fastForward(duration);  // jump forward 10 minutes

        // Then
        // do assertions
    }
}
Tuttifrutti answered 7/2, 2023 at 19:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.