Home

Awesome

stubadub

A small stubbing library for Clojure and ClojureScript.

To quote Kris Jenkins in his Which Programming Languages Are Functional? blog post:

Seen through the lens of side-effects, mocks are a flag that your code is impure, and in the functional programmer's eye, proof that something is wrong.

So yeah, you should treat stubbing your code much like stubbing your toe: It's painful and you should strive to avoid it.

Sometimes tho, the code you're testing needs to do some side-effecting. Or maybe all the code you're maintaining isn't a dreamy pure function landscape.

In that case, feel free to use stubadub.

Install

Add [stubadub "2.0.0"] to [:profiles :dev :dependencies] in your project.clj.

Breaking change in 2.0

The old :returns-by-args has been rechristened :return-fn. The former took only maps, while the latter can be any function. Its use with maps is unchanged, apart from the name.

Usage

(ns example-test
  (:require [stubadub.core :refer [with-stub calls-to]]))

with-stub takes a function symbol and replaces it in the scope of its body. Use calls-to to return the list of calls to the stub.

Like this:

(with-stub spit
  (spit "test1.txt" "not written to disk")
  (spit "test2.txt" "not written to disk either")
  (calls-to spit))

;; => (("test1.txt" "not written to disk")
;;     ("test2.txt" "not written to disk either"))

You can specify a return value for the stub with :returns:

(with-stub slurp :returns "not read from disk"
  (slurp "test3.txt"))

;; => "not read from disk"

Also, you can supply a function to produce the desired return value with :return-fn. The supplied function will be called with a vector containing the arguments of the stubbed function:

(with-stub slurp :return-fn (fn [[file _ _]] (str file " not read from disk"))
  [(slurp "test4.txt" :x :y)
   (slurp "test5.txt" :y :z)])

;; => ["test4.txt not read from disk" "test5.txt not read from disk"]

Since the arguments to your function are supplied in a vector, and Clojure maps indeed are functions themselves, you can conveniently supply a map of arguments to return value:

(with-stub slurp :return-fn {["test4.txt" :x :y] "not read from disk"
                             ["test5.txt" :y :z] "not read from disk either"}
  [(slurp "test4.txt" :x :y)
   (slurp "test5.txt" :y :z)])

;; => ["not read from disk" "not read from disk either"]

And you can nest several stubs, at which point you're probably not a happy person:

(with-stub spit
  (with-stub slurp :returns "not read from disk either"
    (spit "test4.txt" (slurp "test5.txt"))
    (calls-to spit)))

;; => (("test4.txt" "not read from disk either"))

Usage from ClojureScript

Replace

(:require [stubadub.core :refer [with-stub calls-to]])

with

(:require [stubadub.core :refer [calls-to] :refer-macros [with-stub])

A word of warning

Since stubadub uses with-redefs under the hood, your stubs are not thread-local. This means that parallell tests are out. Open an issue if this is a blocker for you, and we can look at making a set of thread-local functions.

Contributors

Thanks!

Contribute

I mainly consider this library complete, but if you have a wonderful addition to the library, please don't let that stop you. :)

Remember to add tests for your feature or fix tho, or I'll certainly break it later.

Running the tests

Run tests with

lein test

Run tests automatically on changes with

lein test-refresh

Make sure to run the tests for ClojureScript as well:

lein doo node

You'll have to have the node binary installed.

License

Copyright © 2016 Magnar Sveen

Distributed under the Eclipse Public License, the same as Clojure.