Clojure: Conditionally Importing
Join the DZone community and get the full member experience.
Join For FreeI recently ran into a test that needed (org.joda.time.DateTime.)
to always return the same time - so it could easily be asserted
against. This situation is fairly common, so it makes sense to add
support to expectations.
However, I didn't want to force a joda-time dependency on everyone who
wanted to use expectations. Luckily, Clojure gives me the ability to
conditionally import dependencies.
The test looked something like the following code snippet.
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 build fn creates a PartialFill who's time is (DateTime.), which means you can either not test the time or you need to freeze the time. Joda time makes it easy to freeze time by calling DateTimeUtils/setCurrentMillisFixed, so you could write the test as:
The syntax for the freezing time is fairly straightforward, and the original test can be written as what follows (with expectations 1.4.3 & up)
The code linked here shows the conditional imports: https://github.com/jaycfields/expectations/blob/f2a8687/src/clojure/expectations/scenarios.clj#L81-L86
Even if you're not familiar with macros it should be obvious that I'm importing and using the joda-time classes conditionally. At macro expansion time I'm checking for the presence of freeze-time, and if it does exist the necessary classes are imported. If freeze-time is never found, the joda-time classes will never be imported, and no joda-time dependency will need to live within your project.
I always assumed this would be possible, but I'd never actually attempted to put this type of code together. I'm definitely happy with how the code turned out - I can freeze time in expectations if I want, but I don't force anyone to use joda.
The test looked something like the following code snippet.
(scenario (handle-fill (build PartialFill)) (expect {:px 10 :size 33 :time 1335758400000} (in (first @fills))))note: build is a fn that creates domain objects (I do a lot of Java Interop).
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 build fn creates a PartialFill who's time is (DateTime.), which means you can either not test the time or you need to freeze the time. Joda time makes it easy to freeze time by calling DateTimeUtils/setCurrentMillisFixed, so you could write the test as:
(scenario (DateTimeUtils/setCurrentMillisFixed 100) (handle-fill (build PartialFill)) (expect {:px 10 :size 33 :time 100} (in (first @fills))) (DateTimeUtils/setCurrentMillisSystem))Of course, that would cause issues if expect ever failed, as failure throws an exception and the time reset would never occur. Even if that wasn't the case, the time related code takes away from what I'm really testing - so I set out to create a declarative approach from within expectations.
The syntax for the freezing time is fairly straightforward, and the original test can be written as what follows (with expectations 1.4.3 & up)
(scenario :freeze-time "2012-4-30" (handle-fill (build PartialFill)) (expect {:px 10 :size 33 :time 1335758400000} (in (first @fills))))Writing the code to freeze time with expectations is trivial enough, but it's only relevant if you actually use joda-time. Since not everyone uses joda-time, it seemed like the best option was to make expectations import joda only if you actually use :freeze-time within your code.
The code linked here shows the conditional imports: https://github.com/jaycfields/expectations/blob/f2a8687/src/clojure/expectations/scenarios.clj#L81-L86
Even if you're not familiar with macros it should be obvious that I'm importing and using the joda-time classes conditionally. At macro expansion time I'm checking for the presence of freeze-time, and if it does exist the necessary classes are imported. If freeze-time is never found, the joda-time classes will never be imported, and no joda-time dependency will need to live within your project.
I always assumed this would be possible, but I'd never actually attempted to put this type of code together. I'm definitely happy with how the code turned out - I can freeze time in expectations if I want, but I don't force anyone to use joda.
Clojure
Published at DZone with permission of Jay Fields, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments