Tuesday, May 29, 2012

Clojure: Freezing Time in expectations

Don't do this, this blog post is out-dated. You want this: http://blog.jayfields.com/2012/11/clojure-freezing-time-added-to.html

The current version of expectations (1.4.3) contains support for freezing time within an expectations scenario. I already put this information out in a previous blog entry, and I'm going to use the same examples here.

The following code is a test I was working on at work:
(scenario
 (handle-fill (build PartialFill))
 (expect {:px 10 :size 33 :time 1335758400000} (first @fills)))
The test builds a PartialFill domain object, and passes it to handle-fill. The handle-fill fn converts the domain object to a map and conj's the new map onto the fills vector (which is an atom). The above test would fail due to the time not being frozen, and the traditional way to deal with this issue was to change the test to use (in ..) and ignore the :time key-value pair.

The following code would have resulted in a passing test:
(scenario
 (handle-fill (build PartialFill))
 (expect {:px 10 :size 33} (in (first @fills))))
The above code is fine, as long as you're not interested in verifying the time; however, things get a lot uglier if you do want to verify time. The following code freezes (joda) time, allowing for time verification:
(scenario
 (DateTimeUtils/setCurrentMillisFixed 100)
 (handle-fill (build PartialFill))
 (expect {:px 10 :size 33 :time 100} (first @fills))
 (DateTimeUtils/setCurrentMillisSystem))
While the above code does result in a passing test, it would cause unexpected issues if expect ever failed. In expectations a failure throws an exception (to terminate scenario execution) and the time reset would never occur. Even if that wasn't the case, the time related code takes away from the actual focus of the test.

Therefore, expectations has been modified to take a :freeze-time parameter as part of a scenario definition. The :freeze-time parameter can be a string or a boolean - when specifying a string, anything parse-able by Joda will do; if you specify a boolean time will simply be stopped at the moment of execution.

With :freeze-time available we can rewrite the above test in the following way:
(scenario
 :freeze-time "2012-4-30"
 (handle-fill (build PartialFill))
 (expect {:px 10 :size 33 :time 1335758400000} (first @fills)))
The resulting code is easier to work with, while still allowing verification of time.

I believe the domain related code does a better job of demonstrating the point; however, the following examples are available if you'd like something simplified.
(scenario
  :freeze-time true
  (expect (DateTime.) (do (Thread/sleep 10) (DateTime.))))

(scenario
  :freeze-time "2012-4-30TZ"
  (expect (.getMillis (DateTime.)) 1335744000000))
Post a Comment