Unit-testing time/timing dependent components

I’m often faced with developing components that are depending on time or timing, one way or the other.

An example can be a query that returns all “active” rows, given current date/time and where the rows have effective and expiry columns. Or components that report different state depending on time T, T+n, T+m etc.

Unit-testing components like these can be a nuisance, unless “current time” is part of the interface in the first place, and with some sleep() and stuff in the second situation. Not ideal.

Unit tests should always run with static pre-conditions. Using “current time of day” is not an option. You might catch errors that occurs on Jan 1st 2013, but you cannot reproduce it Jan 2nd… Also setting up unit-tests is often more than 50% of the unit-test itself; doing it so it correctly can cope with dynamic dates is not trivial.

If by example you have some items in a database, where you have created a JPA-query that returns active items, something along the line of:

SELECT i FROM Item i WHERE 
    (i.effective IS NULL OR :now >= i.effective) 
AND (i.expiry IS NULL OR :now < i.expiry)

Then a typical findActiveItems() operation would look like:

public List<Item> findActiveItems() {
	TypedQuery<Item> q = em.createNamedQuery("findActiveItems", Item.class);
	q.setParameter("now", Calendar.getInstance(), TemporalType.TIMESTAMP);
 
	return q.getResultList();
}

This is not trivial to test with static pre-conditions. If the “equals” part of the effective test is to be tested, the setup must create an Item row where effective is equal to the time-of-day when the findActiveItems() method is unit-tested for this particular case. Not at all easy.

One solution would be to add the time-of-day as parameter to the findActiveItems() operation, but that would pollute the api and just move the problem.

Another issue is the “time dependent” status. Say an application creates an Item, where the state of the Item changes over time, e.g.

<5s GREEN
<30s YELLOW
>30s RED

Operations could be

  • Item create(Item)
  • Status getStatus(Item)

Adding “current time-of-day” as parameter would pollute the api. A “created” timestamp might not have business value and would not be part of the “Item” as seen by clients.

Operations could be implemented as below:

public Item create(Item item) {
	item.setCreated(Calendar.getInstance());
	em.persist(item);
	em.flush();
	return item;
}
 
public Status getStatus(Item item) {
	final long tdiff = System.currentTimeMillis() - item.getCreated().getTimeInMillis();
	if (tdiff < 0) {
		throw new IllegalStateException();
	} else if (tdiff < 5000L) {
		return Status.GREEN;
	} else if (tdiff < 30000L) {
		return Status.YELLOW;
	} else {
		return Status.RED;
	}
}

Again, these operations are difficult to test. Thorough testing of the RED status would take at least a minute using e.g. sleep().

Also, we’ve now seen 2 different ways of getting the “current time-of-day”: System.currentTimeMillis() and Calendar.getInstance(). The latter probably calls the first. And often when Date’s are needed, they are just instantiated as new Date() (again calling System.currentTimeMillis() under the covers).

But generally this is bad practice, having 3 different ways of accomplishing the same task and calling static methods on classes without your control to obtain “business critical” information.

Let me introduce the DateTimeProvider:

  • It isolates the calls to the 3 most common ways of obtaining “current time-of-day” into one place.
  • Being an interface, you can make any implementation you like.
  • Used properly, can help you unit-testing time/timing dependent components.

The code is simple and straight-forward (KISS):

import java.util.Calendar;
import java.util.Date;
 
/**
 * Interface defining methods for obtaining "current time"
 */
public interface DateTimeProvider {
	/**
	 * Returns current time as Calendar
	 * 
	 * @see Calendar#getInstance()
	 * @return
	 */
	Calendar currentTimeCalendar();
	/**
	 * Returns current time in milliseconds
	 * 
	 * @see System#currentTimeMillis()
	 * @return
	 */
	long currentTimeMilliseconds();
	/**
	 * Returns current time as Date
	 * 
	 * @see Date
	 * @return
	 */
	Date currentTimeDate();
 
	/**
	 * Default DateTime provider using standard jdk methods
	 */
	DateTimeProvider DEFAULT = new DateTimeProvider() {
		@Override
		public Calendar currentTimeCalendar() {
			return Calendar.getInstance();
		}
 
		@Override
		public long currentTimeMilliseconds() {
			return System.currentTimeMillis();
		}
 
		@Override
		public Date currentTimeDate() {
			return new Date();
		}
	};
}

Then assume we have an ItemService api implemented as an EJB (ItemServiceEjb), with the DateTimeProvider the implementation could be as follows:

@Stateless
public class ItemServiceEjb implements ItemService {
	private static final long T_GREEN = 5000L;
	private static final long T_YELLOW = 30000L;
 
	@PersistenceContext
	private EntityManager em;
 
	private DateTimeProvider dateTimeProvider = DateTimeProvider.DEFAULT;
 
	public ItemServiceEjb() {
	}
 
	@Override
	public List findActiveItems() {
		TypedQuery q = em.createNamedQuery(Item.QRY_FIND_ACTIVE, Item.class);
		q.setParameter("now", dateTimeProvider.currentTimeCalendar(), TemporalType.TIMESTAMP);
 
		return q.getResultList();
	}
 
	@Override
	public Item create(Item item) {
		item.setCreated(dateTimeProvider.currentTimeCalendar());
		em.persist(item);
		em.flush();
		return item;
	}
 
	@Override
	public Status getStatus(Item item) {
		final long tdiff = dateTimeProvider.currentTimeMilliseconds() - item.getCreated().getTimeInMillis();
		if (tdiff < 0) {
			throw new IllegalStateException("Negative Item age: " + tdiff + ", item.id=" + item.getId());
		} else if (tdiff < T_GREEN) {
			return Status.GREEN;
		} else if (tdiff < T_YELLOW) {
			return Status.YELLOW;
		} else {
			return Status.RED;
		}
	}
 
	void setEm(EntityManager em) {
		this.em = em;
	}
 
	void setDateTimeProvider(DateTimeProvider dateTimeProvider) {
		this.dateTimeProvider = dateTimeProvider;
	}
}

Unit testing the infrastructure would then be something along the lines of:

public class ItemServiceEjbTest {
	// class setup stuff and instance helpers...
	@Before
	public void setUp() throws Exception {
		//...setup test database...
		// setup test DateTimeProvider
		dateTimeProvider = new DateTimeProviderTestImpl(0);
		// emulate ejb functionality
		ItemServiceEjb ejb = new ItemServiceEjb();
		ejb.setDateTimeProvider(dateTimeProvider);
		ejb.setEm(em);
		// create service proxy...
		ejbProxy = ...;
	}
 
	@Test
	public void testFindActiveItems_20121231() {
		// set "current time" to 2012-12-31 00:00:00.0
		dateTimeProvider.set(Timestamp.valueOf("2012-12-31 00:00:00.0").getTime());
 
		List items = ejbProxy.findActiveItems();
 
		assertNotNull(items);
		assertEquals(2, items.size());
		assertEquals("03 Active until Jan 2nd", items.get(0).getItemText());
		assertEquals("06 Allways active", items.get(1).getItemText());
	}
 
	@Test
	public void testCreateItem() {
		Item item = new Item();
		item.setItemText("99 create Test");
 
		item = ejbProxy.create(item);
 
		assertEquals(dateTimeProvider.currentTimeCalendar(), item.getCreated());
 
		Map<String, Object> row = DbTestHelper.selectRow(Item.TABLE_NAME, "ID = ?", true, item.getId());
 
		Timestamp ts = new Timestamp(dateTimeProvider.currentTimeMilliseconds());
		assertEquals(ts, row.get("CREATED"));
		assertEquals("99 create Test", row.get("ITEM_TEXT"));
	}
 
	@Test(expected=IllegalStateException.class)
	public void testGetStatus_minus() {
		Item item = new Item();
		item.setItemText("99 getStatus Test -1");
 
		item = ejbProxy.create(item);
 
		assertEquals(dateTimeProvider.currentTimeCalendar(), item.getCreated());
 
		dateTimeProvider.add(-1);
		// should throw ISE
		ejbProxy.getStatus(item);
	}
 
	@Test
	public void testGetStatus_0() {
		Item item = new Item();
		item.setItemText("90 getStatus Test 0");
 
		item = ejbProxy.create(item);
 
		assertEquals(dateTimeProvider.currentTimeCalendar(), item.getCreated());		
		assertEquals(Status.GREEN, ejbProxy.getStatus(item));
	}
 
	@Test
	public void testGetStatus_5000() {
		Item item = new Item();
		item.setItemText("92 getStatus Test 5000");
 
		item = ejbProxy.create(item);
 
		assertEquals(dateTimeProvider.currentTimeCalendar(), item.getCreated());
 
		dateTimeProvider.add(5000L);
 
		assertEquals(Status.YELLOW, ejbProxy.getStatus(item));
	}
 
	@Test
	public void testGetStatus_30000() {
		Item item = new Item();
		item.setItemText("94 getStatus Test 30000");
 
		item = ejbProxy.create(item);
 
		assertEquals(dateTimeProvider.currentTimeCalendar(), item.getCreated());		
 
		dateTimeProvider.add(30000L);
 
		assertEquals(Status.RED, ejbProxy.getStatus(item));
	}
}

The DateTimeProvider interface (and default implementation) isolates all relevant date/time queries to a single interface. If you are diligent and careful to ensure all date/time queries are done on a DateTimeProvider instance, you can always inject any reasonable implementation to help unit-testing all kinds of interesting timing scenarios.

The attached eclipse project (depends on Glassfish 3.1.2 and Derby 10.8.2.2+) demonstrates an entire implementation. It also demonstrates how to use an in-memory Apache Derby instance to do unit-testing of database infrastructure, and how to – with simple out-of-the-box means – mock ejb 3.0+ beans.

About Jesper Udby

I'm a freelance computer Geek living in Denmark with my wife and 3 kids. I've done professional software development since 1994 and JAVA development since 1998.
This entry was posted in Java and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *