How do I stub langohr RabbitMQ interactions in clojure? - clojure

I'm trying to stub the RabbitMQ interactions, as those aren't really the main purpose of the application I'm writing.
So, I've tried rebinding the langohr functions in my tests like so:
(defn stub [ch]
(langohr.basic/ack ch 1))
(deftest test-stub
(with-redefs [langohr.basic/ack (fn [a1 a2] true)]
(is (= true (stub "dummy")))))
When I run the test with lein test, I get a
java.lang.ClassCastException:
redwood.env_test$fn__2210$fn__2211 cannot be cast to clojure.lang.IFn$OLO
I've been trying several other ways including different test frameworks to redefine or rebind the langohr lib functions with no progress.
I've tested other scenarios and I've successfully stubbed cheshire (json parsing clojure lib) functions with the above code structure.
I humbly request assistance in understanding why my langohr stubs aren't working and for tips on how I can do this in an elegant manner.

The ClassCastException occurs because langohr.basic/ack is a function that takes a primitive argument - specifically, it is of type clojure.lang.IFn$OLO, where the OLO stands for "object, long, object".
You have to redef it be of the same type. Try this:
(with-redefs [langohr.basic/ack (fn [a1 ^long a2] true)] ...)

Related

Is it possible to create writeable bean from Clojure that I can manage from jconsole?

I was exploring Clojure java.jmx API Reference and trying the examples mentioned there e.g.
;; Can I serve my own beans? Sure, just drop a Clojure ref
;; into an instance of clojure.java.jmx.Bean, and the bean
;; will expose read-only attributes for every key/value pair
;; in the ref:
(jmx/register-mbean
(create-bean
(ref {:string-attribute "a-string"}))
"my.namespace:name=Value")
It works great, the bean's attribute value is visible in the console but it is read-only.
Is there a way to create a writeable bean(so that it is listed in "Operations" folder and manageable from console)?
It looks like the clojure.java.jmx code supports setAttribute.
(see (deftype Bean ...) in https://github.com/clojure/java.jmx/blob/master/src/main/clojure/clojure/java/jmx.clj
The easiest way seems to be just using an atom instead of a ref.
You could then have atom watchers to run some code if JMX changes it.
Maybe give that a try. I've forgotten most of JMX ;)
EDIT: Quickly tried it. It looks like the attribute is still read-only :(
I gotta look deeper. Nevertheless, the source code is pretty nice and hopefully easy to understand.
EDIT2:
The issue is with build-attribute-info which passes false to the writeable? flag in the `MBeanAttributeInfo.
You can monkey patch that:
(import 'java.jmx.MBeanAttributeInfo)
(require '[clojure.java.jmx :as jmx])
(defn build-my-attribute-info
"Construct an MBeanAttributeInfo. Normally called with a key/value pair from a Clojure map."
([attr-name attr-value]
(build-my-attribute-info
(name attr-name)
(.getName (class attr-value)) ;; you might have to map primitive types here
(name attr-name) true true false)) ;; false -> true compared to orig code
([name type desc readable? writable? is?] (println "Build info:" name type readable? writable?) (MBeanAttributeInfo. name type desc readable? writable? is? )))
;; the evil trick one should only use in tests, maybe
;; overwrite the original definition of build-attribute-info with yours
(with-redefs [jmx/build-attribute-info build-my-attribute-info]
(jmx/register-mbean (jmx/create-bean (atom {:foo "bar"})) "my.bean:name=Foo"))
;; write to it
(jmx/write! "my.bean:name=Foo" "foo" "hello world")
;; read it
(jmx/read "my.bean:name=Foo" "foo")
=> "hello world"
Now, unfortunately, Java Mission Control still can't change the value, but I'm not sure why. The MBean info is correct.
Must be a permission thing.

How can you mock macros in clojure for tests?

I'd like to mock out a macro in a namespace.
For instance, clojure.tools.logging/error.
I tried with-redefs with no luck
(def logged false)
(defmacro testerror
{:arglists '([message & more] [throwable message & more])}
[& args]
`(def logged true))
(deftest foo
...
(with-redefs
[log/error testerror]
...
That gave this error:
CompilerException java.lang.RuntimeException: Can't take value of a macro
Amalloy provided you the answer for your direct question on how to mock a macro - you cannot.
However, you can solve your problem with other solutions (simpler than moving your whole application to component dependency injection). Let me suggest two alternative implementations (unfortunately, not very straightforward but still simpler than using component).
Mock the function called by logging macro
You cannot mock a macro but you can mock a function that will be used when the logging macro get expanded.
(require '[clojure.tools.logging :as log])
(require '[clojure.pprint :refer [pprint]])
(pprint (macroexpand `(log/error (Exception. "Boom") "There was a failure")))
Gives:
(let*
[logger__739__auto__
(clojure.tools.logging.impl/get-logger
clojure.tools.logging/*logger-factory*
#object[clojure.lang.Namespace 0x2c50fafc "boot.user"])]
(if
(clojure.tools.logging.impl/enabled? logger__739__auto__ :error)
(clojure.core/let
[x__740__auto__ (java.lang.Exception. "Boom")]
(if
(clojure.core/instance? java.lang.Throwable x__740__auto__)
(clojure.tools.logging/log*
logger__739__auto__
:error
x__740__auto__
(clojure.core/print-str "There was a failure"))
(clojure.tools.logging/log*
logger__739__auto__
:error
nil
(clojure.core/print-str x__740__auto__ "There was a failure"))))))
As you can see, the function that does actual logging (if a given level is enabled) is done with clojure.tools.logging/log* function.
We can mock it and write our test:
(require '[clojure.test :refer :all])
(def log-messages (atom []))
(defn log*-mock [logger level throwable message]
(swap! log-messages conj {:logger logger :level level :throwable throwable :message message}))
(with-redefs [clojure.tools.logging/log* log*-mock]
(let [ex (Exception. "Boom")]
(log/error ex "There was a failure")
(let [logged (first #log-messages)]
(is (= :error (:level logged)))
(is (= "There was a failure!" (:message logged)))
(is (= ex (:throwable logged))))))
Use your logging library API to collect and inspect log messages
Your logging library API might provide features that would allow you to plug into in your test to collect and assert logging events. For example with java.util.logging you can write your own implementation of Handler that would collect all logged log records and add it to a specific (or root) logger.
You cannot do this. The point of macros is that they are expanded when the code is compiled, and after that they are gone. The original code that included a call to the macro is unrecoverable. You cannot retroactively redefine a macro at runtime: you're too late already.
An alternative approach, if you want to have swappable logging implementations, would be to use something like Component for dependency injection, and use a different logging component depending on whether you are running tests or running your real program. Arguably that's a bit heavy-handed, and maybe there is a simpler approach, but I don't know it.

Clojure - test a Pedestal route

I would like to write tests for a Pedestal web-service.
If I have :
(defn pong
[request]
(ring-resp/response "pong"))
(defroutes routes[[["/" {:get pong}]]])
How would I write a test for that ?
(deftest alive-system
(testing "ping-pong route"
;; How do I test my route ?
;; If possible :
;; - I would like to have direct access to it
;; ie. no need to bind pedestal to a port would be nice
;; - The ability to omit some interceptors would be nice also,
;; as it would allow me to receive plain Clojure data structures
;; instead of, for instance, JSON which I would have to parse.
...)
Edit:
Here is what I tried :
(deftest alive-system
(testing "ping-pong route"
(let [response (my-other.ns/routes (mock/request :get "/ping"))]
(is (= (:status response) 200))
(is (= (:body response) "pong")))))
But I get an exception :
ERROR in (alive-system) (service_test.clj:13)
Uncaught exception, not in assertion.
expected: nil
actual: java.lang.ClassCastException: clojure.lang.LazySeq cannot be cast to clojure.lang.IFn
So after asking on the issue I linked ohpaulez replied :
#nha - Thanks for using Pedestal! Sorry your question didn't get an
answer on StackOverflow - I don't think anyone monitors SO for
Pedestal questions. The best place to ask those kinds of questions is
on the mailing list.
Pedestal ships with its own utility for making requests directly to
the servlet (similar to ring/mock, although I've never used mock
myself) called response-for. The Pedestal Service template produces a
test automatically for you. Check out one of the samples for an
example.
Also note that said response-for doesn't yet support asynchronous responses (so my routes that uses asynchronous interceptors with core.async failed - I had to make them synchronous).

Clojure - connections at compile time

I have a rabbitMQ connection that seems to be started at compile time (when I type lein compile) and then blocks the building of my project. Here are more details on the problem. Let us say this is the clojure file bla_test.clj
(import (com.rabbitmq.client ConnectionFactory Connection Channel QueueingConsumer))
;; And then we have to translate the equivalent java hello world program using
;; Clojure's excellent interop.
;; It feels very strange writing this sort of ceremony-oriented imperative code
;; in Clojure:
;; Make a connection factory on the local host
(def connection-factory
(doto (ConnectionFactory.)
(.setHost "localhost")))
;; and get it to make you a connection
(def connection (.newConnection connection-factory))
;; get that to make you a channel
(def channel (. connection createChannel))
;;HERE I WOULD LIKE TO USE THE SAME CONNECTION AND THE SAME CHANNEL INSTANCE AS OFTEN AS
;; I LIKE
(dotimes [ i 10 ]
(. channel basicPublish "" "hello" nil (. (format "Hello World! (%d)" i) getBytes)))
The clojure file above is part of a bigger clojure program that I build using lein. My problem is that when I compile with "lein compile", a connection is done because of the line (def connection (.newConnection connection-factory)) and then the compilation is stopped! How can I avoid this? Is there a way to compile without building connection? How can I manage to use the same instance of channel over several calls coming from external components?
Any help would be appreciated.
Regards,
Horace
The Clojure compiler must evaluate all top-level forms, because it can be required to run arbitrary code when expanding calls to macros.
The usual solution to issues like the one you describe is to define a top-level Var holding an object of a dereferenceable type, for example an atom or a promise, and have an initialization function provide the value at runtime. (You could also use a delay and specify the value inline; this is less flexible, since it makes it more difficult to use a different value for testing etc.)

Clojure deftype calling function in the same namespace throws "java.lang.IllegalStateException: Attempting to call unbound fn:"

I'm placing Clojure into an existing Java project which heavily uses Jersey and Annotations. I'd like to be able to leverage the existing custom annotations, filters, etc of the previous work. So far I've been roughly using the deftype approach with javax.ws.rs annotations found in Chapter 9 of Clojure Programming.
(ns my.namespace.TestResource
(:use [clojure.data.json :only (json-str)])
(:import [javax.ws.rs DefaultValue QueryParam Path Produces GET]
[javax.ws.rs.core Response]))
;;My function that I'd like to call from the resource.
(defn get-response [to]
(.build
(Response/ok
(json-str {:hello to}))))
(definterface Test
(getTest [^String to]))
(deftype ^{Path "/test"} TestResource [] Test
(^{GET true
Produces ["application/json"]}
getTest
[this ^{DefaultValue "" QueryParam "to"} to]
;Drop out of "interop" code as soon as possible
(get-response to)))
As you can see from the comments, I'd like to call functions outside the deftype, but within the same namespace. At least in my mind, this allows me to have the deftype focus on interop and wiring up to Jersey, and the application logic to be separate (and more like the Clojure I want to write).
However when I do this I get the following exception:
java.lang.IllegalStateException: Attempting to call unbound fn: #'my.namespace.TestResource/get-response
Is there something unique about a deftype and namespaces?
... funny my hours on this problem did not yield an answer until after I asked here :)
It looks like namespace loading and deftypes was addressed in this post. As I suspected the deftype does not automatically load the namespace. As found in the post, I was able to fix this by adding a require like this:
(deftype ^{Path "/test"} TestResource [] Test
(^{GET true
Produces ["application/json"]}
getTest
[this ^{DefaultValue "" QueryParam "to"} to]
;Drop out of "interop" code as soon as possible
(require 'my.namespace.TestResource)
(get-response to)))