This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-12-11
Channels
- # adventofcode (7)
- # aws-lambda (1)
- # beginners (161)
- # cider (19)
- # cljsjs (5)
- # cljsrn (30)
- # clojure (80)
- # clojure-korea (2)
- # clojure-new-zealand (8)
- # clojure-russia (73)
- # clojure-sanfrancisco (1)
- # clojure-spec (14)
- # clojure-uk (12)
- # clojurescript (84)
- # cursive (7)
- # defnpodcast (8)
- # dirac (16)
- # events (2)
- # garden (7)
- # hoplon (178)
- # off-topic (2)
- # om (58)
- # om-next (2)
- # onyx (21)
- # pedestal (1)
- # planck (15)
- # protorepl (32)
- # re-frame (31)
- # untangled (1)
- # yada (5)
but it says that f should at least be a function of arity 1 and 2, (and 0 if no init is provided)
in fact, none of these resources provided hints as to what arity 1 is for: https://clojuredocs.org/clojure.core/transduce https://clojuredocs.org/clojure.core/completing http://blog.cognitect.com/blog/2014/8/6/transducers-are-coming http://clojure.org/reference/transducers
That last link says what arity 1 is used for: Completion (arity 1) - some processes will not end, but for those that do (like transduce), the completion arity is used to produce a final value and/or flush state. This arity must call the xf completion arity exactly once.
@seancorfield sorry I guess i glossed over that bit, so basically it takes the accumulated valued thus far and produces a final value?
Yup. I'm on my phone right now so I can't provide code. But think of processes that need to do some post-processing when the data has all been consumed.
thanks! that should probably be in the transduce docs but I think i've got it now xd
@roelofw Higher order functions do not necessarily imply purity, but it is a great escape from the limitations of with-redefs
. To understand the problems with with-redefs
you may want to refer this post I blogged recently http://charsequence.blogspot.com/2016/12/mocking-with-var-redefinition.html
I have this :
(read-data (client/get {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True"}}))
@roelofw What is args
here? If it’s already known then you can create a function closing over those args. If it’s expected at runtime then f
needs to accept it as argument.
I was thinking that args is this part : {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True"}}
OK, if this is known beforehand, then you can construct a function: (fn [] (client/get {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True"}}))
— it would be an arity-0 function
so, you can call it thus: (f)
taking no args because all args are known beforehand
oke, and when I want to use it with testing I can replace it with this part : (read-date (constantly {:id 1 :name "Roelof"}))
on the next function I use : (client/get {:as :json :query-params {:format "json" :key (env :key)}})
Where is the URL passed here? As the first argument to client/get
?
I was hoping the function could take a function which can be one of both client/get or a one of both dummy variables
I m using it like this
(let [art-objects (-> (str " " id "/tiles")
(client/get {:as :json :query-params {:format "json" :key (env :key)}})
:body
:levels
)
or
(let [art-objects (-> (str " " id )
(read-data (client/get {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True"}}))
:body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
Whose responsibility is it to pass the URL? If it is the caller fn’s then the contract needs to be changed to (fn [url] …)
If the URL is known beforehand, then you can construct an arity-0 fn using the URL and other options.
part of the url is known. The only thing that is not known is the id because that is a part of a map which is made by another function
Since ID seems to be a runtime argument, you may have to create an arity-1 fn like this: (fn [id] (-> (str "
Your contract is arity-1 fn that accepts ID
oke, lets check if I understand you right. I have to change the function to this :
(let [art-objects (-> f(id) :body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
and make another function like this :
(fn [id] (-> (str " " id) (client/get {:as json ...})))
I think you meant (f id)
in the snippet above 🙂
@kumarshantanu but the idea is the right one ?
No problem! All of us who transitioned from another OOP language do this mistake.
Yes, that’s correct.
Yes, of course. If you make your design as inside-out as possible, you will always be free to stub/mock anything you want.
@kumarshantanu so I can now copy this function :
(defn read-data-painting
"Reads the title, description, date , collection, colors and url of a image"
[id]
(let [art-objects (-> (read-json-data id)
:body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
description (:description art-objects)
date (get-in art-objects [:dating :year])
collectie (first (:objectCollection art-objects))
colors (:colors art-objects)]
{:id id :name name :description description :date date :collectie collectie :colors colors}))
and change the read-json-data function to this :
(read-json-data (constantly {:id 1 :name "Roelof"}))
I think now you have to pass read-json-data
as argument to read-data-painting
?
If read-json-data
is not an argument to read-data-painting
, then you may have to use with-redefs
to mock it when testing read-data-painting
@roelofw Got to be away now. I will check the questions later in case you have any.
I have this test file :
(deftest test-app
(testing "get numbers"
(is (= [ "1234" "abcd"] (api/read-numbers {:body {:artObjects [{:objectNumber "1234"} {:objectNumber "abcd"}]}}))))
(testing "get data "
(let [art-objects (-> (api/read-json-data (constantly {:body {:artObjects {:id 1 :name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"}}}))
:body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
description (:description art-objects)
date (get-in art-objects [:dating :year])
collectie (first (:objectCollection art-objects))
colors (:colors art-objects)]
(is (= {:name name :description description :date date :collectie collectie :colors colors}
{:name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"} ))))
but I see this as output :
expected: {:name nil,
:description nil,
:date nil,
:collectie nil,
:colors nil}
actual: {:name "Roelof",
:description "rubbish",
:date "12-02-1980",
:collectie "",
:colors "yellow"}
why is the expected empty ? I thought it would be the contents of the constanly function
@roelofw two things. First you probably want all the code in that let to be in your production fn but the args to the production fn to be [id read-json-data]
then call it using 1 and your constantly function in the test: (read-paintings 1 (constantly {:body {:artObject {:id 1 :principalMakers [{:name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"}]}}}})
Secondly the reason you have nil is that you are looking for artObject
but your map has :artsObjects
(with an s)
also you are looking inside :artObject
map for :principalMakers
but your artObject
map has no :principalMakers
key
then inside :principalMakers
your getting the first element from a sequential structure (a vector?)
and then that first element is expecting a key-value pair keyed by :name
so you need a map similar to the one I've got in my (constantly
snippet above ☝️
Actually even that's not right as :date
needs to be inside a :dating
map and the value for the year inside :year
for example
The map would look like this:
{:body {:artObject {:principalMakers [{:name "Roelof"}]
:description "rubbish"
:dating {:year "1980"}
:objectCollection ["collectie"]
:colors "yellow"}}}
I typed that without an editor balance parens so it needs checking
@agile_geek why jere read-paintings and not the function that mocks up the client/get one
Well it depends what you are testing
If your original fn is calling the api and then extracting the data and you want to test the fn that parses the data then it's whatever that's called but you pass in the fn the just does the call to the api
so if the fn that parses the data is called read-paintings
its that you want to call in the test (or whatever you called it)
but you pass it two args
the id
i.e. 1
This is the whole function :
(defn read-json-data [id] (-> (str " " id) (client/get {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True"}} )))
(defn read-data-painting
"Reads the title, description, date , collection, colors and url of a image"
[id]
(let [art-objects (-> (read-json-data id)
:body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
description (:description art-objects)
date (get-in art-objects [:dating :year])
collectie (first (:objectCollection art-objects))
colors (:colors art-objects)]
{:id id :name name :description description :date date :collectie collectie :colors colors}))
and the fn that mocks the data
right so make read-data-painting
take two args
the id and a function that calls the api
add arg call read-json-data
to the args list
then pass in the real read-json-data
fn when you call it in the production code
and pass in (constantly ...)
in the test
oke, then I can delete the read-json-data function and have also the change the calling function here :
(defn do-both-in-parallel [ids]
(let [paint-thread (future (pmap read-data-painting ids))
image-thread (future (pmap read-image-url ids))]
(map merge @paint-thread @image-thread)))
and the test - function is then only the call to the read-data-painting with the constantly function .
You still need a fn that calls the api so you still need a read-json-data
function
as you need to pass it into the call to read-data-painting
(defn do-both-in-parallel [ids]
(let [paint-thread (future (pmap (comp read-data-painting read-json-data) ids))
image-thread (future (pmap read-image-url ids))]
(map merge @paint-thread @image-thread)))
if...you rewrite the args to read-data-painting
to this...
(defn read-data-painting
"Reads the title, description, date , collection, colors and url of a image"
[call-api-fn id]
(let [art-objects (-> (call-api-fn id)
:body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
description (:description art-objects)
date (get-in art-objects [:dating :year])
collectie (first (:objectCollection art-objects))
colors (:colors art-objects)]
{:id id :name name :description description :date date :collectie collectie :colors colors}))
I renamed the fn arg passed in to make it more obvious that it's calling whatever you pass in
i.e I named it call-api-fn
In fact you could change the function to not take id or a fn but just take the map from calling the api
and then thread the result of calling read-json-data
to the read-data-painting
fn
(defn do-both-in-parallel [ids]
(let [paint-thread (future (pmap #(read-data-painting (read-json-data %)) ids))
image-thread (future (pmap read-image-url ids))]
(map merge @paint-thread @image-thread)))
and change read-data-painting
to
(defn read-data-painting
"Reads the title, description, date , collection, colors and url of a image"
[response]
(let [art-objects (-> response
:body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
description (:description art-objects)
date (get-in art-objects [:dating :year])
collectie (first (:objectCollection art-objects))
colors (:colors art-objects)]
{:id id :name name :description description :date date :collectie collectie :colors colors}))
Then in the test you can just pass the map
without (constantly ...)
wrapped around it
The only bit you can't test without calling the api is the read-json-data
fn
oke, where is the function where the json is made ? I see response but never a call to call-api-fn
In my second example you are simply calling read-json-data
that returns a map, and then passing it as the response
arg to read-data-painting
(defn read-json-data [id] (-> (str "
In your code ☝️
oke, and the test is one thing a call to read-data-painting and compare that to the map I compare , right ?
Yeah in the test you pass in a map that looks like the response from read-json-data
and then compare it to what you expect
Cool glad to help
You are a wonderfull person who help me a lot. Together with sean and a few more Im still in the clojure world.
@agile_geek I have to change [response]
to [response id]
because otherwise I loose the id of the painting
@roelofw not if you change the read-data-painting
to extract id from response
it's in the map
You might need to check what's returned from the api call but it's in there
oke, maybe that is why I see this error : Wrong number of args (1) passed to: api-get/read-data-painting
that's telling you that you're read-data-painting
takes two args but you're calling it with only one
if you pass only the map you need to work out how to destructure id from the response map
can't remember what it looks like but it's in there
Call read-json-data
from a REPL passing in a good id and look at the response map
Use the REPL to explore fn's like this
and poke at data
it's called REPL based development
I am having issues with tests in my project. in the REPL If i run my function it returns data as expected, if i validate it everything is fine and if i run the test it pass fine.
however, if i run lein test
my tests fail 😞
it seems that s/check
from plumatic schema is returning a map instead of nil.
also, the function i am trying to validate get a string as input and uses the elements in the string to "build" a function (the first element is the function name, the rest is the arguments). the test return a custom error message, while if i run the function with the same input it runs as expected.
can anyone help me or point me to some documentation I might have missed?
@iecya: it's a little hard to tell what's going on from your description -- do you perhaps have this project up on GitHub where we can take a look and try it out for ourselves?
many thanks @seancorfield i got all my tests working now!
@agile_geek can you help me one more time. My test file looks like this now :
(testing "get data "
(let [art-objects (-> (api/read-json-data {:body {:artObjects {:id 1 :name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"}}})
:body
:artObject)
name (-> art-objects
:principalMakers
first
:name)
description (:description art-objects)
date (get-in art-objects [:dating :year])
collectie (first (:objectCollection art-objects))
colors (:colors art-objects)]
(is (= {:name name :description description :date date :collectie collectie :colors colors}
{:name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"} ))))
and now I see this error :
ERROR in (test-app) (support.clj:201)
Uncaught exception, not in assertion.
I'm on my phone so can't really see but is all this inside a (deftest ...)
Also I don't see you actually calling your function in the test?
Just call your read-data-painting
in you test passing a map that looks like the response from read-json-data
and then see if it equals :
{:name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"}
So your test would be;
(is (=
{:name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"}
(read-data-painting {...})))
Where ... is a map that matches what (read-json-data "id")
would return
hmm, still this output :
expected: {:name "Roelof",
:description "rubbish",
:date "12-02-1980",
:collectie "",
:colors "yellow"}
actual: {:name nil,
:description nil,
:date nil,
:collectie "",
:colors nil}
it looks this map is not good :
(api/read-data-painting {:body {:artObjects {:id 1 :principalMakers [{:name "Roelof" :description "rubbish" :date "12-02-1980" :collectie "" :colors "yellow"}]}}})
Look at what comes back from call to api