I just finished my first six weeks working with Clojure and so far I'm pretty happy with the language. I'm developing my personal blog with leiningen and PostgreSQL. I already can publish new content, upload files and I have sessions, cookies and roles, anyway I think at this point I have enough code to start to worry about the testing section, but I'm kind of stuck since looks like a lot of things are happening in the clojure's testing and spec side.
So I have this function:
(defn download
"GET /admin/uploads/download/:id"
[params]
(let [id (-> params :id)
upload (model-upload/get-upload id)
filename (:filename upload)
body (clojure.java.io/file (str "public/uploads/" filename))]
{:status 200
:body body
:headers {"Content-Type" "application/pdf"
"Content-Length" (str (.length body))
"Cache-Control" "no-cache"
"Content-Disposition" (str "attachment; filename=" filename)}}))
The function takes a map as argument and delivers a final map to be sent and processed by compojure. I come from a Rails world so the way to test this function in Rails would be to create a FactoryGirl class, create a Rspec model file with the classic:
expect(first_map).to eq(map_returned_by_function)
in it comparing what is expected, and then to run the rspec from the command line to get the green or red line.
Since yesterday I'm trying to replicate that process with Clojure using this doc:
https://www.codesai.com/2018/03/kata-generating-bingo-cards
but I think there is not yet a "standard" way to do a test including the DB (CRUD) part in Clojure. I don't even know where to put the spec files. I see Clojure libraries similar to FactoryGirl but I don't know if I should create my own data structures with spec so I'm not sure where to start, there are clojure.test.check.generators and spec generators but I don't know if they are different or if I should use only spec but not clojure.test.check. Can I run a test from the command line and not inside the REPL?
I mean: is there a document or tutorial about how to test a set of CRUD functions? I think I just need the initial HOWTO and then I could take it from there and I'll write a tutorial to newbies like me.
UPDATED:
It looks like Midje is what I'm looking for:
https://github.com/marick/Midje/wiki/A-tutorial-introduction
It's idiomatic in Clojure to push IO to the edges of your application. Instead of reading from the DB inside your download function, you pass in the data read from the DB into your download function in the param map. Then you write your tests against the pure part.
Your function would end up looking like this:
(defn download-without-db
"GET /admin/uploads/download/:id"
[params]
(let [upload (-> params :upload)
filename (:filename upload)
body (clojure.java.io/file (str "public/uploads/" filename))]
{:status 200
:body body
:headers {"Content-Type" "application/pdf"
"Content-Length" (str (.length body))
"Cache-Control" "no-cache"
"Content-Disposition" (str "attachment; filename=" filename)}}))
(defn get-upload-from-db [params]
(assoc params :upload (-> params :id model-upload/get-upload)))
(defn download [params]
(-> params
get-upload-from-db
download-without-db))
You're just looking for clojure.test. It even mentions relationships with RSpec in its doc.
This is included as part of Clojure itself, no dependencies are needed, and I'd recommend you get familiar with it first before using a non standard test framework like Midje, since it is the defacto test framework for Clojure, and the most popular one.
You would write a test as such:
(deftest download
(testing "With valid input"
(testing "it should return a header map with filename included"
(is (= first_map (unit/download {:id 1}))))))
Now, Clojure is not object oriented, so there are no objects to mock. That said, you often use Java form within Clojure, and Java provides classes and objects. If you want to mock them easily, you can use the Java mocking framework called Mockito.
In your case though, the download function does not use any Java objects. So you don't need too.
Now, if you want this to be an integration test, the test I wrote is good enough for you. If you want this to be a unit test, and I assume (model-upload/get-upload id) does some IO, you'll want to mock the model-upload/get-upload function. You can easily do this using with-redefs-fn:
(deftest download
(testing "With valid input"
(testing "it should return a header map with filename included"
(with-redefs-fn {#'model-upload/get-upload (constantly {:filename "cool.pdf"})}
(is (= first_map (unit/download {:id 1})))))))
Or you can use with-redefs:
(deftest download
(with-redefs [model-upload/get-upload (constantly {:filename "cool.pdf"})]
(testing "With valid input"
(testing "it should return a header map with filename included"
(is (= first_map (unit/download {:id 1})))))))
Here are some online resources for you:
http://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html
http://clojuredocs.org/
http://clojure-doc.org/
https://www.braveclojure.com/
https://pragprog.com/book/dswdcloj2/web-development-with-clojure-second-edition
and the following is an example of how I like to structure general tests (not CRUD, though):
https://github.com/cloojure/tupelo/blob/master/test/tst/tupelo/misc.cljc
Related
Created project using lein new compojure project-name and have the server referencing (wrap-defaults project-name site-defaults).
Inside my handler I am calling controllers and passing the params to those controllers. Project structure:
handler.clj ;; ring accesses def app
controllers/app.clj ;; where route params are sent
example: (GET "/signup" {params :params} (controller-app/signup params))
So the issue I am having is I cannot figure out how to get or set cookies from the controllers file. Things I have tried:
passing cookie as a param: {cookies :cookies}. I was able to view the default cookie but could not set any data.
Use cookie-response and cookie-request. Same problem of not being able to add to the cookie.
Using the :cookie in every route possible and getting nothing back.
Any help would be much appreciated. There is not much documentation on this so unfortunately the problem has taken a fair amount of time.
Finally was able to solve it by brute force. It's no joke when people say the documentation for Clojure is pretty sparse. A couple of notes on ring:
Sessions are not what you think of when you hear sessions. It's just a signed cookie and a poorly documented one at that. Just ignore it. The few tutorials I did find constantly misspoke and used "sessions" when they meant "signed cookie". The documentation even uses cookies and sessions interchangeably. Why? I have no idea as they are completely separate methods of storing data. Sessions are a server side in memory store and cookies are a client side browser store.
:headers are needed otherwise the cookie will just download into an empty text file. Took forever to find out why this was the case.
:path has to go into the cookie body otherwise the cookie will only persist for the page where the cookie was set. You would think :path would go after :cookies and above :body. It makes no sense to me why the :path would be included in the hashmap along with the value. Again, no documentation for how or why so this took forever as well.
Now onto how to do this:
Here you pass the cookie to the controller from inside your handler. Cookies are available by default if you used "lein new compojure app-name" to create your app. I had to read the source code to figure this out.
default namespace (app-name/handler.clj) -
(ns some-namespace.handler
[compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]
[app-name.controllers.home :as home-controller))
Your app routes (app-name/handler.clj) -
(defroutes app-routes
(GET "/" {cookies :cookie} (home-controller/home cookies)))
Here is how you set the cookie itself (app-name/controllers/home.clj).
(ns app-name.controllers.home)
(defn home [cookies]
{:headers {"Content-Type" "Set-Cookie"},
:cookies {"cookie-name" {:value "cookie-value", :path "/"}},
:body "setting a cookie"})
Bottom of handler.clj using default wraps for specified routes (app-name/handler.clj)
(def app
(wrap-defaults app-routes site-defaults ))
This was a very simple problem that turned out to be far more complex. It took me 3 days to figure all of the above out. I am new to Clojure/Ring/Compojure but this has been the worst experience I have had yet programming.
It's really a problem of just enough abstraction to become dangerous (so basically nothing is obvious). Libraries like Ring REALLY need to be better documented and over explained if wider adoption is wanted.
Anyways, I hope this helps someone.
I personally spent quite some time to making cookie reading work due to my poor understanding of destructuring in compojure.
After wrapping yout application with the wrap-cookies handler, you need to destructure cookies variable "the clojure way" and not "the compojure way" since the cookies map is not in the request params map.
Example that doesn't work :
(GET "/read-cookie" [cookies]
(let [name (get-in cookies ["name" :value] "")]
(if (empty? name)
(layout/common [:h1 "I don't read a cookie"])
(layout/common [:h1 (str "I read a cookie : " name)]))))
Example that works :
(GET "/read-cookie" {:keys [cookies]} ;; or {cookies :cookies}
(let [name (get-in cookies ["name" :value] "")]
(if (empty? name)
(layout/common [:h1 "I don't read a cookie"])
(layout/common [:h1 (str "I read a cookie : " name)]))))
Hope this helps,
Use wrap-cookie from ring this will add cookie key in your request map, more details are here, then with compjure you have access to the req map, and you can use it.
https://github.com/ring-clojure/ring/wiki/Cookies
NOTE: I resolved my issue. However, it took a number of incremental changes. If you happen upon this page, feel free to checkout my github below to see how I made this application work.
I am using http-kit to post a request to btc-china. I want to use their trading api. I am able to do this just fine with python, but for some reason I keep getting 401s with clojure and http-kit. I've posted a snippit of code below which may show that I am not using http-kit correctly. In addition to that, here is a github for my full code if you wish to look at that: https://github.com/gilmaso/btc-trading
Here are the btc-china api docs: http://btcchina.org/api-trade-documentation-en
(def options {:timeout 2000 ; ms
:query-params (sorted-map :tonce tonce
:accesskey access-key
:requestmethod request-method
:id tonce
:method method
:params "")
:headers {"Authorization" auth-string
"Json-Rpc-Tonce" tonce}})
(client/post (str "https://" base-url) options
(fn [{:keys [status headers body error]}] ;; asynchronous handle response
(if error
(println "Failed, exception is " error)
(println "Async HTTP GET: " status))))
quoting from the example on the bttchina site:
# The order of params is critical for calculating a correct hash
clojure hash maps are unordered, and you cannot use a clojure hash map literal to provide the input if order is significant
I had very similar problem with bitstamp api. The solution was to replace :query-params with :form-params. Then the parameters are sent in the body. I noticed that in your api you are manually sending then in the body. It looks like using :form-params might help in your case as well.
I am trying to write Webdriver checks using Clojure. If I was using an object oriented language, I would use the Page Object Pattern. I think modeling a page as an object makes sense, I could create some java classes for the page objects and all would be well.
I want to know if there are any alternatives to the page object pattern using a functional style that maintain the same level of clarity.
A page (especially a RESTful one), can be thought of as a function from request to render (and, if you want to take the next step, the render exposes some set of new requests).
The translation from sending a request to page to applying a function to arguments is simple, and also quite comprehensive.
If you are providing a complex webapp, try taking a functional view of requests. GET can retrieve data, but should not modify server side state, use PUT to create a resource, use POST for mutation.
Once you write your controllers in this way, you can do quite a bit of testing without webdrivers. It should mostly suffice to provide a mockup of the request input to the controller, and verify some properties of the rendered result (for GET) or the storage state (for POST AND PUT).
I have even found it useful to break off request parsing and rendering into separate functions, in order to simplify the testing of the data processing that should happen in the middle, ie.:
(defn parse-home
[request]
(let [user-id (-> request :params :session :id)
account (get-user user-id)]
{:user-id user-id
:account account}))
(defn do-home
[user-id account]
(let [messages (get-messages account)
feed (generate-feed user-id)]
(update-user user-id :last-visited (java.util.Date.))
{:messages messages
:feed feed}))
(defn render-home
[request messages feed account]
(let [messages (mapv summarize (filter important messages))
feed (sort-by :priority feed)
template (-> request :page :template)]
(render template (assoc request :data {:messages messages :feed feed :account account}))))
(defn home
[request]
(let [{:keys [user-id account]} (parse-home request)
{:keys [messages feed]} (do-home user-id account)
body (render-home request messages feed account)]
{:status 200
:content-type "text/html"
:body body}))
Each element of the home page logic can be verified by providing one of these functions with the right input, and verifying some properties of the output. There is no need to mock up state or simulate page interaction unless you are also using clojurescript on the front end (and even in that case the logic you want to verify can be abstracted from the interface of the browser to avoid the need for replay testing).
I would like to write out an EDN data file from Clojure as tagged literals. Although the clojure.edn API contains read and read-string, there are no writers. I'm familiar with the issue reported here. Based on that, it's my understanding that the pr and pr-str functions are what are meant to be used today.
I wanted to check with the StackOverflow community to see if something like the following would be considered the "correct" way to write out an EDN file:
(spit "friends.edn" (apply str
(interpose "\n\n"
[(pr-str (symbol "#address-book/person") {:name "Janet Wood"})
(pr-str (symbol "#address-book/person") {:name "Jack Tripper"})
(pr-str (symbol "#address-book/person") {:name "Chrissy Snow"})])))
If you are using EDN in production, how do you write out an EDN file? Similar to the above? Are there any issues I need to look out for?
Update
The Clojure Cookbook entry, "Emitting Records as EDN Values" contains a more thorough explanation of this issue and ways to handle it that result in valid EDN tags.
you should not need to generate the tags manually. If you use any of the clojure type definition mechanisms they will be created by the printer. defrecord is particularly convenient for this.
(ns address-book)
(defrecord person [name])
(def people [(person. "Janet Wood")
(person. "Jack Tripper")
(person. "Chrissy Snow")])
address-book> (pr-str people)
"[#address_book.person{:name \"Janet Wood\"}
#address_book.person{:name \"Jack Tripper\"}
#address_book.person{:name \"Chrissy Snow\"}]"
if you want them formatted more nicely you can combine with-out-str and clojure.pprint/pprint. Using Clojure types to create the tags also gives you reading of those tags for free.
address-book> (read-string (pr-str people))
[#address_book.person{:name "Janet Wood"}
#address_book.person{:name "Jack Tripper"}
#address_book.person{:name "Chrissy Snow"}]
address-book> (def read-people (read-string (pr-str people)))
#'address-book/read-people
address-book> (type (first read-people))
address_book.person
The only downside I see is that you lose some control over the way the tags look if you have -'s in your namespace because java classes can't contain these so they get converted to underscores.
I'm developing a web application with Clojure, currently with Ring, Moustache, Sandbar and Hiccup. I have a resource named job, and a route to show a particular step in a multi-step form for a particular job defined like this (other routes omitted for simplicity):
(def web-app
(moustache/app
;; matches things like "/job/32/foo/bar"
:get [["job" id & step]
(fn [req] (web.controllers.job/show-job id step))]))
In the view my controller renders, there are links to other steps within the same job. At the moment, these urls are constructed by hand, e.g. (str "/job/" id step). I don't like that hard-coded "/job/" part of the url, because it repeats what I defined in the moustache route; if I change the route I need to change my controller, which is a tighter coupling than I care for.
I know that Rails' routing system has methods to generate urls from parameters, and I wish I had similar functionality, i.e. I wish I had a function url-for that I could call like this:
(url-for :job 32 "foo" "bar")
; => "/job/32/foo/bar"
Is there a Clojure web framework that makes this easy? If not, what are your thoughts on how this could be implemented?
Noir provides something similar. It's even called url-for.
The example function you have mentioned could be implemented as below. But I am not sure if this is exactly what you are looking for.
(defn url-for [& rest]
(reduce
#(str %1 "/" %2) "" (map #(if (keyword? %1) (name %1) (str %1)) rest)))