clojure unit-testing with-redefs - unit-testing

I have something like this:
(ns server.core
(:require [db.api :as d]))
(defrecord Server [host port instance]
(start [c]
(let [connection (d/connect (:host c) (:port c))]
(assoc c :instance connection)))
(stop [c]
;; close the connection
))
(defn new-server
[host port]
(map->Server {:host host
:port port}))
And the unit-tests code
(ns server.core_test
(:require [server.core :refer :all]
[clojure.test :refer :all]))
(deftest server-test
(testing "Calling start should populate :instance"
(with-redefs [d/connect (fn [h p] [h p])]
(let [server (start (new-server "foobar" 12313123))]
(is (-> server :instance nil? not))))))
Running the code above with boot watch test throws an error similar to:
Unable to resolve var: d/connect in this context
And then I modify the test code so it requires the db.api
(ns server.core_test
(:require [server.core :refer :all]
[clojure.test :refer :all]
[db.api :as d]))
I ran the tests again, this time d/connect still refers to db.api.
Any advice?

You need to require db.api in your test code so the var d/connect is accessible:
(ns server.core_test
(:require [server.core :refer :all]
[db.api :as d]
[clojure.test :refer :all]))
(deftest server-test
(testing "Calling start should populate :instance"
(with-redefs [d/connect (fn [h p] [h p])]
(let [server (start (new-server "foobar" 12313123))]
(is (-> server :instance nil? not))))))

Related

(:gen-class) and fully qualified symbol

I'm wondering why I need to include (:gen-class) to access imported functions from other ns without fully qualify them.
Example:
(ns project.core
(:gen-class))
(defn foo [] "foo")
(ns project.core-test
(:gen-class)
(:require [project.core :refer :all]))
(foo) ;=> "foo"
(ns project.core)
(defn foo [] "foo")
(ns project.core-test
(:require [project.core :refer :all]))
(foo) ;=> ComplilerException java.lang.RuntimeException: Unable to resolve symbol: foo in this context
(project.core/foo) ;=> "foo"
I couldn't get my head around this concept.
EDIT:
I had a typo in the require form. Idk why (:gen-class) solved this problem but now the project works again.
The (:gen-class) clause in the ns form is for Ahead-of-Time compilation (AOT). See https://clojure.org/reference/compilation
Something else must be going wrong in your setup. My example:
> d **/core*
-rw-rw-r-- 1 alan alan 98 Jan 13 16:15 src/fred/core.clj
-rw-rw-r-- 1 alan alan 47 Jan 13 16:15 src/tst/fred/core.clj
src/fred/core.clj
------------------------
(ns fred.core)
(defn foo [] (println "foo you"))
(defn -main []
(println "main - enter")
)
src/tst/fred/core.clj
----------------------
(ns tst.fred.core
(:use fred.core ))
(foo)
> lein test
foo you
I get the same results with the require version:
(ns tst.fred.core
(:require [fred.core :refer :all]))
(foo)
It also works if I paste your code into the repl:
~/tmp/fred > lein repl
Clojure 1.9.0
fred.core=> (ns project.core)
nil
project.core=> (defn foo [] "foo")
#'project.core/foo
project.core=> (ns project.core-test
#_=> (:require [project.core :refer :all]))
nil
project.core-test=> (foo)
"foo"

Convert Clojure schema input to lower case.

I can validate clojure schema inputs as string as shown below.
name :- s/Str,
place :- s/Str,
How can I write a defschema for Email to use like
email : - s/Email
which will convert/coerce it to lower case.
I happen to have exactly the one...
(ns myapp.utils.schema
(:import [org.apache.commons.validator.routines EmailValidator])
(:require [clojure.string :as str]
[schema.core :as s]
[schema.coerce :as sco]))
(def valid-email? "Checks that the value is a valid email string."
(let [ev (EmailValidator/getInstance)]
(fn [s]
(and
(string? s)
(.isValid ev, ^String s)
))))
(def Email "Email schema type, stricter than schema.core/Str."
(s/pred valid-email?))
(defn sanitize-email [s]
(if (string? s)
(-> s str/lower-case str/trim)
s))
(defn email-matcher [s]
(when (= Email s) sanitize-email))
(def matcher
"The Plumatic matcher used for Plumatic schema coercion in myapp."
(sco/first-matcher [email-matcher,,,]))
(defn coercer
[schema]
(sco/coercer schema matcher))
;; ...
(ns myapp.customer
(:require [schema.core :as s]
[myapp.utils.schema :as sc]))
(def Customer
{:customer/email sc/Email
:customer/name s/Str})
(def coerce-customer (sc/coercer Customer))
(coerce-customer {:customer/email "vAlENTin#gmaIl.com "
:customer/name "Valentin"})
=> {:customer/email "valentin#gmail.com"
:customer/name "Valentin"}

Shortcut for Requiring Several 'sub-namespaces' with :as

Consider this at the beginning of a Clojure program:
(ns my.core
(:require [lib.a :as my1]
[lib.b :as my2]
[lib.c :as my3]
[lib.d :as my4]))
is there a shortcut for this?
something like:
(ns my.core
(:require [lib [[a :as my1]
[b :as my2]
[c :as my3]
[d :as my4]]]))
Yep, it's called a prefix list:
(ns my.core
(:require (lib [a :as my1]
[b :as my2]
[c :as my3]
[d :as my4])))

clojure - simple map function returns list of nils

This function takes a list of a files and is supposed to return a list of artists:
(defn get-artists [files]
(map #(.get-artist (->Mp3 %)) files))
Here the rest of the code:
(ns musicdb.filesystem
(:use [green-tags.core]))
(import '(java.io.File) '(java.net.url) '(java.io))
(require '[clojure.string :as str])
(defn get-files [search-path]
(let [directory (clojure.java.io/file search-path)
files (file-seq directory)
fonly (filter #(.isFile %) files)]
(map #(last (str/split (.toString %) #"/")) fonly)))
(defprotocol MusicFile
(get-artist [this])
(get-song [this])
(get-album [this]))
(defrecord Mp3 [filename]
MusicFile
(get-artist [this]
(:artist (get-all-info filename)))
(get-song [this]
(:title (get-all-info filename)))
(get-album [this]
(:album (get-all-info filename))))
And here are my tests:
(ns musicdb.core-test
(:require [clojure.test :refer :all]
[musicdb.core :refer :all]
[musicdb.filesystem :refer :all]
[clojure.pprint :refer :all]
))
(deftest test_0
(testing "getFiles returns valid result"
(is (> (count (get-files "/home/ls/books/books")) 50))))
(deftest test_1
(testing "check for file included"
(is (some #{"02 Backlit.mp3"} (get-files "/home/ls/Musik")))))
(deftest test_2
(testing "creating music file record"
(let [myfile (->Mp3 "/home/ls/Musik/Panopticon/02 Backlit.mp3")]
(is (= "Isis" (.get-artist myfile)))
(is (= "Backlit" (.get-song myfile))))))
(deftest test_3
(testing "testing get-artists"
(let [artists (get-artists (get-files "/home/ls/Musik"))
]
(is (> (count artists) 10)))))
(deftest test_4
(testing "testing get-artists check for artist"
(let [artists (get-artists (get-files "/home/ls/Musik"))
]
(is (some #{"Isis"} artists))))) ;artists is [nil nil nil ...]
From this tests only the last fails, which returns a list of nils.
If you want to reproduce ths be sure to include the green-tags dependency in your leiningen project.clj:
[green-tags "0.3.0-alpha"]
Your get-files function doesn't return the full path of the file so get-all-info just returns nil (https://github.com/DanPallas/green-tags/blob/master/src/green_tags/core.clj#L59 in combination with https://github.com/DanPallas/green-tags/blob/master/src/green_tags/core.clj#L120).
Here is a simple example that works:
(map (comp :artist get-all-info)
(filter #(.isFile %)
(file-seq (java.io.File. "/home/vema/Downloads/mp3"))))
;=> ("Yo Yo Honey Singh (DJJOhAL.Com)")
(Humoristic?) disclaimer: The MP3 should not be taken as an example of my musical taste, it was just the first free MP3 I found online.

Hiccup template function

I'm trying to add the following Hiccup template function to my file
(defn d3-page [title js body & {:keys [extra-js] :or {extra-js []}}]
(html5
[:head
[:title title]
(include-css "/css/nv.d3.css"))
(include-css "/css/style.css")]
[:body
(concat
[body]
[(include-js "http://d3js.org/d3.v3.min.js")
(include-js (str "https://raw.github.com"
"/novus/nvd3"
"/master/nv.d3.min.js")]
(map include-js extra-js)
[(include-js "/js/script.js")
(javascript-tag js)])]))
but keep getting an unmatched delimiter when I run lein ring server. This comes from Clojure Data Cookbook, so I am surprised to find an error and suspect the error is just on my end. Below is the rest of the code in the file:
(ns web-viz.web
(:require [compojure.route :as route]
[compojure.handler :as handler]
[clojure.string :as str])
(:use compojure.core
ring.adapter.jetty
[ring.middleware.content-type :only
(wrap-content-type)]
[ring.middleware.file :only (wrap-file)]
[ring.middleware.file-info :only
(wrap-file-info)]
[ring.middleware.stacktrace :only
(wrap-stacktrace)]
[ring.util.response :only (redirect)]
[hiccup core element page]
[hiccup.middleware :only (wrap-base-url)]))
(defn d3-page...as above
...)
(deftype Group [key values])
(deftype Point [x y size])
(defn add-label [chart axis label]
(if-not (nil? label)
(.axisLabel (aget chart axis) label)))
(defn add-axes-labels [chart x-label y-label]
(doto chart (add-label "xAxis" x-label)
(add-label "yAxis" y-label)))
(defn populate-node [selector chart groups transition continuation]
(-> (.select js/d3 selector)
(.datum groups)
(.transition)
(.duration (if transition 500 0))
(.call chart)
(.call continuation)))
(defn force-layout-plot []
(d3-page "Force-Directed Layout"
"webviz.force.force_layout();"
[:div#force.chart [:svg]]))
(defroutes site-routes
(GET "/force" [] (force-layout-plot))
(GET "/force/data.json" []
(redirect "/data/census-race.json"))
(route/resources "/")
(route/not-found "Page not found"))
(def app (-> (handler/site site-routes)))
In line 5
(include-css "/css/nv.d3.css"))
There's an extra ) there.
And in line 13,
"/master/nv.d3.min.js")]
There's one ) missing. Should be
"/master/nv.d3.min.js"))]
You should use an editor which can do the matching of braces, parentheses, and brackets, etc. automatically.