4

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?

pdoherty926
  • 9,895
  • 4
  • 37
  • 68
  • 2
    Most (all?) clojure http libraries represent requests and responses as maps so you can just construct these directly in your tests without mocking. – Lee Mar 01 '17 at 16:07
  • What's wrong with dependency injection? Are you *hard-coding urls* in your functions? Because you shouldn't do that, that's bad, mkay. – Jared Smith Mar 01 '17 at 16:53

2 Answers2

5

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))))))
Johan
  • 37,479
  • 32
  • 149
  • 237
3

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)))))

Update

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.

Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
  • Thanks for this. I've updated my question to be more specific about the types of requests I'm trying to stub. Specifically, outbound requests from my application to third-parties. – pdoherty926 Mar 01 '17 at 16:36
  • Thanks, again. I'll experiment with your suggestion and follow-up (it might be a few days). At first glance, this is fairly heavy-handed compared to a solution like webmock because I need intimate knowledge of what nested function calls will be doing. – pdoherty926 Mar 01 '17 at 16:53