Skip to content

Instantly share code, notes, and snippets.

@benjchristensen
Created March 6, 2012 19:53
Show Gist options
  • Save benjchristensen/1988624 to your computer and use it in GitHub Desktop.
Save benjchristensen/1988624 to your computer and use it in GitHub Desktop.
MockTimer
/**
* A mock for the java.util.Timer.schedule() method that allows manually incrementing time
* so as to have deterministic unit tests.
*/
private static class MockTimer implements ScheduledTimer {
private final ArrayList<ATask> tasks = new ArrayList<ATask>();
@Override
public synchronized void schedule(TimerTask task, int delay, int period) {
tasks.add(new ATask(task, delay, period));
}
/**
* Increment time by X. Note that incrementing by multiples of delay or period time will NOT execute multiple times.
* <p>
* You must call incrementTime multiple times each increment being larger than 'period' on subsequent calls to cause multiple executions.
* <p>
* This is because executing multiple times in a tight-loop would not achieve the correct behavior, such as batching, since it will all execute "now" not after intervals of time.
*
* @param timeInMilliseconds
*/
public synchronized void incrementTime(int timeInMilliseconds) {
for (ATask t : tasks) {
t.incrementTime(timeInMilliseconds);
}
}
private static class ATask {
final TimerTask task;
final int delay;
final int period;
// our relative time that we'll use
volatile int time = 0;
volatile int executionCount = 0;
private ATask(TimerTask task, int delay, int period) {
this.task = task;
this.delay = delay;
this.period = period;
}
public synchronized void incrementTime(int timeInMilliseconds) {
time += timeInMilliseconds;
if (task != null) {
if (executionCount == 0) {
System.out.println("ExecutionCount 0 => Time: " + time + " Delay: " + delay);
if (time >= delay) {
// first execution, we're past the delay time
executeTask();
}
} else {
System.out.println("ExecutionCount 1+ => Time: " + time + " Period: " + period);
if (time >= period) {
// subsequent executions, we're past the interval time
executeTask();
}
}
}
}
private synchronized void executeTask() {
System.out.println("Executing task ...");
task.run();
this.time = 0; // we reset time after each execution
this.executionCount++;
System.out.println("executionCount: " + executionCount);
}
}
}
private static interface ScheduledTimer {
void schedule(TimerTask collapseTask, int delay, int period);
}
@benjchristensen
Copy link
Author

I needed to mock out java.util.Timer to make some unit tests deterministic and couldn't find any quick solution (that didn't involve statically altering System.currentTimeMillis() which sounded painful) so hacked this code together.

It only handles a very limited use case, but it works for what it was designed to do.

Example of how the unit test looks while using Thread.sleep (non-deterministic):

Future<String> response1 = new ClassToTestWithTimer(counter, 1).queue();
Thread.sleep(5);
Future<String> response2 = new ClassToTestWithTimer(counter, 2).queue();
Thread.sleep(8);
// should execute here
Future<String> response3 = new ClassToTestWithTimer(counter, 3).queue();
Thread.sleep(6);
Future<String> response4 = new ClassToTestWithTimer(counter, 4).queue();
Thread.sleep(8);
// should execute here
Future<String> response5 = new ClassToTestWithTimer(counter, 5).queue();
Thread.sleep(10);

// wait for all tasks to complete
assertEquals("1", response1.get());
assertEquals("2", response2.get());
assertEquals("3", response3.get());
assertEquals("4", response4.get());
assertEquals("5", response5.get());

System.out.println("number of executions: " + counter.get());
assertEquals(3, counter.get());

Here is how the same unit test looks with Thread.sleep() replaced with MockTimer.incrementTime() which makes it deterministic.

MockTimer timer = new MockTimer();
Future<String> response1 = new ClassToTestWithTimer(timer, counter, 1).queue();
timer.incrementTime(5);
Future<String> response2 = new ClassToTestWithTimer(timer, counter, 2).queue();
timer.incrementTime(8);
// should execute here
Future<String> response3 = new ClassToTestWithTimer(timer, counter, 3).queue();
timer.incrementTime(6);
Future<String> response4 = new ClassToTestWithTimer(timer, counter, 4).queue();
timer.incrementTime(8);
// should execute here
Future<String> response5 = new ClassToTestWithTimer(timer, counter, 5).queue();
timer.incrementTime(10);

// wait for all tasks to complete
assertEquals("1", response1.get());
assertEquals("2", response2.get());
assertEquals("3", response3.get());
assertEquals("4", response4.get());
assertEquals("5", response5.get());

System.out.println("number of executions: " + counter.get());
assertEquals(3, counter.get());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment