4

What would be the best way to something like this where a timestamp is updated automatically before making the mocked call?

Here is some dummy code of what I am trying to test:

public class ThingWithATimestamp {
    public Long timestamp;
    public String name;

    public ThingWithATimestamp(String name) {
        this.name = name;
    }
}

public class TheClassThatDoesStuff {
    private ThingConnector connector;

    public TheClassThatDoesStuff(ThingConnector connector) {
        this.connector = connector;
    }

    public void updateTheThing(MyThingWithATimestamp thing) {
        thing.timestamp = currentTimestamp();
        connector.update(thing);            
    }
}

Here is what I want to test:

public class TheClassThatDoesStuffTests {
    @Test
    public void canUpdateTheThing() {
        ThingConnector connector = mock(ThingConnector.class);
        TheClassThatDoesStuff doer = new ThisClassThatDoesStuff(connector);

        doer.updateTheThing(new ThingWithATimestamp("the name"));

        verify(connector, times(1)).update(SomeMatcherThatICantFigureOut);
    }

I know this code is pretty dumbed down but I think it accurately portrays what I am trying to verify. I basically need a matcher to fill in the test to verify that the timestamp is within X of the current time so I know it got updated correctly and that connector.update was called with the proper timestamp for the object.

River
  • 8,585
  • 14
  • 54
  • 67
mockobject
  • 1,797
  • 16
  • 26
  • Why do you have this timestamp? What logic is involved with it? Checking timestamp in interval looks tricky for me - you have to select quite small interval to check accuracy, but also it should be quite wide to cut false alerts. For me it sign that something too complex in design or shouldn't be tested. – Eugen Martynov Jun 14 '12 at 22:46
  • We have the timestamp because our actual class is a save and we want to always set "now" on the server for the last update time. If our "connector" was something talking to a SQL db I would actually just choose to do the timestamp in SQL but our connector talks to a service that has no functionality to do the timestamp. – mockobject Jun 14 '12 at 22:54
  • Wouldn't it be enough to check that timestamp changed or later than before? – Eugen Martynov Jun 14 '12 at 23:03
  • Yeah, that would probably be good enough for what I am doing. – mockobject Jun 14 '12 at 23:08

3 Answers3

9

I find the most robust way to deal with time-critical code is to wrap up all of your time-critical functions in their own class. I usually call it TimeHelper. So this class might look like the following.

import java.util.Date;
public class TimeHelper{
    public long currentTimeMillis(){
        return System.currentTimeMillis();
    }
    public Date makeDate(){
        return new Date();
    }
}

and it might have more methods of the same type. Now, any class that uses such functions should have (at least) two constructors - the normal one that you'll use in your application, plus a package-private one in which a TimeHelper is a parameter. This TimeHelper needs to be stored away for later use.

public class ClassThatDoesStuff {
    private ThingConnector connector;
    private TimeHelper timeHelper;

    public ClassThatDoesStuff(ThingConnector connector) {
        this(connector, new TimeHelper());
    }

    ClassThatDoesStuff(ThingConnector connector, TimeHelper timeHelper) {
        this.connector = connector;
        this.timeHelper = timeHelper;
    } 
}

Now, within your class, instead of writing System.currentTimeMillis(), write timeHelper.currentTimeMillis(). This will, of course, have exactly the same effect; except now, your class has magically become much more testable.

When you test your class, make a mock of TimeHelper. Configure this mock (using Mockito's when and thenReturn, or alternatively doReturn) to return any time values you like - whatever you need for your test. You can even return multiple values here, if you're going to have multiple calls to currentTimeMillis() in the course of the test.

Now use the second constructor to make the object that you're going to test, and pass in the mock. This gives you perfect control of what time values will be used in the test; and you can make your assertions or verifications assert that precisely the right value has been used.

public class ClassThatDoesStuffTest{
    @Mock private TimeHelper mockTime;
    @Mock private ThingConnector mockConnector;
    private ClassThatDoesStuff toTest;

    @Test
    public void doesSomething(){
        // Arrange
        initMocks(this);
        when(mockTime.currentTimeMillis()).thenReturn(1000L, 2000L, 5000L);
        toTest = new ClassThatDoesStuff(mockConnector, mockTime);

        // Act
        toTest.doSomething();

        // Assert
        // ... ???
    }
}        

If you do this, you know that your test will always work, and never be dependent on the time slicing policies of your operating system. You also have the power to verify the exact values of your timestamps, rather than asserting that they fall within some approximate interval.

Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110
  • Duh, yeah, this is so obvious, can't believe I hadn't thought to do something like this. I am actually going to go with this solution due to the timing reasons you have mentioned. – mockobject Jun 15 '12 at 14:28
  • As a side note is it more appropriate to mark this as the accepted answer or the other? The other more accurately answers the original question but this is the actual solution to the problem. Maybe this is more a comment to post on meta or something. – mockobject Jun 15 '12 at 14:30
  • I've accepted this answer based on several things I have read on meta. It appears the consensus is to accept the answer you use even if it isn't the most direct answer to the question. – mockobject Jun 15 '12 at 14:40
  • 1
    Even better than TimeHelper, you could use jodatime for holding your timestamps. Jodatime contains a utility class for setting the "current time" that it uses, exactly for this purpose. – Kkkev Jun 15 '12 at 16:47
2

First:

class TimestampInInterval extends ArgumentMatcher< ThingWithATimestamp > {
      public boolean matches(Object arg) {
          ThingWithATimestamp thing = (ThingWithATimestamp) arg;
          long delta = thing.timestamp - System.currentTimeMillis();
          return delta < INTERVAL;
      }
   }

Second:

verify(connector, times(1)).update(argThat(new TimestampInInterval());
Eugen Martynov
  • 19,888
  • 10
  • 61
  • 114
  • Ok, and then if I also needed to validate say the name I could just use the and(). I think I was caught up in matching both in the same matcher. Cool, this should work for me, thanks a bunch. – mockobject Jun 14 '12 at 23:10
  • 1
    Until you come to run your tests on a different platform, which dishes out timeslices differently. I've been burnt by using this approach - writing tests that work fine on my windows PC, then fail when they run in the Jenkins environment on a Linux box. If you're testing time critical stuff like this, you really want to use a time helper. I'll post a detailed answer if I have time. – Dawood ibn Kareem Jun 15 '12 at 07:57
  • OK, done. Also, please don't write `times(1)` in your calls to `verify`. The default is `times(1)`, so to explicitly write it in just clutters up your test. – Dawood ibn Kareem Jun 15 '12 at 08:23
  • 1
    I would also write a function that reads nicely and that will return the `TimestampInInterval`. You could write then `verify(connector, times(1)).update(argThat(timestampIsInInterval());`. – bric3 Jun 15 '12 at 09:36
0

Although the accepted answer is great, there is no need to create a custom TimeHelper class. You can instead just use the java.time.Clock as a dependency in your TheClassThatDoesStuff class as documented in this answer.

java.time.Clock has lots of static factory methods to create clocks with fixed point in time, custom cadences/ticks, and offset from the current moment. These factory methods can be useful during tests.

Dr. Pro
  • 39
  • 1
  • 7