Midje stub method doesn't work - clojure

I'm trying to do this:
(defn pilot-ready [args] [2])
(defn ready []
(pilot-ready ["hello"]))
(facts
(ready) => [1]
(provided (pilot-ready ["hello"]) => [1]))
(against-background
[(pilot-ready ["hello"]) => [1]]
(fact
(ready) => [1]))
What's this supposed to do, is stub the pilot ready method, and make it return [1] for the argument ["hello"]
The first fact fails with:
FAIL at (innkeeper_paths_client_facts.clj:53)
These calls were not made the right number of times:
(pilot-ready ["hello"]) [expected at least once, actually never called]
FAIL at (innkeeper_paths_client_facts.clj:52)
Expected: [1]
Actual: [2]
Diffs: in [0] expected 1, was 2
=> false
The second with:
FAIL at (innkeeper_paths_client_facts.clj:58)
Expected: [1]
Actual: [2]
Diffs: in [0] expected 1, was 2
=> false
Can someone help me figure out what am I doing wrong?

The problem was this line, in the project.clj file:
:jvm-opts ^:replace ["-Dclojure.compiler.direct-linking=true"]

Related

How do I generate random email addresses in test.check?

I'm trying to use gen/fmap with two random alphanumeric strings. Then I concatenate them with "#" and append ".com". But I'm struggling with the syntax.
First attempt:
(gen/fmap str (gen/string-alphanumeric) "#" (gen/string-alphanumeric) ".com")
But gen/fmap only takes two arguments.
Second attempt, where I group the second part doesn't work either
(gen/fmap str ((gen/string-alphanumeric) "#" (gen/string-alphanumeric) ".com"))
EDIT: I have a partial solution. It generates an email address, but the part before and after the # are the same. Example: john#john.com
This is the partial solution
(def gen-full-string
(gen/such-that #(not= % "") gen/string-alphanumeric))
(gen/fmap #(str % "#" % ".com") gen-full-string)
I wrote gen-full-string because the empty string "" was crashing the code. Since I have parsing and plan to make validation functions, I didn't care about the empty string. I wanted to test core functionality not edge cases. Once I implement validation, I will probably remove gen-full-string. So the email generator would become (gen/fmap #(str % "#" % ".com") gen/string-alphanumeric)
Gary Fredericks has a library https://github.com/gfredericks/test.chuck that adds string regex generation to Spec. This allows you to use a regex that is as simple or as detailed as you want for email addresses.
When I run this script:
clojure -Sdeps '{:deps {org.clojure/test.check {:mvn/version "1.1.0"}}}' /dev/stdin <<EOF
(require '[clojure.test.check.generators :as gen])
(def gen-email
(gen/fmap (fn [[s1 s2]] (format "%s#%s.com" s1 s2))
(gen/tuple gen/string-alphanumeric
gen/string-alphanumeric)))
(run! prn (gen/sample gen-email))
EOF
I get this output:
"#.com"
"#.com"
"9p#VH.com"
"x1#Ws.com"
"23mF#93.com"
"b40#14.com"
"v0n#5Wskg.com"
"mNo#R85LuM.com"
"#.com"
"8Z84B9U0#f9QSJgM.com"
I like test.check, but it takes a lot of time to understand the details. I created a helper library you may like. An example:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[tupelo.gen :as tg]
[clojure.test.check.properties :as prop]
))
(dospec 9
(do
(newline)
(spy :sample)
(prop/for-all [w tg/word-alpha+]
(spyx w))))
(dospec 9
(do
(newline)
(spy :emails)
(prop/for-all [w1 tg/word-alpha+
w2 tg/word-alpha+]
(let [email-addr (str w1 \# w2 ".com")]
(spyx email-addr)
)
)))
with result:
-----------------------------------
Clojure 1.10.3 Java 15.0.2
-----------------------------------
Testing tst.demo.core
:spy--tst.demo.core--line-011 => :sample
w => "b"
w => "lb"
w => "k"
w => "Y"
w => "mMWC"
w => "TzD"
w => "Nq"
w => "wQzPrF"
w => "HqEM"
{:result true, :num-tests 9, :seed 1629153846012, :time-elapsed-ms 1, :test-var "dospec-line-8"}
:spy--tst.demo.core--line-018 => :emails
email-addr => "k#v.com"
email-addr => "M#uL.com"
email-addr => "N#a.com"
email-addr => "OUr#B.com"
email-addr => "O#v.com"
email-addr => "UtZ#ndXS.com"
email-addr => "qhtDt#YLfE.com"
email-addr => "mLw#pFjKJJq.com"
email-addr => "hJ#DkCSnpAG.com"
{:result true, :num-tests 9, :seed 1629153846014, :time-elapsed-ms 1, :test-var "dospec-line-15"}
More details here. The above example uses my favorite template project.

Can not access to static methods in Clojure

I have a problem when trying to access a static method of some class.
Here is the code and the error message:
(ns demo.app
(:import
[java.nio.file Paths]
[java.util List])
=> (Paths/get "a" "B" "c")
CompilerException java.lang.IllegalArgumentException: No matching method: get, compiling:(*cider-repl clj-demo*:68:16)
=> (java.nio.file.Paths/get ".")
ClassCastException java.lang.String cannot be cast to java.net.URI clj-demo.ch08/eval11327 (form-init3786136217280477578.clj:76)
=> (java.nio.file.Paths/get "." "finally.txt")
ClassCastException java.lang.String cannot be cast to [Ljava.lang.String; clj-demo.ch08/eval11329 (form-init3786136217280477578.clj:78)
The java doc follows:
https://docs.oracle.com/javase/10/docs/api/java/nio/file/Paths.html#get(java.lang.String,java.lang.String...)
I've found on https://clojure.org/reference/java_interop , it writes:
(Classname/staticMethod args*)
I also tried these:
=> (System/getProperty "java.vm.version")
"25.102-b14"
=> (clojure-version)
"1.8.0"
No problem. So:
Why does Paths/get not work in REPL?
Why does java.nio.file.Paths/get not work, either?
Why does Paths/get and java.nio.file.Paths/get get different error messages?
I've also tried
(List/of 0) ;; => [0]
(List/of 1 2 3) ;; => Method
java.util.List.of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
must be InterfaceMethodref constant
(List/of (Integer. 1)) ;; => Method java.util.List.of(Ljava/lang/Object;)Ljava/util/List; must
be InterfaceMethodref constant
(List/of "s") ;; => Method java.util.List.of(Ljava/lang/Object;)Ljava/util/List; must
be InterfaceMethodref constant
Paths.get(String, String...) takes two arguments - a string and an array of strings. Java creates the array automatically for you, but in clojure you need to construct the array yourself e.g.
(Paths/get "a" (into-array String ["b" "c"]))
Calling
(java.nio.file.Paths/get ".")
fails because the only overload of get which takes a single argument requires it to be a URI not a String.

unable to remove duplication when composing tests

I am unable to remove duplication inside a clojure.test test.
Suppose I have multiple implementations for the same abstraction:
(defn foo1 [] ,,,)
(defn foo2 [] ,,,)
(defn foo3 [] ,,,)
and I also have a test that all implementations should pass:
(defn test-impl [foo]
(is (= ,,, (foo))))
I can now create a clojure.test test that checks all the implementations in a single step:
(deftest test-all-impls
(test-impl foo1)
(test-impl foo2)
(test-impl foo3))
All is good; running the tests in the REPL I get:
(run-tests)
Testing user
Ran 1 tests containing 3 assertions.
0 failures, 0 errors.
=> {:test 1, :pass 3, :fail 0, :error 0, :type :summary}
I now want to modify test-all-impls to remove the duplication of having to invoke test-impl explicitly for each of the implementations. It occurs to me to modify test-all-impls as follows:
(deftest test-all-impls
(for [foo [foo1 foo2 foo3]] (test-impl foo))
Hmm, now not all is good; in the REPL I get:
(run-tests)
Testing user
Ran 1 tests containing 0 assertions.
0 failures, 0 errors.
=> {:test 1, :pass 0, :fail 0, :error 0, :type :summary}
What am I missing?
To get around the lazyness of for, use doseq instead:
(deftest test-all-impls
(doseq [foo [foo1 foo2 foo3]] (test-impl foo))
Another answer is to convert the result into a vector, which will force the for loop to run:
(deftest test-all-impls
(vec (for [foo [foo1 foo2 foo3]] (test-impl foo))))

Use of loose schemas in Clojure with clj-schema

I want to create what I believe is called a loose schema to verify the contents of a Clojure map.
The clj-schema examples led me to believe that I only needed to specify :loose as the first argument to def-map-schema. However this does not seem to work.
Consider the following test (using midje):
(def-map-schema loose-schema :loose [[:id] Number])
(fact (empty? (validation-errors loose-schema {:id 1})) => true) ; OK
(fact (empty? (validation-errors loose-schema {:id "string"})) => false) ; OK
(fact (empty? (validation-errors loose-schema {:id 1 :foo "bar"})) => true) ; NO!
The third test produces:
"Path [:foo] was not specified in the schema."
How can I make the schema ignore extraneous map entries?
I had the arguments around the wrong way.
The schema returned by def-map-schema was not actually loose:
(loose-schema? loose-schema)
=> false
Instead of:
(def-map-schema loose-schema :loose [[:id] Number])
The correct form is:
(def-map-schema :loose loose-schema [[:id] Number])

Mocking protocol implementations in Midje

Is there any way to mock (not stub) a protocol function with Midje (clojure) using something like the "provided" syntax?
This is simial to the question in: Mocking Clojure protocols, but with mocking.
In more detail: I have a protocol and a function that returns something that implements it. I would like to stub the function to return a mock of the protocol and I would like to register an expectation on one of the functions of the mocked protocol "implementation".
edit - here is an example:
There is a protocol and it's implementation:
(defprotocol Thiny (go-bump [_ _]))
(deftype TheThing []
Thiny
(go-bump [_ _] 23))
There is a function that returns an implementation of a protocol:
(defn gimme [] (TheThing.))
TheThing might be a DB or network connection or some other nasty thing you want to get rid of in the test.
Then, there is the function I want to test:
(defn test-me [n]
(let [t (gimme)]
(-> t (go-bump n))))
I want to make sure it calls go-bump with n.
This is my first attempt to create a test. But it is only halfway done, I would like to setup expectations on the Thiny returned by gimme:
(fact
(test-me 42) => 42
(provided (gimme) => (reify Thiny (go-bump [_ n] n))))
Mocking protocols should be no different than mocking functions, you need to consider that the first dispaching parameter is this and so the mocking function should take that type in consideration.
For instance, given a protocol P:
(defprotocol P
(foo [this])
(bar-me [this] [this y]))
Extended for type Integer
(extend-protocol P
Integer
(foo [this]
(+ this 4))
(bar-me [this]
(* this 5))
(bar-me [this y]
(+ this y)))
You can check a couple things first, there's no implementation for Long:
(foo 3)
=> IllegalArgumentException No implementation of method: :foo of
protocol: #'P found for class: java.lang.Long
clojure.core/-cache-protocol-fn (core_deftype.clj:541)
Works as expected for ints:
(foo (int 3))
=> 7
Now define the fact and provide the protocol function as you need:
(fact
(foo (int 3)) => 7
(provided (foo 3) => 8))
In this case it properly fails since the mock is returning 8 instead of 7 for the specified input.
FAIL at (test.clj:20)
Expected: 7
Actual: 8
If value mocks are not enough and you need to provide an alternative implementation instead, take a look at with-redefs-fn, you can wrap tested functions with it.
=> (defn f [] false)
=> (println (f))
;; false
=> (with-redefs-fn {#'f (fn [] true)}
#(println (f)))
;; true
There's also a discussion in GitHub about his with a couple alternatives for runtime dispatching mocks.
For posterity. Here is a working test:
(fact
(test-me 42) => 42
(provided (gimme) => :the-thingy)
(provided (go-bump :the-thingy 42) => 42))
The trick was to use multiple provided statements that tie into each other.
Wierd observation. The same way of testing doesn't work for a function that uses the other syntax for calling functions on the protocol. No idea why.
(defn test-me2 [n]
(let [t (gimme)]
(.go-bump t n)))