Test pre-requisites for tabular tests; how does tabular work? - testing

Let's say I am attempting to test an api that is supposed to handle presence or absence of certain object fields.
Let's say I have tests like so:
(def without-foo
{:bar "17"})
(def base-request
{:foo "12"
:bar "17"})
(def without-bar
{:foo "12"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(def response-without-bar
{:foo "12"
:bar ""
:name "Bob"})
(def response-without-foo
{:bar "17"
:foo ""
:name "Bob"})
(facts "blah"
(against-background [(external-api-call anything) => {:name => "Bob"})
(fact "base"
(method-under-test base-request) => response)
(fact "without-foo"
(method-under-test without-foo) => response-without-foo)
(fact "without-bar"
(method-under-test without-bar) => response-without-bar))
This works as you would expect and the tests pass. Now I am attempting to refactor this using tabular like so:
(def request
{:foo "12"
:bar "17"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(tabular
(against-background [(external-api-call anything) => {:name "Bob"})]
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which results in:
FAIL at (test.clj:123)
Midje could not understand something you wrote:
It looks like the table has no headings, or perhaps you
tried to use a non-literal string for the doc-string?
Ultimately I ended up with:
(tabular
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff) (provided(external-api-call anything) => {:name "Bob"}))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which passes. My question is. How does the tabular function differ from the facts function, and why does one of them accept an against-background while the other blows up?

You need to have following nesting if you want to establish background prerequisites for all your tabular based facts:
(against-background [...]
(tabular
(fact ...)
?... ?...))
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(against-background
[(fn-a) => {:a 1 :b 2 :c 3}]
(tabular
(fact
(fn-b ?k) => ?v)
?k ?v
:a 1
:b 3
:c 3))
(check-facts)
;; => All checks (3) succeeded.
If you want to have a background prerequisite per each tabular case you need to nest it as following:
(tabular
(against-background [...]
(fact ...))
?... ?...)
It's important to have the table just under tabular level, not nested in against-background or fact.
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(tabular
(against-background
[(fn-a) => {?k ?v}]
(fact
(fn-b ?k) => ?v))
?k ?v
:a 1
:b 2
:c 3)
(check-facts)
;; => All checks (3) succeeded.
In your code it looks like the tabular data is not positioned correctly (parentheses, brackets and curly braces are not balanced correctly so it's impossible to say what exactly is incorrect).

Related

Reagent performance issue when passing atom as a function parameter

I work on the react-native application using Clojurescript re-frame and reagent. I have one text input component, and have two versions of the code:
Version 1: input text is a separate component, and state atom is passed as an argument, the same as the recommended in the reagent library docs and examples.
(defn todo-input [value]
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
)
(defn todo-screen []
(let [value (r/atom nil)]
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[todo-input value]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]
[todos]
]))
Version 2: everything in one component.
(defn todo-screen []
(let [value (r/atom nil)]
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]
]]
[todos]]))
The issue is that first version has a performance issue while typing, since there's a big delay and flickering when trying to type fast. Version 2 doesn't have any issues, and I can type as fast as I can.
According to the reagent documentation, passing r/atom as a parameter should not incur any performance issues.
Am I doing something wrong here? What would be the best way to avoid performance penalty here.
This is a small example, and having one component instead of two is not a big deal, but splitting one big to multiple smaller components in a good praxis. State here is local to the component, and I don't want to use re-frame for it.
re-frame/dispatch puts your events in a queue for re-frame to process, so there can be a slightly delay before it actually goes through and your change will be there.
Sounds like you're experiencing the same issue stated here: https://github.com/day8/re-frame/issues/368
So one work around is to use re-frame.core/dispatch-sync which forces re-frame to handle the event directly and synchronously. You might have to also add a call to reagent.core/flush to force re-render the component. I haven't needed flush before when building web clients, but React Native seems to work differently.
You can read more about these two functions here:
https://github.com/day8/re-frame/blob/master/src/re_frame/router.cljc#L251-L263
https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/batching.cljs
Mentioned in the issue above is also https://github.com/Day8/re-com that supposedly works around the issue somehow, but I didn't take a closer look at that.
Your solution #2 is not wrong either, it just gives you a different way of working. So if you need the data in your app-db to update on every keypress for example, only something more like #1 will work. Or using solution #2 but passing in the atom as an argument to your component.
Both of your versions have an issue. You should use Form-2 type components when using local state. Like this:
(defn todo-screen []
(let [value (r/atom nil)]
(fn []
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[todo-input value]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]
[todos]])))
More info about Form-2 here.
Or you could use the r/with-let instead. More info about with-let.
Regarding your original question, you could have a compromise between both your versions, and extract the input and submit button into a separate component:
(defn todo-input-container [on-press]
(r/with-let [value (r/atom nil)]
[rn/view {:style (styles :textInputContainer)}
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
[rn/touchable-opacity
{:on-press (fn []
(on-press #value)
(reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]))
(defn todo-screen []
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[todo-input-container (fn [value] (rf/dispatch [:add-todo value]))]
[todos]])
Looks like the issue here is that reagent doesn't support well components with the controlled inputs due to its async nature.
Controlled input (via :value) should be avoided, or worked around by forcing component update immediately after changing :value.
See reagent issue and explanation for more details.

Clojure tools.analyzer, don't expand macros

What is the best way to avoid expanding macros when creating ast's using tools.analyzer.jvm/analyze
This is an example of the information I am currently collecting:
(map of function name to dependency set)
{some-function
#{{:name load-order-ns-file-maps, :ns #namespace[clj-graph.core]}
{:name *logger-factory*, :ns #namespace[clojure.tools.logging]}
{:name analyze, :ns #namespace[clojure.tools.analyzer.jvm]}
{:name make-dir-tracker, :ns #namespace[clj-graph.core]}
{:name enabled?, :ns #namespace[clojure.tools.logging.impl]}
{:name read-all, :ns #namespace[clj-graph.core]}
{:name get-logger, :ns #namespace[clojure.tools.logging.impl]}
{:name traverse-expr, :ns #namespace[clj-graph.core]}
{:name log*, :ns #namespace[clojure.tools.logging]}
{:name track-reload,
:ns #namespace[clojure.tools.namespace.reload]}
{:name examine-form, :ns #namespace[clj-graph.core]}}}
The actual code calls log/info but as this is macroexanded, I can never capture the the name and ns in which it was declared - instead I get the macro expansion giving me:
:name *logger-factory*, :ns #namespace[clojure.tools.logging]
:name enabled?, :ns #namespace[clojure.tools.logging.impl]
:name get-logger, :ns #namespace[clojure.tools.logging.impl]
:name log*, :ns #namespace[clojure.tools.logging]
As I am building a dependency graph, ideally I just want to find the endpoints, ie :name info :ns #namespace[clojure.tools.logging].
Reading the documentation for analyzer.jvm/analyze it gives an example as:
(analyze form env {:bindings {#'ana/macroexpand-1 my-mexpand-1}})
but when I try this, ie:
(defn ^:dynamic my-expand-1 [form] form)
(ana/analyze
'(defn prnt [xs] (my-pre-defined-macro xs))
(ana/empty-env)
{:bindings {#'ana/macroexpand-1 my-expand-1}})
I get the error
IllegalStateException Can't dynamically bind non-dynamic var:
clojure.tools.analyzer.jvm/macroexpand-1 clojure.lang.Var.pushThreadBindings (Var.java:320)
If macros were not expanded, you couldn't get any useful information, because what a macro expands into is completely opaque to you. Perhaps someone has written this function:
(defn foo [x]
(-> x
inc
println))
If you don't expand the -> macro, you will not find out that foo depends on inc and println. And finding out that it depends on -> is not very interesting.
I solved this for specific macros by providing implementations of macroexpand-1 and parse. My macroexpand-1 implementation ignores the specific macros I want to parse myself (by simply returning the form) and my parse implementation provides custom parsing. You can pass custom method bindings in the options to analyze:
For example, to provide custom parsing of the binding macro:
(require '[clojure.tools.analyzer.jvm :as ja])
(require '[clojure.tools.analyzer :as ana])
(defn my-parse [[op & _ :as form] env]
(if (= op 'binding)
(merge (ana/parse-let* form env)
{:op :unexpanded-dynamic-binding})
(ja/parse form env)))
(defn binding-form? [o] (and (seq? o) (= (first o) 'binding)))
(defn my-macroexpand-1
([form] (my-macroexpand-1 form (ja/empty-env)))
([form env]
(if (binding-form? form)
form
(ja/macroexpand-1 form env))))
(ja/analyze
'(binding [*clojure-version* 1] *clojure-version*)
(ja/empty-env)
{:bindings {#'ana/parse my-parse
#'ana/macroexpand-1 my-macroexpand-1}})

mocking a function call in midje

Say I have a function
(defn extenal_api_fn [stuff]
... do things....
)
(defn register_user [stuff]
(external_api_fn stuff))
And then a test
(def stuff1
{:user_id 123 })
(def stuff2
{:user_id 234})
(background (external_api_fn stuff1) => true
(with-redefs [external_api_fn (fn [data] (println "mocked function"))]
(register_user stuff1) => true)
(register_user stuff2) => true)
(facts "stuff goes here"
(fact "user that registers correctly
(= 1 1) => truthy)
(fact "user that has a registration failure"
(= 1 2) => falsy))
This fails with
"you never said #'external_api_fn" would be called with these arguments:
contents of stuff1
What would be a good way to stub this function call (in only some cases) in order to simulate an internal transaction failure.
You could use Midje's provided:
(fact
(register_user stuff1) => :registered
(provided
(extenal_api_fn stuff1) => :registered))
(fact
(register_user stuff2) => :error
(provided
(external_api_fn stuff2) => :error))
You can also stub a function to return a value no matter input parameters by using anything in place of the function argument:
(fact
(register_user stuff2) => :error
(provided
(external_api_fn anything) => :error))

Merge nested params via link_to

I'm using nested params (via ransack nested as q) alongside normal params to build links on a page and am having trouble with getting the two to play nicely when I try and merge the nested params with the other params.
For example if I have:
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
and try and build a link to change the param "lowEquity_true" with
rates_url(params[:q].merge(:lowEquity_true => '0'))
then I end up with the new params below, which looks like its updated q but dropped the rest of the params.
{"lowEquity_true"=>"0", "s"=>"rate asc"}
If I instead try to merge q & merge into the other params it doesn't update q, and just merges what was in q into the other params instead
rates_url(params[:q].merge(:lowEquity_true => '0').merge(params))
{"freq"=>"weekly", "loan_amount"=>"350000", "lowEquity_true"=>"0",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"},
"s"=>"rate asc"}
I've tried all sorts of various combinations and don't appear to be getting anywhere so am sure that I'm missing something basic!
You are doing it wrong.
Let me explain with an example :
params = {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
At this point, params[:q] is
{:x=>24, :y=>25}
If I do,
params[:q].merge(:x => 99)
then my params[:q] will become
{:x=>99, :y=>25}
and this is what you are supplying as an argument to rates_url(params[:q].merge(:lowEquity_true => '0'))
that's why only {"lowEquity_true"=>"0", "s"=>"rate asc"} is passed to rates_url as parameters.
Now, if you do something like
params[:q].merge(:x => 99).merge(params)
then params[:q].merge(:x => 99) gives you {:x=>99, :y=>25} and then it merges {:x=>99, :y=>25} into the original params {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
, so this results into
{:x=>99, :y=>25, :a=>1, :b=>2, :q=>{:x=>24, :y=>25}}
Now, let me explain you what you should do :-
You params is
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
So, you should do :
params[:q].merge!(:lowEquity_true => '0')
rates_url(params)
That's it
I hope you khow the difference between merge and merge! :-
merge! is destructive, it will modify the original paramter where as merge will not unless you take it in a variable and use it.
Alternatively, if you want to do the same thing stated above in a single line then, just do
rates_url(params.merge!(:q => {:lowEquity_true => '0', "s"=>"rate asc"}))
OR
rates_url(params.merge(:q => params[:q].merge(:lowEquity_true => '0')))

How to filter IS NULL in ActiveAdmin?

I've a table with an integer column called "map_id", I want to add an activeadmin filter to filter if this column IS NULL or IS NOT NULL.
How could this be implemented ?
I tried the following filter
filter :map_id, :label => 'Assigned', :as => :select, :collection => {:true => nil, :false => ''}
But, I get the following error message :
undefined method `map_eq' for #
If anyone is happening on this thread belatedly, there is now an easy way to filter for null or non null in active admin :
filter :attribute_present, :as => :boolean
filter :attribute_blank, :as => :boolean
It is no longer necessary to add a custom method to the scope to accomplish this.
Not found a good solution but here is a how.
filters of Active_admin are accomplished by meta_search, you can override the functions automatically generated by meta_search in your model to get the behavior that you want, the best way is to use the scope since you need to return a relation in order to chain with other query/scopes, as stated here
in your model:
for :as=>:select filters, acitve_admin use the _eq wheres, here is the source code
scope :map_eq,
lambda{ |id|
if(id !='none')
where( :map_id=> id)
else
where( :map_id=> nil)
end
}
#re-define the search method:
search_method :map_eq, :type => :integer
in your ative_admin register block:
filter :map_id, :label => 'Assigned', :as => :select, :collection => [['none', 'none'], ['one', 1],['tow', 2]]
# not using :none=>nil because active_admin will igore your nil value so your self-defined scope will never get chained.
Hope this help.
seems search_method doesn't work in recent rails version, here is another solution:
add scope to your model:
scope :field_blank, -> { where "field is null" }
scope :field_not_blank, -> { where "field is not null" }
add to /app/admin/[YOUR MODEL]
scope :field_blank
scope :field_not_blank
you will see buttons for these scopes appear (in top section, under model name, not in filter section)
The new version of ActiveAdmin uses Ransacker. I manage to got it working this way:
On the admin
filter :non_nil_map_id, :label => 'Assigned', :as => :select, :collection => [['none', 'none'], ['one', 1],['tow', 2]]
For consistency, I took the same code from #Gret answer just changing the filter name
On your model
ransacker :not_nil_map_id, :formatter => proc {|id| map_id != 'none' ? id : 'none' } do |parent|
parent.table[:id]
end
This should trigger a search against nil in case the id is 'none', and active record will return all the nil id entries.
This thread helped a lot.
With ransackable scopes:
On the ActiveAdmin resource definition:
filter :map_id, :label => 'Assigned', as: :select, :collection => [['Is Null', 'none'], ['Not null', 'present']]
On your model:
scope :by_map_id, ->(id) { (id == 'none' ? where(map_id: nil) : where('map_id IS NOT NULL')) }
def self.ransackable_scopes(_auth_object = nil)
%i[by_map_id]
end