Search code examples
unit-testingtestingclojureclojure-testing

Clojure - tests with components strategy


I am implementing an app using Stuart Sierra component. As he states in the README :

Having a coherent way to set up and tear down all the state associated with an application enables rapid development cycles without restarting the JVM. It can also make unit tests faster and more independent, since the cost of creating and starting a system is low enough that every test can create a new instance of the system.

What would be the preferred strategy here ? Something similar to JUnit oneTimeSetUp / oneTimeTearDown , or really between each test (similar to setUp / tearDown) ?

And if between each test, is there a simple way to start/stop a system for all tests (before and after) without repeating the code every time ?

Edit : sample code to show what I mean

(defn test-component-lifecycle [f]
  (println "Setting up test-system")
  (let [s (system/new-test-system)]
    (f s) ;; I cannot pass an argument here ( https://github.com/clojure/clojure/blob/master/src/clj/clojure/test.clj#L718 ), so how can I pass a system in parameters of a test ?
    (println "Stopping test-system")
    (component/stop s)))

(use-fixtures :once test-component-lifecycle)

Note : I am talking about unit-testing here.


Solution

  • You can do this in a couple different ways. The first is to use a dynamic variable:

    (def ^:dynamic *system* nil)
    
    (defn test-component-lifecycle [f]
      (binding [*system* (system/new-test-system)]
        (try
          (f)
          (finally
            (component/stop s)))))
    
    (use-fixtures :once test-component-lifecycle)
    
    (deftest example-test
      (is (= :foo (:server *system*))))
    

    The second is to use an atom, which will look almost exactly the same as the above:

    (def *system* (atom nil))
    
    (defn test-component-lifecycle [f]
      (let [s (system/new-test-system)]
        (reset! *system* s)
        (try
          (f)
          (finally
            (reset! *system* nil)
            (component/stop s)))))
    
    (use-fixtures :once test-component-lifecycle)
    
    (deftest example-test
      (is (= :foo (:server @*system*))))
    

    Otherwise, I'd suggest the system initialization in a let binding at the top of every test. That will be the most obvious and simplest to work with long-term. It can seem like a lot of boilerplate, but it's at most one line and will save you from headaches down the road.