I've created a new Compojure Leiningen project using lein new compojure test. Web server is run by lein repl and then
user=> (use 'ring.adapter.jetty)
user=> (run-jetty test.handler/app {:port 3000})
Routes and app handler specification is trivial:
(defroutes app-routes
(GET "/*.do" [] "Dynamic page")
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
Now, after changing anything in app-routes definition (e.g. changing "Dynamic page" text to anything else, or modifying URI matching string), i do not get the updated text/routes in the browser. But, when changing app-routes definition slightly to
(defn dynfn [] "Dynamic page fn")
(defroutes app-routes
(GET "/*.do" [] (dynfn))
(route/not-found "Not Found"))
i do get dynamic updates when changing the return value of dynfn. Also, following the advice from this article and modifying the app definition to
(def app
(wrap-defaults #'app-routes site-defaults))
(note the #' that transparently creates a var for app-routes) also helps!
Why is that so? Is there any other way one could get a truly dynamic behaviour in defroutes?
Thanks!
#'app-routes is a reader macro that expands to (var app-routes). When a var is used as if it were a function, it is dereferenced anew on each invocation, and then the value returned by that deref is called.
If you were to supply app-routes as the argument, the compiler would give the dereferenced value to wrap-defaults, and when the var is updated, the previous value is not changed, so changing the var does not change the behavior of app.
The following repl transcript might be instructive:
user=> (defn foo [] "original")
#'user/foo
user=> (defn caller [f] #(f))
#'user/caller
user=> (def call-foo-value (caller foo))
#'user/call-foo-value
user=> (call-foo-value)
"original"
user=> (def call-foo-var (caller #'foo))
#'user/call-foo-var
user=> (call-foo-var)
"original"
user=> (defn foo [] "changed")
#'user/foo
user=> (call-foo-value)
"original"
user=> (call-foo-var)
"changed"
Related
Figwheel displays the code bellow just fine. But I have to refresh the page to see any changes. What has to change for Figwheel to show changes? Is there a command to force redraw, without losing the application state?
BTW: Chrome has Disable Cache true and the CLJS icon appears when the file is saved.
(defn simple-example []
[ui/mui-theme-provider {:mui-theme (get-mui-theme
{:palette {:text-color (color :blue800)}})}
[:div
[ui/app-bar {:title "Hi all"
:icon-class-name-right "muidocs-icon-navigation-expand-more"}]
[ui/paper
[:div
[ui/tabs
[ui/tab {:label "Menu" :value "0"}
[:div "Hello world"]]]]]]]))
(defn ^:export run []
(render [simple-example]
(js/document.getElementById "app"))))
From the docs:
Setting :figwheel true or :figwheel { :on-jsload "example.core/reload-hook" } will automagically insert the figwheel client code into your application. If you supply :on-jsload the name of a function, that function will be called after new code gets reloaded.
An example reload hook plus configuration for Reagent:
(ns your-namespace.core
(:require [reagent.core :as r]))
(defn render [view]
(let [node (.getElementById js/document "app")]
(r/render-component view node)))
(defn rerender []
(let [node (.getElementById js/document "app")]
(r/unmount-component-at-node node)
(render [:div "Reloading"]))
(defn ^:export reload []
(rerender))
And then in your project.clj:
:cljsbuild {:builds {:dev {:source-paths ["src"]
:figwheel {:on-jsload "your-namespace.core/reload"}}}
/edit
Note that re-frame uses Reagent. In the case of re-frame I recommend starting with the re-frame-template. E.g.,
lein new re-frame your-project-name # options, e.g., +re-frisk +cider
This will give a default core.cljs as follows:
(defn dev-setup []
(when config/debug?
(enable-console-print!)
(println "dev mode")))
(defn mount-root []
(re-frame/clear-subscription-cache!)
(reagent/render [views/main-panel]
(.getElementById js/document "app")))
(defn ^:export init []
(re-frame/dispatch-sync [:initialize-db])
(dev-setup)
(mount-root))
The index.html has a node with id app and calls init. And the project.cljs specifies the on-jsload as follows:
:cljsbuild
{:builds
[{:id "dev"
:source-paths ["src/cljs"]
:figwheel {:on-jsload "your-project-name.core/mount-root"}
#_(...)}}
This should absolutely update the page with the component changed. If it does not do what you want I might have misunderstood your question.
Reagent needs to be notified about state changes to re-render the affected components on the screen. Your code does not yet have any inner state that can be watched in order to decide if a re-render is required.
You can store your app state in reagent atoms. When you dedreference a reagent atom in a reagent component (that is the simple-example component in your case) an event listener is set up to the state atom so that any time it changes the component will be re-rendered.
Put the following just before the definition of simple-example:
(defonce counter (reagent.core/atom 0))
(swap! counter inc)
This creates a state called counter if it does not exist yet. It also immediately increases it so any already registered components will be refreshed.
Then put a #counter deref call anywhere inside the function body of simple-example. This way the initial call of the function installs the state change listeners.
Now any time you modify the code the namespace get reloaded and thus counter atom increases triggering the re-render of your component.
I want to test my Compojure endpoints and see that the required method is called. However, with-redefs-fn is not replacing the method.
Test method-
(ns project-name.handler-test
(:require [clojure.test :refer :all]
[project-name.handler :as handler :refer [retrieve-records test-handler]]
[ring.mock.request :as mock]))
(deftest fn-handler-routes
(with-redefs-fn {#'handler/retrieve-records (fn [arg] :test)}
(let [response {:status 200 :body :test}]
(let [mock-handler (test-handler)]
(is (= response (mock-handler (mock/request :get "/records/name"))))))))
I am guessing routes are static, and thus the method fires and returns actual data instead of calling the redefined function. test-handler is a failing attempt to try to circumvent -- it fires okay, but does not use with-redefs and returns repo data.
Pertinent Source Code-
(defn retrieve-records [arg] (repo/get-records arg))
(defroutes routes
(GET "/" [] (resource-response "index.html" {:root "public"}))
(context "/records" []
(GET "/name" [] (retrieve-records :lastname)))
(resources "/"))
(defn test-handler []
(-> #'routes wrap-reload))
Related Issues
I suspect a similar underlying issue as this SO question about Midje but I am using clojure.test and not midje.
This question is different from a very similar SO question in that the Compojure route is legitimate and I want to return something in the :body, not just test the status (already tried that answer).
This question is different from an integration test question in that it is a handler unit test, though probably the same underlying issue.
Any help would be greatly appreciated.
When running this code :
(:use 'compojure.core)
(keys (ns-publics 'compojure.core))
(defroutes app-routes
(GET "/" [] "Hello World")
(route/resources "/")
(route/not-found "Not Found"))
I got this message:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: defroutes in this context, compiling:(restful_clojure\routes.clj:5:1)
but when I run:
(keys (ns-publics 'compojure.core))
it shows that macro is defined:
(defroutes PUT POST routing routes make-route let-routes DELETE ANY let-request GET HEAD PATCH context OPTIONS)
Clojure has methods require, import, refer, and use. These are for working with different namespaces.
:use is a Keyword, which can behave like a function (in your example it should return nil), but does not do what you want.
The confusion likely arises from the fact that inside the ns macro, you can 'embed' the behavior of these functions using the corresponding keywords.
For more reading on namespaces, see this link.
This code is from the default luminus template:
(deftype RenderableTemplate [template params]
Renderable
(render
[this request]
(content-type
(->>
(assoc
params
(keyword (s/replace template #".html" "-selected"))
"active"
:servlet-context
(:context request)
:user-id
(session/get :user-id)
:user
(session/get :user))
(parser/render-file (str template-path template))
response)
"text/html; charset=utf-8")))
(defn render [template & [params]]
(RenderableTemplate. template params))
And I need to test this function using clojure.test:
(defn home-page [& [user]]
(layout/render
"home.html"
{:user user}))
How will I test the above function with the value associated to the key :user?
I suggest you to read some documentation and make a reasonable effort before launching so general questions. This could be an start http://blog.jayfields.com/2010/08/clojuretest-introduction.html
After you feel a bit comfortable with clojure test you may like to move to https://github.com/marick/Midje/wiki/A-tutorial-introduction-for-Clojure.test-users
Enjoy :)
First set your routes and handlers like this:
(defn home-page [& [user]]
(layout/render
"home.html"
{:user user}))
; setting routes
(defroutes main-routes
(GET "/user/:user" [user] (home-page user)))
Then do the unit testing:
Basic unit test
(deftest home-page-test
; Check if response code is 200
(is (= 200 (:status (main-routes {:request-method :get :uri "/user/Michael"})))))
or you can also use Midje
Using Midje
(:use midje.sweet
clojure.test)
(fact "Homepage Test"
(:status (main-routes {:request-method :get :uri "/user/Michael")) => 200)
I faced the same problem.
Calling (home-page) directly will return a RenderableTemplate type which isn't useful for testing.
In your test require:
(:require [capacityplanning.layout :as layout]
[selmer.parser :as parser])
Add this function:
(def template-path "templates/")
(defn mockRender [template params]
(parser/render-file (str template-path template) params))
And in your test you can bind:
(with-redefs [layout/render mockRender]
(home-page user))
Now when home-page is called it will return the html as a string. This should be more useful to write unit tests against.
I want to create a handler function which takes two inputs. One is a parameter taken from the url /name, and second is a param from the query string /name?x=3
(def my-app (app
[page-name] (handler page-name)))
(defn handler
[{:keys [params]} page-name]
(let [x (params "x")]
(-> (page-templ page-name x) response constantly)))
The above fails because the handler is expecting 2 params, however I am only passing one.
How do I get hold of the request map, and pass it to the handler ?
The request map in the above case contains a param named x.
It is best if you could dispatch on the page name, like that:
(app
[""] (index-page)
["login"] (serve-login))
Here functions index-page and serve-login return function of one argument.
(defn index-page[]
(fn [req] ..))
req is the request that will contain all the url parameters in key/value map. To get parameter value do this:
(-> req (get :params) (get :x))
So the full solution would look something like this:
(def my-app (app
["page1-name"] (handler)))
(defn handler []
(fn [req]
(let [x (-> req :params :x)]
(-> (page-templ page-name x) response))))
EDIT: Don't forget to wrap you application into (wrap-keyword-params) and (wrap-params), here's how you can do it:
(def my-wrapped-app
(-> my-app
(wrap-keyword-params)
(wrap-params))