What you have already is not far from a working functional version. I changed things around a bit to be more idiomatic to Clojure.
The following assumes that generate-sky-nodes and generate-hot-nodes each return some value (this can be done in addition to any side effects they have), i.e.:
(defn generate-sky-nodes
[]
(doseq [i (range 10)] (do-make-sky-node i))
:sky-nodes)
then, your generate-nodes is adjusted as follows:
(defn generate-nodes
[sky-blue? hot-outside? name]
(cond
(sky-blue? name) (generate-sky-nodes)
(hot-outside?) (generate-hot-nodes)))
and finally, the functional version of the tests:
(deftest when-sky-blue-then-generate-sky-nodes
(let [truthy (constantly true)
falsey (constantly false)
name nil]
(is (= (generate-nodes truthy falsey name)
:sky-nodes))
(is (= (generate-nodes truthy truthy name)
:sky-nodes))
(is (not (= (generate-nodes falsey falsey name)
:sky-nodes)))
(is (not (= (generate-nodes falsey truthy name)
:sky-nodes)))))
The general idea is that you don't test what it did, you test what it returns. Then you arrange your code such that (whenever possible) all that matters about a function call is what it returns.
An additional suggestion is to minimize the number of places where side effects happen by using generate-sky-nodes
and generate-hot-nodes
to return the side effect to be carried out:
(defn generate-sky-nodes
[]
(fn []
(doseq [i (range 10)] (do-make-sky-node i))
:sky-nodes))
and your call of generate-nodes
would look like the following:
(apply (generate-nodes blue-test hot-test name) [])
or more succinctly (though admittedly odd if you are less familiar with Clojure):
((generate-nodes blue-test hot-test name))
(mutatis mutandis in the above test code the tests will work with this version as well)