with-redefs doesn't redefine my function - unit-testing

I have a test:
(ns gui-proxy.handler-test
(:require [clojure.test :refer :all]
[ring.mock.request :as mock]
[gui-proxy.handler :as handler]))
(deftest test-app
(testing "not-found route"
(with-redefs-fn [handler/log-request (fn [type url] (str ""))]
(let [response (handler/app (mock/request :get "/invalid"))]
(is (= (:status response) 404))))))
and the code that are under test:
(ns gui-proxy.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[clj-http.client :as client]
[gui-proxy.db :as db]))
(defn log-request [type url]
(db/insert-request-info type url))
(defn log-error []
(log-request ":fail" "fail"))
"gui-proxy - File not found")
(defroutes app-routes
(route/not-found (log-error)))
So, basically i'd like to stop the call to the database-namespace, but i end upp in a fat database exeception stacktrace...
What is wrong?

with-redefs-fn takes a map of bindings, not a vector. Note that the examples at clojuredocs use the #' reader macro to refer to the Var, so summing up you could try
(deftest test-app
(testing "not-found route"
(with-redefs-fn {#'handler/log-request (fn [type url] (str ""))}
(let [response (handler/app (mock/request :get "/invalid"))]
(is (= (:status response) 404))))))

Take a look at with-redefs it should match your usage.
http://clojuredocs.org/clojure.core/with-redefs

Related

How do I invoke a resolved function in a Compojure view?

I have the following code as a test:
(defn test-fn [] (println "This is a test"))
(def my-test "test")
((resolve (symbol (str my-test "-fn"))))
Which runs as I would expect producing This is a test.
So I put it inside of a separate view like so:
(ns my-test.template-views
(:require
[hiccup.core :refer :all]
[hiccup.page :refer :all]
[my-test.home-views :refer :all]
[my-test.page1-views :refer :all]))
(defn template-body
[uri]
(html5 {:lang "en"}
[:body
(let [the-page (if (> (count uri) 1)
(clojure.string/replace uri #"/" "")
"home")]
((resolve (symbol (str the-page "-body")))))]))
Which gets called from Compojure like this:
(ns the-test.reporting-dashboard
(:gen-class)
(:require
[the-test.template-views :refer :all]
[compojure.core :refer [defroutes GET POST context]]
[compojure.route :as route]
[org.httpkit.server :refer [run-server]]
))
(defn wrap-request
[handler]
(fn [request]
(let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
(println (str "REQUEST: " request)))
(handler request)))
(defroutes app
(wrap-request
(GET "/" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (home-views/home-body (:uri request))}))
(wrap-request
(GET "/foo" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(route/resources "/")
(route/not-found {:status 404
:headers {"Content-Type" "text/html"}
:body "<h1>Not Found</h1>"}))
When I call https//mydomain.tld/foo I get a java.lang.NullPointerException:
user=> Sat Apr 24 17:59:16 MDT 2021 [worker-2] ERROR - GET /foo
java.lang.NullPointerException
at the-test.template_views$template_body.invokeStatic(template_views.clj:14)
at the-test.template_views$template_body.invoke(template_views.clj:12)
at the-test.my_test$fn__269541.invokeStatic(my_test.clj:49)
at the-test.my_test$fn__269541.invoke(my_test.clj:46)
at compojure.core$wrap_response$fn__269102.invoke(core.clj:158)
at compojure.core$wrap_route_middleware$fn__269086.invoke(core.clj:128)
at compojure.core$wrap_route_info$fn__269091.invoke(core.clj:137)
at compojure.core$wrap_route_matches$fn__269095.invoke(core.clj:146)
at the-test.my_test$wrap_request$fn__269531.invoke(my_test.clj:22)
at compojure.core$routing$fn__269110.invoke(core.clj:185)
at clojure.core$some.invokeStatic(core.clj:2705)
at clojure.core$some.invoke(core.clj:2696)
at compojure.core$routing.invokeStatic(core.clj:185)
at compojure.core$routing.doInvoke(core.clj:182)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invokeStatic(core.clj:669)
at clojure.core$apply.invoke(core.clj:662)
at compojure.core$routes$fn__269114.invoke(core.clj:192)
at org.httpkit.server.HttpHandler.run(RingHandler.java:117)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
What is going on that this function is no longer able to be called when in Compojure?
Not sure about the root cause, but I was able to find a similar, minimal example and a workaround.
I created a simple project with lein new app demo, then replaced the code for core.clj with:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((resolve 'capitalize) "hello")))
Running this with lein run crashes:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Syntax error (NullPointerException) compiling at (/tmp/form-init1769060018979063159.clj:1:73).
null
Full report at:
/tmp/clojure-17609112051049758700.edn
The error report shows something about an error in Compiler.java so maybe it's some lazy initialization issue or just a bug. It seems to me the var cannot be resolved at the time you are attempting to call it as a function, hence the NullPointerException.
A workaround would be to use ns-resolve instead:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((ns-resolve 'clojure.string 'capitalize) "hello")))
The above works as expected:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Hello
See if you can replace your usage of resolve in the view with ns-resolve if you know the namespace where the symbol needs to be looked-up.

Clojure, Compojure: How Can I add Ring middleware CORS to ring adapter jetty based project?

I am using ring adapter jetty server in my compojure api project. Now I want to add ring middleware CORS to my Project. How should I add and where should I add ring middleware CORS in my project?
These are my project code snippets
API
(ns clojure-dauble-business-api.core
(:require [compojure.api.sweet :refer :all]
[ring.util.http-response :refer :all]
[clojure-dauble-business-api.logic :as logic]
[clojure.tools.logging :as log]
[clojure-dauble-business-api.domain.artwork]
[cheshire.core :as json])
(:import [clojure_dauble_business_api.domain.artwork Artwork]))
(defapi app
(GET ["/hello/:id", :id #"[0-9]+"] [id :as request]
(log/info "Function begins from here" request)
(def jsonString (json/generate-string (get-in request [:headers])))
(log/info "Create - header value is " (get-in (json/parse-string jsonString true) [:accesstoken]))
(def artworkData (logic/artwork-id (->> id (re-find #"\d+") Long/parseLong)))
(def data (if (not-empty artworkData)
{:data artworkData :status 200}
{:data [] :status 201}))
(ok data)))
Method to Run the api on Jetty
(ns clojure-dauble-business-api.routes
(:require [compojure.core :refer :all]
[ring.adapter.jetty :as jetty]
[ring.middleware.cors :refer [wrap-cors]]
(clojure-dauble-business-api [core :as core]
[test :as t])))
(def app
(routes core/app t/test))
(jetty/run-jetty app {:port 3000})
Here core/app is the above API function path and t/test another API function(I have not provided the code here).

Carmine connection error during tests

I've a luminus project with some simple compojure-api routes.
I've added carmine to communicate with a redis server, using the wcar* macro (defined in services.clj) to make calls to it, and everything works fine.
Now I'm trying to add some tests, but seems that the redis connection doesn't works properly during them, because I'm receiving this error with lein test:
ERROR Carmine connection error
clojure.lang.ExceptionInfo: Carmine connection error {}
Since it's working in dev e prod environments, I think that is something related to a missing env load in the test environment, but I didn't find a way to solve it.
These are relevant parts of the code in use:
test.clj
(ns app.test.handler
(:require [clojure.test :refer :all]
[ring.mock.request :refer :all]
[app.handler :refer :all]))
(deftest test-app
(testing "redis ping"
(let [response ((app) (request :get "/api/redis-ping"))]
(is (= 200 (:status response))))))
services.clj
(ns app.routes.services
(:require [ring.util.http-response :refer :all]
[compojure.api.sweet :refer :all]
[schema.core :as s]
[app.config :refer [env]]
[clojure.tools.logging :as log]
[mount.core :refer [defstate]]
[taoensso.carmine :as car :refer (wcar)]))
(defmacro wcar* [& body] `(car/wcar
{:spec {:host (:redis-host env) :port (:redis-port env)}}
~#body))
(defapi service-routes
(context "/api" []
:tags ["myapi"]
(GET "/redis-ping" []
:return String
:summary "A redis client test."
(ok (wcar* (car/ping "hello"))))))
handler.clj
(ns app.handler
(:require [compojure.core :refer [routes wrap-routes]]
[app.routes.services :refer [service-routes]]
[compojure.route :as route]
[app.env :refer [defaults]]
[mount.core :as mount]
[app.middleware :as middleware]))
(mount/defstate init-app
:start ((or (:init defaults) identity))
:stop ((or (:stop defaults) identity)))
(def app-routes
(routes
#'service-routes
(route/not-found
"page not found")))
(defn app [] (middleware/wrap-base #'app-routes))
Profiles.clj
{:profiles/dev {:env {:redis-host "127.0.0.1" :redis-port 6381}}
:profiles/test {:env {:redis-host "127.0.0.1" :redis-port 6381}}}
Config.clj
(ns app.config
(:require [cprop.core :refer [load-config]]
[cprop.source :as source]
[mount.core :refer [args defstate]]))
(defstate env :start (load-config
:merge
[(args)
(source/from-system-props)
(source/from-env)]))
SOLUTION
Add a text fixture with the mount/start command that's executed before tests.
Add to test.clj:
(defn my-test-fixture [f]
(mount/start)
(f))
(use-fixtures :once my-test-fixture)
You are using mount to manage your application state lifecycle. I think you are not calling (mount/start) in your tests thus your app.config/env state is not initialized properly. On the other hand when you start your application (mount/start) is probably called and thus it's working correctly.

How to use http.kit server with drawbridge ring handler?

I have following server code:
(ns tweet-sentiment.server
(:require [clojure.java.io :as io]
[tweet-sentiment.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel start-less]]
[compojure.core :refer [GET POST defroutes]]
[compojure.route :refer [resources]]
[net.cgrand.enlive-html :refer [deftemplate]]
[net.cgrand.reload :refer [auto-reload]]
[ring.middleware.reload :as reload]
[ring.middleware.defaults :refer [wrap-defaults api-defaults]]
[tweet-sentiment.utils :refer [generate-response]]
[environ.core :refer [env]]
[org.httpkit.server :refer [run-server]]
[ring.middleware.edn :refer [wrap-edn-params]]
[tweet-sentiment.tweets :refer [tweets]]
[tweet-sentiment.dandelion :refer [dandelion-sentiment]]
[clojure.core.async :refer [>!! <!! put! take! pipe chan]]
[cemerick.drawbridge :as drawbridge]
[ring.middleware.basic-authentication :refer [wrap-basic-authentication]]
)
(:gen-class))
(defonce server (atom nil))
(deftemplate page (io/resource "index.html") []
[:body] (if is-dev? inject-devmode-html identity))
(defroutes routes
(resources "/")
(resources "/react" {:root "react"})
(GET "/*" req (page)))
(defn wrap-drawbridge [handler]
(fn [req]
(if (= "/repl" (:uri req))
(drawbridge/ring-handler req)
(handler req))))
(def http-handler
(if is-dev?
(-> #'routes
(wrap-defaults api-defaults)
reload/wrap-reload
wrap-edn-params
wrap-drawbridge)
(-> (wrap-defaults #'routes api-defaults)
wrap-edn-params
wrap-drawbridge)))
(defn run-web-server [& [port]]
(let [port (Integer. (or port (env :port) 10555))]
(println (format "Starting web server on port %d." port))
(reset! server (run-server http-handler {:port port :join? false}))))
(defn run-auto-reload [& [port]]
(auto-reload *ns*)
(start-figwheel)
(start-less))
(defn run [& [port]]
(when is-dev?
(run-auto-reload))
(run-web-server port))
(defn stop-server []
(when-not (nil? #server)
(#server :timeout 0)
(reset! server nil)))
(defn restart-server []
(stop-server)
(run-web-server))
(defn -main [& [port]]
(run port))
When I try to connect to drawbridge REPL as dev or production:
lein repl :connect http://localhost:10555/repl
I get following error:
ERROR - POST /repl
java.lang.IllegalArgumentException: No value supplied for key: {:remote-addr "127.0.0.1", :headers {"accept-encoding" "gzip, deflate", "connection" "close", "content-length" "48", "content-type" "application/x-www-form-urlencoded", "host" "localhost:10555", "user-agent" "Apache-HttpClient/4.3.3 (java 1.5)"}, :async-channel #<AsyncChannel /127.0.0.1:10555<->/127.0.0.1:60374>, :server-port 10555, :content-length 48, :websocket? false, :content-type "application/x-www-form-urlencoded", :character-encoding "utf8", :uri "/repl", :server-name "localhost", :query-string nil, :body #<BytesInputStream BytesInputStream[len=48]>, :scheme :http, :request-method :post}
at clojure.lang.PersistentHashMap.create(PersistentHashMap.java:77)
at cemerick.drawbridge$ring_handler.doInvoke(drawbridge.clj:49)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at tweet_sentiment.server$wrap_drawbridge$fn__21884.invoke(server.clj:36)
at org.httpkit.server.HttpHandler.run(RingHandler.java:91)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Problem is obviously in drawbridge/ring-handler function.
These are my dependencies:
[[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-3058" :scope "provided"]
[ring "1.3.2"]
[ring/ring-defaults "0.1.4"]
[compojure "1.3.2"]
[enlive "1.1.6"]
[org.omcljs/om "0.8.8"]
[om-sync "0.1.1"]
[environ "1.0.0"]
[http-kit "2.1.19"]
[fogus/ring-edn "0.3.0"]
[prismatic/om-tools "0.3.11"]
[secretary "1.2.3"]
[sablono "0.3.4"]
[twitter-api "0.7.8"]
[racehub/om-bootstrap "0.5.3"]
[cheshire "5.2.0"]
[com.cemerick/drawbridge "0.0.7"]
[ring-basic-authentication "1.0.1"]]
Did anybody get working http.kit server with drawbrige? What am I missing?
I eventually figured it out, server file should look somewhat like this
(ns tweet-sentiment.server
(:require [clojure.java.io :as io]
[tweet-sentiment.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel start-less]]
[compojure.core :refer [GET POST defroutes]]
[compojure.route :refer [resources]]
[net.cgrand.enlive-html :refer [deftemplate]]
[net.cgrand.reload :refer [auto-reload]]
[ring.middleware.reload :as reload]
[ring.middleware.defaults :refer [wrap-defaults api-defaults]]
[tweet-sentiment.utils :refer [generate-response]]
[environ.core :refer [env]]
[org.httpkit.server :refer [run-server]]
[ring.middleware.edn :refer [wrap-edn-params]]
[clojure.core.async :refer [>!! <!! put! take! pipe chan]]
[cemerick.drawbridge :as drawbridge]
[ring.middleware.basic-authentication :refer [wrap-basic-authentication]]
[ring.middleware.params :as params]
[ring.middleware.keyword-params :as keyword-params]
[ring.middleware.nested-params :as nested-params]
[ring.middleware.session :as session]
)
(:gen-class))
(defonce server (atom nil))
(deftemplate page (io/resource "index.html") []
[:body] (if is-dev? inject-devmode-html identity))
(defroutes routes
(resources "/")
(resources "/react" {:root "react"})
(GET "/*" req (page)))
(defn authenticated? [name pass]
(= [name pass] [(System/getenv "AUTH_USER") (System/getenv "AUTH_PASS")]))
(def drawbridge-handler
(-> (drawbridge/ring-handler)
(keyword-params/wrap-keyword-params)
(nested-params/wrap-nested-params)
(params/wrap-params)
(session/wrap-session)))
(defn http-handler [handler]
(-> handler
(wrap-defaults api-defaults)
wrap-edn-params))
(defn wrap-http [handler]
(fn [req]
(let [handler (if (= "/repl" (:uri req))
(wrap-basic-authentication drawbridge-handler authenticated?)
(if is-dev?
(-> handler
http-handler
reload/wrap-reload)
(-> handler
http-handler)))]
(handler req))))
(defn run-web-server [& [port]]
(let [port (Integer. (or port (env :port) 10555))]
(println (format "Starting web server on port %d." port))
(reset! server
(run-server (wrap-http #'routes) {:port port :join? false})
)))
(defn run-auto-reload [& [port]]
(auto-reload *ns*)
(start-figwheel)
(start-less))
(defn run [& [port]]
(when is-dev?
(run-auto-reload))
(run-web-server port))
(defn stop-server []
(when-not (nil? #server)
(#server :timeout 0)
(reset! server nil)))
(defn restart-server []
(stop-server)
(run-web-server))
(defn -main [& [port]]
(run port))

Compojure app not playing well with with-redefs

I'm writing a Compojure application and am using clj-webdriver to graphically test it. I'm trying to use with-redefs to mock out the function that pulls out data from persistence to just return canned values, but it's ignoring my function overwrite. I know with-redefs works in terms of vars, but it's still not working:
project.clj relevant pieces:
(defproject run-hub "0.1.0-SNAPSHOT"
:main run-hub.handler/start-server)
handler.clj:
(ns run-hub.handler
(:require [compojure.core :refer :all]
[compojure.handler :as handler]
[compojure.route :as route]
[ring.adapter.jetty :refer :all]
[run-hub.controllers.log-controller :as log-controller]))
(defroutes app-routes
(GET "/MikeDrogalis/log" [] (log-controller/mikes-log))
(route/resources "/")
(route/not-found "Not Found"))
(def app (handler/site #'app-routes))
log-controller.clj:
(ns run-hub.controllers.log-controller
(:require [run-hub.views.log :as views]
[run-hub.persistence :as persistence]))
(defn mikes-log []
(views/mikes-log (persistence/mikes-log)))
persistence.clj
(ns run-hub.persistence
(require [clj-time.core :as time]
[run-hub.models.log :as log]))
(defn mikes-log [] [])
And finally, my graphical test - which tries to override mikes-log and fails:
(fact
"It has the first date of training as August 19, 2012"
(with-redefs [persistence/mikes-log (fn [] (one-week-snippet))]
(to (local "/MikeDrogalis/log"))
(.contains (text "#training-log") "August 19, 2012"))
=> true)
Where one-week-snippet is a function that returns some sample data.
(defn start-server []
(run-jetty (var app) {:port 3000 :join? false}))
I am able to use with-redefs in a clj-webdriver test doing the following:
(defn with-server
[f]
(let [server (run-jetty #'APP {:port 0 :join? false})
port (-> server .getConnectors first .getLocalPort)]
(binding [test-port port]
(try
(println "Started jetty on port " test-port)
(f)
(finally
(.stop server))))))
(use-fixtures :once with-server)
Then the whole bunch of tests gets its own jetty and this seems to run in
such a manner that with-redefs works.