Reputation: 10349
I'm wondering if there's a widely used pattern or solution for stubbing outbound HTTP requests to third-parties in Clojure integration tests (a la Ruby's webmock). I'd like to be able to stub requests at a high-level (for instance, in a setup function), without having to wrap each of my tests in something like (with-fake-http [] ...)
or having to resort to dependency injection.
Would this be a good use-case for a dynamic var? I suppose I could reach into the offending namespace in the setup step and set the side-effecting function to an innocuous anonymous function. However, this feels heavy-handed and I don't like the idea of changing my application code in order to accommodate my tests. (It also isn't much better than the solution mentioned above.)
Would it make sense to swap in a test-specific ns containing fake functions? Is there a clean way to do this from within my tests?
Upvotes: 5
Views: 2034
Reputation: 40510
I was in a similar situation a while back and I couldn't find any Clojure library that filled my needs so I created my own library called Stub HTTP. Usage example:
(ns stub-http.example1
(:require [clojure.test :refer :all]
[stub-http.core :refer :all]
[cheshire.core :as json]
[clj-http.lite.client :as client]))
(deftest Example1
(with-routes!
{"/something" {:status 200 :content-type "application/json"
:body (json/generate-string {:hello "world"})}}
(let [response (client/get (str uri "/something"))
json-response (json/parse-string (:body response) true)]
(is (= "world" (:hello json-response))))))
Upvotes: 6
Reputation: 29958
You can see a good example using the ring/compojure framework:
> lein new compojure sample
> cat sample/test/sample/handler_test.clj
(ns sample.handler-test
(:require [clojure.test :refer :all]
[ring.mock.request :as mock]
[sample.handler :refer :all]))
(deftest test-app
(testing "main route"
(let [response (app (mock/request :get "/"))]
(is (= (:status response) 200))
(is (= (:body response) "Hello World"))))
(testing "not-found route"
(let [response (app (mock/request :get "/invalid"))]
(is (= (:status response) 404)))))
For outbound http calls, you may find with-redefs
useful:
(ns http)
(defn post [url]
{:body "Hello world"})
(ns app
(:require [clojure.test :refer [deftest is run-tests]]))
(deftest is-a-macro
(with-redefs [http/post (fn [url] {:body "Goodbye world"})]
(is (= {:body "Goodbye world"} (http/post "http://service.com/greet")))))
(run-tests) ;; test is passing
In this example, the original function post
returns "Hello world". In the unit test, we temporarily override post
using a stub function returning "Goodbye world".
Full documentation is at ClojureDocs.
Upvotes: 4