Rspec expect a yield - ruby-on-rails

I have a hash in Rails and one of the values is a yield.
{
key1: 'foo',
key2: lambda { |p| root_path(p) },
}
I am not entirely sure how I can expect key2 to receive this yield
I looked in to yield_control but I am not sure that is what I need.
I want to assert that key2 receives a yield and I also want to assert what that yield returns (root_path(p))

You want to be sure that key2 contains a lambda and you want to test what the lambda returns?
example of a test for a lambda
let(:my_hash) = {
{
key1: 'foo',
key2: lambda { |p| root_path(p) }
}
}
expect(my_hash[:key2].try(:lambda?)).to be true
to test for result
expect(my_hash[:key2].call("foo")) to eq root_path("foo")

Related

Plucking out all hash keys that has a specific word

How do you pluck out a hash key that has for example
Hash 1
{sample => {apple => 1, guest_email => my_email#example.com }}
Hash 2
{guest => {email => my_email#example.com}}
Lets say I want 1 method that will pluck out the email from either of those hashes, is there any way I can that like lets say hash.get_key_match("email")
You may use Hash#select to only return keypairs, matching the block:
h = { guest_email: 'some_mail', other_key: '123', apple: 1 }
h.select { |key, _value| key =~ /email/ }
#=> { guest_email: 'some_mail' }
I guess that you need the deep search.
There is no method from the box.
You need use recursion for this goal.
I suspect that your issue can be solved by:
class Hash
def deep_find(key, node = self)
searchable_key = key.to_s
matched_keys = node.keys.select { |e| e.to_s.match?(searchable_key) }
return node[matched_keys.first] if matched_keys.any?
node.values
.select { |e| e.respond_to?(:deep_find) }
.map { |e| e.deep_find(key, e) }
.flatten.first
end
end
h = {a_foo: {d: 4}, b: {foo: 1}}
p (h.deep_find(:foo))
# => {:d=>4}
h = {a: 2, c: {a_foo: :bar}, b: {foo: 1}}
p (h.deep_find(:foo))
# => :bar
you can use this
hash = { first_email: 'first_email', second_email: 'second_email' }
hash.select { |key, _value| key =~ /email/ }.map {|k, v| v}

Sort items in a nested hash by their key

I have a nested hash with unsorted keys:
given = {
"lorem" => {
:AA => "foo",
:GR => "foo",
:BB => "foo"
},
"ipsum" => {
:ZZ => "foo",
:GR => "foo",
}
}
What I'm trying to accomplish is a hash with sorted keys:
goal = {
"ipsum" => {
:GR => "foo",
:ZZ => "foo"
},
"lorem" => {
:AA => "foo",
:BB => "foo",
:GR => "foo"
}
}
I have experimented with .each method and sort_by
given.each { |topic| topic[:key].sort_by { |k, v| k } }
But I'm getting an error message: TypeError: no implicit conversion of Symbol into Integer
Any help is greatly appreciated!
PS: I noticed with gem pry the output is already sorted. But in IRB it's not.
You can use group_by, and transform_values to transform the values inside each hash, also using sort_by plus to_h:
given.transform_values { |value| value.sort.to_h }.sort.to_h
# {"ipsum"=>{:GR=>"foo", :ZZ=>"foo"}, "lorem"=>{:AA=>"foo", :BB=>"foo", :GR=>"foo"}}
You're getting an error because when iterating over a hash, you have to local variables within the block scope to use, the key and its value, you're assigning only one (topic) and trying to get its key, which would be trying to access a key in:
["lorem", {:AA=>"foo", :GR=>"foo", :BB=>"foo"}]
Which isn't possible as is an array. You can update your code to:
given.each do |topic, value|
...
end
But anyway you'll need a way to store the changes or updated and sorted version of that topic values.
given_hash = {"lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}, "ipsum"=>{:ZZ=>"foo", :GR=>"foo"}}
Get keys
given_hash.keys
=> ["lorem", "ipsum"]
New sorted hash
new_hash = {}
given_hash.keys.sort.each do |sorted_key|
new_hash[sorted_key] = given[sorted_key]
end
=> {"ipsum"=>{:ZZ=>"foo", :GR=>"foo"}, "lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}}
There can be a better way to do this.

Looking for a rspec matcher that checks a value to be included in array

How can I write this:
Given an array b = ['one', 'two', 'three']
I expect value a to be
included in the array b.
I want to use it together with an all matcher so my final code would look like this:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
expect(my_list).to all(include("type" => a_value_included_in(b))
Which is testing for:
all hashes from my_list must have a type key whose value is in array b.
Is there such built-in matcher in Rspec?
And how do you check inclusion of a value in the array besides using the obvious reverse: expect([1, 2, 3]).to include(value), which in my example, is not really fitting in?
If you want to check if every string from b is present in any of the hashes from my_list:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
b.each do |str|
expect(my_list.find { |h| h['type'] == str }).to be_present
end
Or, you can go with:
expect(my_list.map { |h| h['type'] }.sort) to eq(b.sort)
So when someone adds my_list << { 'type' => 'not_in_list' } the spec
will fail.
expect(my_list.map { |h| b.include?(h['type']) }.uniq).to eq(true)
In your case I would simply use (order of elements doesn't matter):
expect(my_list).to match_array(b.map{|v| { 'type' => v })
or use include matcher instead of match_array if b can ba accepted as a subset of my_list.
I don't think that special matcher for such cases exists and/or it's really needed. Still, you can create a custom one if you really can't live w/o it.

Rspec send params to subject

I wrote a small service, which need params: [:search][:area], [:search][:floor] etc.
I write test:
subject { AwesomeService.new(params) }
let(:params) do
{
"search" => {
'area' => object1.area,
'floor' => object1.floor
}
}
end
But my test fails(manually work perfectly). When I debug my service in test mode, params[:search][:floor] is NULL. How can I fix my params in test?
The params object in rails does not care if you look for values in it as symbols or strings:
params[:search][:floor] == params['search']['floor']
A Hash in ruby, though, is different - if you insert strings as keys, you need to query it with strings.
param_hash[:search][:floor] != params['search']['floor']
You stub params as a hash. This means you should either set it with symbols instead of strings, or use HashWithIndifferentAccess.
subject { AwesomeService.new(params) }
let(:params) do
ActiveSupport::HashWithIndifferentAccess.new {
"search" => {
'area' => object1.area,
'floor' => object1.floor
}
}
end

Rspec: Check if array includes object which includes property

I have a json array full of objects.
my_array = [{id => 6, name => "bob"},
{id => 5, name => "jim"},
{id => 2, name => "steve"}]
I need to see if the array holds an object which contains an attribute "id" that is set to 5. The "name" attribute is unknown.
How do I do this in rspec?
I know if I had the name attribute I know I could just do:
my_array.should include({:id => 5, :name => "jim"})
expect(myArray.find { |item| item[:id] == 5 }).to_not be_nil
or with the legacy should syntax
myArray.find { |item| item[:id] == 5 }.should_not be_nil
Please note that myArray is not following Ruby conventions. Variables use underscore
my_array
not camelcase
myArray
It is also possible using the having_attributes alias:
expect(my_array).to include( an_object_having_attributes(id: 5) )
or, as in my own use case, matching the whole array:
expect(my_array).to contain_exactly(
an_object_having_attributes(id: 5),
an_object_having_attributes(id: 6),
an_object_having_attributes(id: 2)
)
You can unfold an array and to check matching of two arrays like here:
expect(my_array).to include(*compare_array)
It'll unfold and match each value of array.
It's equivalent to this:
expected([1, 3, 7]).to include(1,3,7)
Source: Relish documentation
This would only be worth it if you were doing many of these, but you could define a custom matcher:
RSpec::Matchers.define :object_with_id do |expected|
match do |actual|
actual[:id] == expected
end
description do
"an object with id '#{expected}'"
end
end
# ...
myArray.should include(object_with_id 5)
Put this any matcher into spec/support/matchers.rb and require it in your spec_helper.rb
RSpec::Matchers.define :any do |matcher|
match do |actual|
actual.any? do |item|
matcher.matches?(item)
end
end
end
Then you can use it in examples like this:
expect(my_array).to any(include(id: 5))
I would use the RSpec 3's composable include matcher like so:
expect(my_array).to include(include(id: 5))
This would have the benefit of a more verbose output via RSpec in case of failure.
it 'expects to have element with id 3' do
my_array = [
{ id: 6, name: "bob" },
{ id: 5, name: "jim" },
{ id: 2, name: "steve" }
]
expect(my_array).to include(include(id: 3))
end
This would generate following failure message:
Failures:
1) Test expects to have element with id
Failure/Error: expect(my_array).to include(include(id: 3))
expected [{:id => 6, :name => "bob"}, {:id => 5, :name => "jim"}, {:id => 2, :name => "steve"}] to include (include {:id => 3})
Diff:
## -1,2 +1,2 ##
-[(include {:id => 3})]
+[{:id=>6, :name=>"bob"}, {:id=>5, :name=>"jim"}, {:id=>2, :name=>"steve"}]
Further reading:
https://relishapp.com/rspec/rspec-expectations/docs/composing-matchers
Here's a customer matcher "include_object" (probably a better name should be used since it just checks if the id's are present)
used as follows
obj = {id:1}
objs = [{id: 1}, {id: 2}, {id: 3}]
expect(objs).to include_object obj
Matcher can handle Object, Hashs (symbols or string)
It also prints just the id's in the array on exception for easier viewing
RSpec::Matchers.define :include_object do |expected|
ids = []
match do |actual|
ids = actual.collect { |item| item['id'] || item[:id] || item.id }
ids.find { |id| id.to_s == expected.id.to_s }
end
failure_message_for_should_not do |actual|
"expected that array with object id's #{ids} would contain the object with id '#{expected.id}'"
end
failure_message_for_should_not do |actual|
"expected that array with object id's #{ids} would not contain the object with id '#{expected.id}'"
end
end
I see there are many responses, but here is an implementation that was helpful for me on a case which you need to check for multiple key-value pairs on each array object, instead of single attribute checking
Usage
expect(array).to include(have_attributes_with_values({ id: 5, name: 'Jim' }))
Matcher implementation
RSpec::Matchers.define :have_attributes_with_values do |expected|
match do |actual|
expected.each do |key, value|
return false unless actual[key] == value
end
end
end

Resources