Require / import inside deftest

For unit testing source files I like to keep the tests inside the source file. This generally works great, if I build for production I can set load-tests to false so that nothing gets generated by the clojure.test/deftest macro.

The problem I’m running into is that some test require additional components (in his case a database testcontainer), for which additional require and import are needed. I’d like to exclude these in production builds. However, adding (require ...) and (import ...) inside deftest expressions does not seem to work or get recognized. Putting it in the (ns ...) form makes the tests work, but they break for production build because the dependencies are test profile only (not on class path).

The simple (usual) solution is of course use separate files for these tests, and exclude those paths in production builds. However I find that approach leaves me with a lot of file switching and decaying tests.

In summary; is there some way to require and import dependencies for tests only, without splitting source files? I hope I’m overlooking something simple.

(when t/*load-tests* (require '[my-stuff]))

Put this above your first deftest and you should be fine.

Thanks for the suggestions, but unfortunately this does not work. The reason for this is that the when macro does not conditionally include the body. deftest actually does that, but fails to work in this case because it adds the code to meta of a test var. Both cases break on source file compilation.


After (quite) some digging I’ve arrived at two possible solutions. The root problem is something needs to be changed at either read or compile time. I know of two features that can do that; tagged literals and macros. Both rely on conditional source code inclusion.

Tagged literals

Tagged literals like #inst transform the next form in the source code. Custom tagged literals can be added to the symbol/fn map in file data_readers.clj(c) at the root classpath. So for example:

{test clojure.core/identity}

The identity function just takes the form and returns it unmodified. The source file would look something like this:

(ns foo)

(my implementation ...)

#test
(do
  (require ...)

  (testing "my tests"
    (deftest some-test
       ...)))

For a production build a different data_readers.clj(c) file could be included that configures a different function for test, which takes an argument and wraps it in a (comment ....) for example.

Unfortunately there is no function in clojure.core that does something like that. Which means a custom function is required, which has to be (:require ...) in the source file.

Custom macro

A customized macro that conditionally includes the form actually seems to work:

(defmacro tests
  "Include when clojure.test/*load-tests* is true"
  [& body]
  (if clojure.test/*load-tests*
    `(do ~@body)
    `(comment ~@body)))

Which when used in source code is as follows:

(ns foo)

(my implementation ...)

(tests
  (require ...)

  (testing "my tests"
    (deftest some-test
       ...)))

Instead of using clojure.test/*load-tests* a different macro variant could be used depending on alias used.

Related: tools.build

It appears to be impossible to set values (like *load-tests*) or inject code around tools.build. This is because compile is invoked through a generated script somewhere in a tmp directory that does not take any parameters except for compiler options. It seems that this leaves conditionally including different macro or custom tagged literals implementations as the only working solution. That is unfortunate, because that means tests included with the with-test macro can’t be filtered out.


I hope the above makes sense. I’m surprised that there are advanced techniques (I consider custom tagged literals and macros as advanced topics) required to solve this relatively simple requirement. Any thoughts or feedback on this is appreciated.

requiring-resolve can help in some cases, although it’s probably too unwieldy for your use case:

(deftest test-requiring-resolve
  (is (= 12 (requiring-resolve 'test-deps/constant))))

deftest does conditionally include the var, not just the body, so that this works:

(deftest test-require
  (require '[test-deps :as td])
  (is (= 12 td/constant)))

However, this also gets unwieldy with a large number of tests.

We can take advantage of deftest's behavior to make a hacky conditional require without macros:

(defn load-require [])
(deftest load-require
  (require '[test-deps :as td]))
(load-require)

(deftest test-load-require
  (is (= 12 td/constant)))

This works with just a little boilerplate, but I prefer the tests macro. It would be nice if clojure.test had something equivalent.

For future visitors: tools.build v0.8.1 now supports a bindings option for compilation, allowing to set *load-tests* (or any other var) to false (or any other value) during compilation. Many thanks to Alex for including this.

1 Like