Dependency injection and utility belt for testing for Clojure and ClojureScript.
Add the following to your project.clj
:
[clj-di "0.5.0"]
Before we start, we need to import library. In Clojure:
(ns clj-di.example
(:require [clj-di.core :refer [register! get-dep let-deps defprotocol*]]))
In ClojureScript:
(ns clj-di.example
(:require-macros [clj-di.core :refer [let-deps defprotocol*]])
(:require [clj-di.core :refer [register! get-dep]]))
For registering dependencies you can use register!
function, example:
(register! :cache (atom {})
:logger (get-logger))
You should call it only on application initialization.
For example: in ring :init
handler, in -main
.
For receiving single dependency you can use get-dep
function, example:
(get-dep :cache)
If you need more than one dependencies it's better to use let-deps
macro:
(let-deps [cache :cache
log :logger]
(log :info (:last-id @cache)))
For example, you need http client as a dependency,
first way – register http-get
, http-post
, http-put
dependencies:
(register! :http-get http/get
:http-post http/post
:http-put http/put)
(let-deps [http-get :http-get
http-post :http-post]
(http-get "some-url")
(http-post "some-url" :some-data))
But it's ugly. Or you can create http protocol and http type and use it as a dependency:
(defprotocol http
(GET [_ url])
(POST [_ url data])
(PUT [_ url data]))
(deftype http-impl
(GET [_ url] (http/get url))
(POST [_ url data] (http/post url data))
(PUT [_ url data] (http/put url data)))
(defn make-http [] (http-impl.))
(register! :http (make-http))
(let-deps [http :http]
(GET http "some-url")
(POST http "some-url" :some-data))
Less ugly, but you need to pass http
to GET
and POST
.
So for simplifying this process you can use defprotocol*
macro:
(defprotocol* http
(GET [_ url])
(POST [_ url data])
(PUT [_ url data]))
(deftype http-impl
(GET [_ url] (http/get url))
(POST [_ url data] (http/post url data))
(PUT [_ url data] (http/put url data)))
(defn make-http [] (http-impl.))
(register! :http (make-http))
(GET* "some-url")
(POST* "some-url" :some-data)
It creates function (method-name*
) for each method in which they automatically receive
dependency, which name equals to protocol name. For example, GET*
works like:
(defn GET*
[url]
(GET (get-dep :http) url))
In tests you need to import test utils. In Clojure:
(ns clj-di.example-test
(:require [clj-di.test :refer [with-fresh-dependencies with-registered]]))
In ClojureScript:
(ns clj-di.example-test
(:require-macros [clj-di.test :refer [with-fresh-dependencies with-registered with-reset]]))
For clearing dependencies before and after each test you can use with-fresh-dependencies
macro:
(use-fixtures :each (fn [f] (with-fresh-dependencies (f))))
For registering dependencies in tests you can use with-registered
macro:
(deftest test-write-to-cache
(with-registered [:cache (atom {})]
(write-to-cache :test "test")
(is (= @(get-dep :cache) {:test "test"}))))
For redefining variables you can use clojure with-redefs
,
but it works incorrectly in ClojureScript inside go
block,
so in this situation you can use with-reset
macro:
(deftest ^:async test-model-get
(go (with-reset [model/fetch (fn [& _] [:a :b :c])]
(is (= (model/get :a) :a))
(done))))
clj-di
written using cljx
, so for compiling you need to run:
lein cljx auto # watch for changes and compile
lein cljx # compile once
For running tests:
lein test
lein cljsbuild test
- 0.5 — move
with-reset
andwith-registered
toclj-di.test
namespace; - 0.4 — add
with-reset
as a working alternative of clojurewith-redefs
for ClojureScript insidego
block.