Mike Mike - 4 months ago 297
Java Question

Unit testing a class with a Java 8 Clock

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.

Answer

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 
    }
}