Testing Timbre log outputs with Midje - unit-testing

I am using Timbre as a logging library but I am having issues testing it with Midje. I've got the following method and all I'm trying to do is asserting that Will be printed is printed.
(defn init [level]
(timbre/merge-config! {:output-fn my-output-fn})
(timbre/set-level! level)
(timbre/info "Will be printed")
(timbre/debug "Won't be printed"))
By wrapping (timbre/default-output-fn) around a function of my own my-output-fn, I am able to assert that only 1 out of 2 logs are printed, which is true:
(fact "print info but not debug"
(core/init :info) => nil
(provided
(core/my-output-fn anything) => irrelevant :times 1))
However I would like to make sure that the message being printed is Will be printed. I can't find any way of doing this, what would you do?
I am doing experiments in the following project https://github.com/stephanebruckert/timbre-midje

It is possible to validate the input of a function using Midje's as-checker prerequisite.
When you print the data that come into timbre/default-output-fn (I discovered this via your output-fn) you see it has the following structure:
{:hash_ #delay[{:status :pending, :val nil} 0x5ed805b1], :instant #inst "2016-10-14T17:07:16.779-00:00", :config {:level :info, ....
So the log-level is available in data. With as-checker the log level can be validated as follows:
(fact "print info but not debug"
(core/init :info) => nil
(provided
(timbre/default-output-fn anything ; opts is first argument
(as-checker (fn [data]
(= (-> data :config :level) ; get log level
:info)))) ; validate that it is info
=> anything :times 1)
(provided
(timbre/default-output-fn anything
(as-checker (fn [data]
(= (-> data :config :level)
:debug))))
=> anything :times 0))
Now, you wanted to check the content of the message. Not only the log level.
The actual message is not available in the opts or data passed toward default-output-fn. Looking around in the the Timbre code I saw it is available in a private function vargs->margs that takes an ?err, a msg-type and vargs. The vargs contain the message (in your case what comes in is for example :auto :p ["The message"]).
Via the as-checker approach the messages can be validated follows:
(fact "print Will be printed, not Won't be printed"
(core/init :info) => nil
(provided
(#'timbre/vargs->margs anything anything ["Will be printed"]) => {} :times 1)
(provided
(#'timbre/vargs->margs anything anything ["Won't be printed"]) => {} :times 0) )
Note that vargs->margs has to return a map, otherwise Timbre throws an exception in a later function.
This way it is validated that "Will be printed" is printed once, and "Won't be printed" is never printed.

#ErwinRooijakkers' idea worked but #ptaoussanis on Github has a good reason not to do it.
Please note that timbre/vargs->margs is private, and an implementation
detail. Its behaviour can change at any time without notice, would
strongly recommend not depending on it in any way.
It'd be possible to use a custom appender that sends output somewhere that are handy for your tests to analyse. You could setup these appenders as part of your test setup, and/or use something like timbre/with-config for the appropriate test calls.
So we can add an appender that passes the parameters to check (level message) to a stub.
core.clj
(defn init
([level]
(init level {}))
([level config]
(timbre/merge-config! config)
(timbre/set-level! level)
(timbre/info "will be printed")
(timbre/debug "won't be printed")))
core_test.clj
(:require [timbre-midje.core :as core]
[midje.sweet :refer :all]))
(defn log-stub [level message])
(def log-stub-appender
{:appenders
{:test-appender
{:enabled? true
:fn (fn [data] (log-stub (:level data) (:vargs data)))}}})
(fact "print info but not debug"
(core/init :info log-stub-appender) => nil
(provided
(log-stub :info ["will be printed"]) => irrelevant :times 1
(log-stub :debug ["won't be printed"]) => irrelevant :times 0))

Related

Clojure, re-graph fetched data from graphql successfully, but callback didn't activate

So I use re-graph version 0.1.11 and I try fetching data from the endpoint. After fetching data, I checked network tab in the browser and I found the expected data after that it should activate my callback function, but it doesn't seem to work (but sometimes it works and after refreshing the page a few times it doesn't work again). Here is the code.
;; how I init re-graph
(rf/dispatch [::re-graph/init
::conf/graphql-client-name
{:ws-url url
:http-url url
:ws-reconnect-timeout 500
:resume-subscriptions? true}])
(re-frame.core/reg-event-fx
::fetch-expected-data
(fn [cofx event]
(let [app-db (:db cofx)
some-params (-> event second (cljs.core/js->clj :keywordize-keys true))
token (-> app-db (lens/get-in (auth-db/lens-token :group-level-x)))]
(re-frame.core/dispatch
[:re-graph.core/query
::conf/graphql-client-name
"query findExpectedData($query: FetchExpectedDataInput!, $token: String!) {
findExpectedData(query: $query, token: $token){
value1
value2
...
}
}"
{:query some-params
:token token}
;; this is where the problem occurs
;; even though i found the data in the network tab, but
;; this callback doesn't seem to work (sometimes it works sometimes it doens't)
[::fetched-data-completed]]))))
(re-frame.core/reg-event-fx
::fetched-data-completed
(fn [cofx [_ {:keys [data errors] :as payload}]]
(let [app-db (:db cofx)
error-message (-> errors :errors first :message)]
(if (or (nil? errors) (empty? errors))
(do (bla bla when success))
(pr error-message)))))
I'm stuck with this problem for a few months. maybe because I fetch a lot of data at the same time? or could be something else anyone knows?. By the way the actual code I use defmacro, but it works the same way as the above code.
So I managed to find the answer to my own question. It seems like app-db has not been initialized properly so I fixed that problem and everything works fine. hope it helps someone who struggle with this problem.

Get information out of go block

I have the following ClojureScript code to make a POST request:
(defn perform-post [resource]
"Performs a post and returns the body :)"
(go (let [response (<! (http/post resource))]
(:body response))))
When I make a call to a resource which returns a number
(js/console.log (perform-post post-create-recipe-url))
This prints:
bufObject { buf={...}, n=1, cljs$lang$protocol_mask$partition0$=2,
more...}
bufObject { head=1, tail=0, length=1, meer...}
arr
["6276677237104933520", undefined]
I want to obtain the "6276677237104933520" (the post body) information as a "return" value.
How can I accomplish this? I tried <!! but it does not work since it is not defined.
Blocking semantics (<!!) is not available on ClojureScript platform.
You can retrieve value from a channel only within go block:
(go (js/console.log (<! (perform-post post-create-recipe-url))))

Clojure - avoid duplicated code with monger requests

I am using Clojure and Monger
It works fine, and I group functions by the collection they relate to.
Therefore, every file begins like this :
(ns img-cli.model.mycollectionname
(:require [monger.core :as mg]
[monger.collection :as mc]
[edn-config.core :refer [env]])
(:import [com.mongodb MongoOptions ServerAddress DB WriteConcern]
[org.bson.types ObjectId]))
(def config (get-in env [:mongo]))
;; using MongoOptions allows fine-tuning connection parameters,
;; like automatic reconnection (highly recommended for production
;; environment)
(def ^MongoOptions opts (mg/mongo-options { :threads-allowed-to-block-for-connection-multiplier 300}))
(def ^ServerAddress sa (mg/server-address (:url config) (:port config)))
(def conn (mg/connect sa opts))
(def db (mg/get-db conn (:db config)))
(def collection-name "asset")
;; I have to write one like this every time
(defn find-one-as-map
"fetch asset by Id"
[^String id]
(mc/find-one-as-map db collection-name {:_id (ObjectId. id)}))
Code duplication has of course several disadvantages in itself.
Also I'm not sure if connections are properly pooled afterwards ?
How can I avoid doing this ?
I sense I could pass an additional "db" parameter to each function, but then where would it come from ?
If I create the db connection in the "entry" file of my program, then how could it be passed to every function from there ?
For instance let's says I have Compojure routes in different files :
;; in the main handler file
(def db ...) ;; if I move the previous db configuration
;; in here, it could be the only place where this is set
;; importing Compojure routes from different files
(defroutes routes-from-file1
routes-from-file2...)
Let's say that some functions called from some of the routes in "file2" need access to the db, how can I pass this variable to them ?
I also have a lot of repetitive code afterwards, for instance to get data by Id for every collection...
I feel this could be simplified, but I'm not sure how.
Just refer to it by its namespace
(ns foo
(:require [handler :as h]))
(println h/db)

Midje provided not stubbing function in Compojure / Ring handler

I'm attempting to use Midje to stub the view in a handler unit test, but my use of Midje's (provided) obviously isn't correct.
I've simplified and inlined the view to a (content) function in the handler:
(ns whattodo.handler
(:use compojure.core)
(:require [whattodo.views :as views]))
(defn content [] (views/index))
(defn index [] (content))
(defroutes app
(GET "/" [] (index)))
and am trying to test it using
(ns whattodo.t-handler
(:use midje.sweet)
(:use ring.mock.request)
(:use whattodo.handler))
(facts "It returns response"
(let [response (app (request :get "/"))]
(fact "renders index view" (:body response) => "fake-html"
(provided (#'whattodo.handler/content) => (fn [] "fake-html")))))
I was expecting the stubbed function to be called returning 'fake-html' and thus the unit test passing, but instead, the test fails as the real implementation is called - invoking the real view.
You don't need the function shortcut, just use (content) => .... As you have it right now, midje expects that your code calls literally (#content), but your index function calls (content) instead. Your confusion about midje's syntax might be that you expect that you assign to the function name the expected result, but that is not the case. You have to replace the exact call. I.e., if your index function would call content with some argument, you would have to account for this as well, e.g. by (provided (content "my content") => ...)
I discovered today that I had my scopes confused - the let block introducing response was outside of the fact call that included the provided. Thus the response was created before the provided was invoked.
Working code which passed this early test instead used the against-background call
(facts "It returns response"
(against-background (whattodo.handler/content) => "fake-html")
(let [response (app (request :get "/"))]
(fact "renders index view"
(:body response) => "fake-html")))

NPE in clojure.lang.Reflector.invokeInstanceMethod only in some situations

I'm experiencing a strange problem with Java interop. I wrote a small specialized wrapper around the BDB JE API. When I'm in the repl (cider-repl) everything works fine: I can open the database, add entries, etc. But if I call the add-record function from another function in my app I get an NPE,
NullPointerException clojure.lang.Reflector.invokeInstanceMethod (Reflector.java:26)
I'm starting to think I'm losing my mind. This opens the database:
(defn opendb [envpath dbname]
(let [envconf (.setAllowCreate (EnvironmentConfig.) true)
env (Environment. (ensure-directory envpath) envconf)
dbconf (-> (DatabaseConfig.) (.setAllowCreate true) (.setDeferredWrite true))
database (.openDatabase env nil dbname dbconf)]
{:environment env :database database}))
Here is add-record:
(defn add-record [dbm k v]
(let [key (DatabaseEntry. (.getBytes k "UTF-8"))
value (DatabaseEntry. (.getBytes v "UTF-8"))]
(.put (:database dbm) nil key value)))
There really isn't anything special going on here. The usage where this yields the NPE is similar to
(let [cache (bdb/opendb "local-cache" "subject-map")]
;; do stuff
(doseq [node (function "that returns a sequence of maps")]
(bdb/add-record cache (:foo node) (:bar node)))
(bdb/closedb cache))
I've added prnS inside bb/add-record and all the values are non-nil, yet I still get the NPE.
Something subtle is going on here, and it's eluding me. Anyone have an idea?
Thanks inadvance.
EDIT: here's the full stack trace:
Exception in thread "main" java.lang.NullPointerException
at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:26)
at sterling.bdbje$add_record.invoke(bdbje.clj:33)
at sterling.loaders$load_subject_batch.invoke(loaders.clj:214)
at sterling.loaders$load_subject_headings.invoke(loaders.clj:224)
at sterling.loaders$_main.doInvoke(loaders.clj:257)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at sterling.loaders.main(Unknown Source)
As I said, this makes no sense to me, but is completely reproducible.