I have a function that I would like to test.
(defn -main
[& args]
(let [port (Integer/parseInt (or (first args) "8080"))]
(run-jetty
app
{:port port})))
I need to mock away run-jetty for obvious reasons, so I use with-redefs to verify that the correct args are passed. This works great.
(it "should overide the port with first arg"
(with-redefs [run-jetty (fn [handler opts] [handler opts])]
(should= 8081 (jetty-port (underTest/-main "8081")))))
I'd like to extract the with-redefs call since I use it in a few more tests, so I tried this:
(defn mock-jetty
[test]
(with-redefs [run-jetty (fn [handler args] [handler args])] test))
(describe "The app"
(it "should default to port 8080"
(mock-jetty
(should= 8080 (jetty-port (underTest/-main))))))
This test calls the real run-jetty function and starts a server. Why does this happen?
EDIT
Changing extraction to a macro:
(defmacro mock-jetty
[someTest]
`(with-redefs [run-jetty (fn [handler args] [handler args])] ~someTest))
(describe "The app"
(it "should default to port 8080"
(mock-jetty
(should= 8080 (jetty-port (underTest/-main))))))
throws this exception:
Can't use qualified name as parameter: boost-bin.handler-test/handler
I can guess, that it is connected with the fact that with-redefs is a macro. So when you call it straight in code, it executes test body in context of redefs, while when you move it out to the function, the body (should= 8080 (jetty-port (underTest/-main))) is being executed before being passed to macro (as it always happens to functions' params)
To make it work, you could rewrite mock-jetty as a macro:
(defmacro mock-jetty
[test]
`(with-redefs [run-jetty (fn [handler# args#] [handler# args#])] ~test))
I'm pretty sure it would help
Related
I am trying to get the namespace associated with the code actually running.
I created a template project using 'lein new app test':
(ns test.core
(:gen-class))
(defn -main
[& args]
(println (ns-name *ns*)))
When using the repl, *ns* evaluates to test.core:
echo "(-main)" | lein repl
nREPL server started on port 37435 on host 127.0.0.1 - nrepl://127.0.0.1:37435
REPL-y 0.4.4, nREPL 0.7.0
Clojure 1.10.1
OpenJDK 64-Bit Server VM 1.8.0_265-b01
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
test.core=> (-main)
test.core
nil
test.core=> Bye for now!
But when using lein run, I get user namespace:
lein run
user
Why such a behavior ?
How can I get the namespace of a piece of code like in the repl ?
A few things to note:
*ns* is bound to the namespace wherein the expression is evaluated
user is the default namespace
when you call (test.core/-main) from the user namespace, *ns* is bound to user, not test.core.
If you would like to print the namespace in which -main was defined, you can use a top-level expression:
(ns test.core)
(def current-ns *ns*)
(defn -main [& args]
(println (ns-name current-ns)))
or use var metadata:
(ns test.core)
(defn -main [& args]
(println (ns-name (:ns (meta #'-main)))))
I'm using compojure for a basic web app, I have this code in core.clj:
(defroutes routes
(GET "/" [] (layout/application "Home" (contents/index)))
(route/resources "/"))
(def application (handler/site routes))
(defn -main []
(let [port (Integer/parseInt (or (System/getenv "PORT") "8090"))]
(jetty/run-jetty application {:port port :join? false})))
When I access the 0.0.0.0:8090 everything is loading ok, but I keep seeing this error:
java.lang.NullPointerException: Response map is nil
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:100)
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91)
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:95)
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91)
at ring.adapter.jetty$proxy_handler$fn__337.invoke(jetty.clj:27)
at ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle(Unknown Source)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:503)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
at java.base/java.lang.Thread.run(Thread.java:844)
Any idea what's going on?
#Svante is almost certainly correct. An easy way to verify is to use the spyx function from the Tupelo library:
(ns demo.core
(:use tupelo.core))
(defroutes routes
(GET "/" [] (spyx (layout/application "Home" (contents/index))))
(route/resources "/"))
which will print something like:
(layout/application "Home" (contents/index))) => nil
when run. spyx ("spy explicit") prints the expression you give it, an arrow, and the expression value. spy, spyx, spy-pretty, etc also return the value printed (unlike println which always returns nil) so you can insert a spy printout anywhere without disrupting the processing chain. Thus, you don't need to write something like:
(defroutes routes
(GET "/" [] (let [tmp-1 (layout/application "Home" (contents/index))]
(println "layout/application result => " tmp-1)
tmp-1)))
(route/resources "/"))
in order to get a debug message printed. In order to spy & friends, add this to the :dependencies in your project.clj:
[tupelo "0.9.138"]
Update
Hmmmm.... Not sure what could be the problem. I made a simple demo app from lein new compojure demo-compojure with the following:
(ns demo-compojure.handler
(:use tupelo.core)
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defn index []
(spy :index--result "Hello World"))
(defroutes app-routes
(GET "/" [] (spyx (index)))
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
and results:
~/expr/demo-compojure > lein ring server
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Started server on port 3000
:index--result => "Hello World"
(index) => "Hello World"
So that is working. Also, int? is a basic Clojure function, so that is puzzling. Perhaps make a clean demo project like the above and take it from there?
I am learning clojure, and I've hit a snag while trying to refactor my web app to make it more functional and less reliant on global state.
The way you make a simple auto-reloading server with ring framework is like this:
(defn handler [req]
{:status 200
:body "<h1>Hello World</h1>"
:headers {}})
(defn -main []
(jetty/run-jetty (wrap-reload #'handler)
{:port 12345}))
(source: https://practicalli.github.io/clojure-webapps/middleware-in-ring/wrap-reload.html)
So handler is a global function. It is given as a var reference to wrap-reload. On each request, wrap-reload will reload the entire namespace, and then re-resolve handler reference to a potentially different function. Or at least this is my understanding.
This is all good in this simple example. The trouble starts when I replace this hello world handler with my actual handler, which has all sorts of state bound into it (eg. database connection). This state is initialized once inside -main, and then injected into the routing stack. Something like this:
(defn init-state [args dev]
(let
[db (nth args 1 "jdbc:postgresql://localhost/mydb")
routes (make-routes dev)
app (make-app-stack routes db)
{:port (Integer. (nth args 0 3000))
:route routes
:dev dev
:db db
:app app})
(defn start [state]
(println "Running in " (if (:dev state) "DEVELOPMENT" "PRODUCTION") " mode")
(model/create-tables (:db state))
(jetty/run-jetty (:app state) {:port (:port state)}))
(defn -main [& args]
(start (init-state args false)))
make-routes generates the router based on compojure library, and make-app-stack then wraps this router into a bunch of middlewares, which will inject global state (like DB string) for the use by handlers.
So how do I add wrap-reload to this setup? #'app macro won't work on local let "variable" (or whatever it's called). Do I need to expose my app as a global variable? Or can I re-generate the entire closure on each request.
My instincts tell me to avoid having global initialization code in the module body and keep all code in the main, but maybe I am overthinking. Should I just type my init-state code as globals and call it a day, or is there a better way?
I came up with a solution I can live with.
(ns webdev.core
(:require [ring.middleware.reload :as ring-reload]))
; Defeat private defn
(def reloader #'ring-reload/reloader)
; Other stuff...
(defn load-settings [args dev]
{:port (Integer. (nth args 0 3000))
:db (nth args 1 "jdbc:postgresql://localhost/webdev")
:dev dev})
(defn make-reloading-app [settings]
(let [reload! (reloader ["src"] true)]
(fn [request]
(reload!)
((make-app settings) request))))
(defn start [settings]
(let
[app
(if (:dev settings)
(make-reloading-app settings)
(make-app settings))]
(println "Running in " (if (:dev settings) "DEVELOPMENT" "PRODUCTION") " mode")
(model/create-tables (:db settings))
(jetty/run-jetty app {:port (:port settings)})))
(defn -main [& args]
(start (load-settings args false)))
Full code is available here: https://github.com/panta82/clojure-webdev/blob/master/src/webdev/core.clj
Instead of using wrap-reload directly, I use the underlying private reload function. I have to recreate the routing stack in every request, but it seems to work fine in dev. No global state needed :)
I am having a little trouble starting my app.
Here is my core.clj
(ns myapp.core
(:require [yada.yada :as yada :refer [resource as-resource]]
[yada.resources.file-resource :refer [new-directory-resource]]
[aero.core :refer [read-config]]
[web.view :as view]
[web.routes :as routes]
[clojure.java.io :as io]
[aero.core :refer [read-config]]
[com.stuartsierra.component :as component]
[clojure.java.jdbc :as jdbc]
[clojure.tools.namespace.repl :refer (refresh)]
[ring.adapter.jetty :as jetty]
[environ.core :refer [env]]))
(defrecord Listener [listener]
component/Lifecycle
(start [component]
(assoc component :listener (yada/listener
["/"
[(view/view-route)
routes/route-handler
["public/" (new-directory-resource (io/file "target/cljsbuild/public") {})]
[true (as-resource nil)]]] )))
(stop [component]
(when-let [close (-> component :listener :close)]
(close))
(assoc component :listener nil)))
(defn new-system []
(component/system-map
:listener (map->Listener {})
))
(def system nil)
(defn init []
(alter-var-root #'system
(constantly (new-system))))
(defn start []
(alter-var-root #'system component/start))
(defn stop []
(alter-var-root #'system
(fn [s] (when s (component/stop s)))))
(defn go []
(init)
(start))
(defn reset []
(stop)
(refresh :after 'web.core/go))
(defn -main
[& [port]]
(let [port (Integer. (or port (env :port) 3300))]
(jetty/run-jetty (component/start (new-system)) {:port port :join? false})))
I am testing out Stuart Sierra's library, component.
I can start the app if I do lein repl and (go) but I am trying to start my app by running lein run (to see what the app is like if I deployed it in production). When I do lein run in the browser I get the error
HTTP ERROR: 500
Problem accessing /view. Reason:
com.stuartsierra.component.SystemMap cannot be cast to clojure.lang.IFn
I am confused because I don't know why the system-map (in new-system) is the error. I'm also not sure what the error means so I don't know how to fix it
Could someone please help. Thanks
Your -main function calls jetty/run-jetty function first argument of which must be a Ring handler - function which accepts request map and produces response map. You're passing a system instead which leads to the exception. Exception means that jetty adapter tries to call passed system as a function, but can't, because system is actually a record and doesn't implement function interface IFn.
I'm not that familiar with yada, but it looks like yada/listener starts the (Aleph) server, so there's no need to explicitly call the jetty adapter. Your main should look something like this:
(defn -main [& [port]]
(component/start (new-system)))
Port (or any other config) could be passed as an argument to the new-system and then forwarded to components requiring it (in your case port should be passed down to the Listener and then to yada/listener call in start implementation).
I have this code to get data from sumo logic and other services.
core.clj has this, which parses the arguments and routes it to the right function in route.clj
(def cli-options
[
["-a" "--app APPNAME" "set app. app can be:
sumologic or jira"]
["-?" "--help"]
])
(defn -main
[& args]
(let [{:keys [options summary errors arguments]} (parse-opts args cli-options)]
(cond
(:app options) (route/to (:app options) options arguments)
:else (print_usage summary))))
route.clj has this:
(defn to
[app options arguments]
(case app
"jira" (jira/respond options arguments)
"sumologic" (sumo/respond)))
And then sumo.clj has this. there are other functions, of course, but showing just the relevant parts.
(defn get-env-var
[var]
(let [result (System/getenv var)]
(if (nil? result)
(throw (Exception. (str "Environment variable: " var " not set. Aborting")))
result)))
(def access_key
(let [user (get-env-var "SUMO_ID")
pass (get-env-var "SUMO_KEY")]
[user pass]))
(defn respond
[]
(let [{:keys [status body error] :as response} (http/get endpoint rest-options)]
(if error
(println error)
(print-response body))))
When I run the program using leiningen as lein run -- -? or even just lein run, I get this error, even though I haven't explicitly called the sumologic function. What am I doing wrong and what are things that I can do differently?
Caused by: java.lang.Exception: Environment variable: SUMO_KEY not set. Aborting
at clarion.sumo$get_env_var.invoke(sumo.clj:14)
at clarion.sumo$fn__3765.invoke(sumo.clj:19)
at clojure.lang.AFn.applyToHelper(AFn.java:152)
at clojure.lang.AFn.applyTo(AFn.java:144)
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3553)
You have def'd access_key so it is being evaluated when you load the application. You probably want to make it a function instead.