Mocking clojurescript code written with core.async



When I write tests for the code in clojurescript and core.async I feel little pain — with-redefs doesn’t work correctly with go-blocks. For example I have a function:

( defn get-subtitles [ sources limit ] ( go ( -> ( http/get ( get-url sources limit )) <! :body format-dates )))

And the test for it:

( deftest ^ :async test-get-subtitles ( with-redefs [ http/get ( constantly fixture )] ( go ( is ( = ( <! ( get-subtitles const/all 100 )) expected )) ( done ))))

It didn’t work, it actually tries to make http request. Ok, I can try to put with-redefs inside go-block:

( deftest ^ :async test-get-subtitles ( go ( with-redefs [ http/get ( constantly fixture )] ( is ( = ( <! ( get-subtitles const/all 100 )) expected ))) ( with-redefs [ http/get ( constantly blank-result )] ( is ( = ( <! ( get-subtitles const/addicted 100 )) []))) ( done )))

The first assertion works, but in the second assertion I have previously redefined http/get and it’s incorrect and the assertion fails — with-redefs permanently changes var when applied in the go-block.

So I’ve developed a little macro which works like with-redefs and can be used inside of go-block and with code without core.async:

( defmacro with-reset [ bindings & body ] ( let [ names ( take-nth 2 bindings ) vals ( take-nth 2 ( drop 1 bindings )) current-vals ( map # ( list 'identity % ) names ) tempnames ( map ( comp gensym name ) names ) binds ( map vector names vals ) resets ( reverse ( map vector names tempnames )) bind-value ( fn [[ k v ]] ( list 'set! k v ))] ` ( let [ ~@ ( interleave tempnames current-vals )] ( try ~@ ( map bind-value binds ) ~@ body ( finally ~@ ( map bind-value resets ))))))

And usage:

( deftest ^ :async test-get-subtitles ( go ( with-reset [ http/get ( constantly fixture )] ( is ( = ( <! ( get-subtitles const/all 100 )) expected ))) ( with-reset [ http/get ( constantly blank-result )] ( is ( = ( <! ( get-subtitles const/addicted 100 )) []))) ( done )))

I put this macro in clj-di for avoiding copying it between projects.

Tests for this macro on github.

UPD: example updated.