RSpec magic for testing an array's none? method? - ruby

I have an Array/Enumerable where each entity is a Hash. Using RSpec what is the most idiomatic way to test for "none of the entries in the enumerable should have a key called 'body'"?
I can do something like:
array.none? {|thing| thing.key? 'body'}.should be_true
or
array.should be_none {|thing| thing.key? 'body'}
...but there must be a more RSpec-way of doing this, correct?
I can't seem to find an appropriate built-in matcher. Is the answer a custom matcher?

I would use
responses.should be_none { |response| response.key? 'body' }
Between the two you gave. This would be slightly more helpful with an error like
"Expected none? to return true"
where as your first example would say something like
"expected False:false class to be true"
The third option I can see would be something like
keys = responses.map { |response| response.keys }.flatten.uniq
keys.should_not include "body"
This would give an error like
expected ["foo", "bar", "body"] not to include "body"
Other than that, looking at https://www.relishapp.com/rspec/rspec-expectations/v/2-11/docs/built-in-matchers/satisfy-matcher
you could try
responses.should satisfy { |response| not response.keys.include? "body" }

Another option would be turning the be_none statement around with be_any:
responses.should_not be_any { |response| response.key? 'body' }
I would assume that the result is equivalent as any? is the negation of none?.
It's mostly a question of which option you feel is the most intuitive to read and, as EnabrenTane mentions, if you think the error messages are helpful enough.

Related

Ruby passing symbol(s) as keyword argument(s)?

I have a method, where I want to use keyword parameter(s) and case/when statements, where I prefered to use symbols:
def navigate_to(page:)
case page
when :inbox
# some code
when :archive
# some code
else
# some code
end
end
Is this the correct syntax when calling the method?
navigate_to page: :inbox
The : : seems a little bit strange.
The keyword argument syntax is:
key: value
If key is page and value is the symbol :inbox, you indeed have:
page: :inbox
You could also use a positional argument instead:
def navigate_to(page)
# same as your code
end
navigate_to :inbox
Yes, the correct syntax is navigate_to page: :inbox.
While this is common Ruby, it is short and equivalent for several different things. First, the braces.
You are actually calling:
navigate_to(page: :inbox)
Second, the keyword argument pattern originates from hashes as arguments. Before there were keyword arguments, a common way would be to pass in a hash[1], like so:
def navigate_to(options)
page = options[:page]
end
navigate_to({ page: :inbox })
But when last argument in a method call is a hash, one can leave out the {}.
And last, the actual keys in the hash. A while ago (1.8 -> 1.9 IIRC) a short version was introduced for the following:
{ :page => 'Some Value' }, namely { page: 'Some Value' }. When Some Value is a symbol, that becomes { page: :inbox }.
So, taking all that:
navigate_to page: :inbox
Orignates from:
navigate_to({ :page => :inbox })
It might make more sense reading it like this, or knowing it comes from that.
And I know Ruby, nor ruby-ist, like braces, (), as can be seen in the mindboggling DSL of for example rspec, but I can advise especially new developers, to add them. It often makes code better understandable.
navigate_to(page: :inbox) is probably easier to understand than navigate_to page: :inbox, especially when you start calling through other methods: navigate_to page page_from_session :user.
[1] But, to stress, that is not really what is happening here. Keyword arguments and hash arguments do differ, now that we have keyword arguments. this just shows why the syntax is this way.

Expect assertion that is only true for one element in an array [duplicate]

This question already has answers here:
RSpec matcher that checks collection to include item that satisfies lambda
(2 answers)
Closed 3 years ago.
I want to assert that an array contains at least one element that passes an RSpec expectation. But most of the elements in the array will not pass the expectation. So I'd like to do something like this:
it "finds one element that matches" do
array.any? do |element|
expect(element).to eq("expected value")
end
end
And have the test pass if any of the elements pass the expectation. But, of course, the test will fail as I've written it here.
Is there a pattern in RSpec to do what I want to accomplish?
I don't want to do this:
it "finds one element that matches" do
expect(array.any? {|val| val == "expected value"}).to be_true
end
Because it's not clear to me how to manually check the same thing as the matcher I need to use in my test. I want to use the have_attributes matcher, which does some subtle metaprogramming magic I don't want to risk messing up trying to re-implement on my own.
You can use the include matcher to compose matchers:
expect(array).to include(a_string_matching(/foo/))
Despite the somewhat awkward syntax, you can use this with have_attributes:
expect(obj).to have_attributes(tags: include(a_string_matching(/foo/))
But if that's not flexible enough for whatever reason, you can use the satisfy matcher:
expect(array).to satisfy {|arr| arr.any? {|val| val == "expected value"})
The double-nested block is somewhat awkward by itself, but the flexibility of satisfy lets you do all kinds of stuff with it, and you can use the include matcher to get you there. For example:
require "rspec"
require "ostruct"
obj = OpenStruct.new(name: "foobar", tags: %w(bin bazzle))
describe obj do
it "has a bin tag" do
is_expected.to have_attributes(tags: include(/bin/))
end
it "has a tag 3 characters long" do
is_expected.to have_attributes(tags: include(satisfy { |t| t.length == 3 }))
end
end
If you're willing to add a gem, I really like rspec-its for cases like these: they can clean up the specs for individual attributes of an object which don't warrant their own subject block nicely:
describe obj do
its(:tags) { is_expected.to be_a Array }
its(:tags) { is_expected.to include "bin" }
end
RSpec has composing matchers that can be used with expect(array).to include to achieve what I wanted. For example:
expect(array).to include(a_string_matching("expected value"))
For the have_attributes matcher specifically, RSpec has an alias for it called an_object_having_attributes, allowing me to write:
expect(array).to include(an_object_matching(object_i_want_duplicated))

rspec - matcher for one of choices

I have method returning random value from the predefined array (namely: [ 'value1', 'value2']).
How should I test that with rspec?
I'd like to do something like:
expect(FooClass.new.value).to be_in ['value1', 'value2']
Any way to do that? Thanks.
Use this
expect(['value1', 'value2']).to include(FooClass.new.value)
Or a simple Boolean match
expect(['value1', 'value2'].include? FooClass.new.value).to be true
Also, there is an or
expect('value').to eq('value1').or eq('value2')
Advantages:
It sounds like normal English.
The error message is a bit longer but contains all relevant info:
expected: "value1"
got: "value"
...or:
expected: "value2"
got: "value"
As muirbot pointed out you should pass the value you are testing to expect(), not the other way.
It's more flexible, it will work if someone came here looking for a solution to something like:
expect({one: 1, two: 2, three: 3}).to have_key(:one).or have_key(:first)
This does work, but is unconventional because the value you pass to expect should be the value you're testing.
expect(['value1', 'value2']).to include(FooClass.new.value)
I think it would be better to do
expect(Foo.new.value).to satisfy { |value| ['value1', 'value2'].include?(value) }
This will also give you a more accurate message when your test fails.
If you need this behavior often, you can write your own matcher. Here is the one I wrote - you can stick this in your spec file directly or into any file included by your test suite:
RSpec::Matchers.define(:be_one_of) do |expected|
match do |actual|
expected.include?(actual)
end
failure_message do |actual|
"expected one of #{expected}, got #{actual}"
end
end
This has a nicer failure message than any of the other answers so far (in my opinion). For instance:
Failures:
1) mydata is either empty or a list
Failure/Error: expect(mydata.class).to(be_one_of([NilClass, Array]))
expected one of [NilClass, Array], got String
Or you can customize the error message if some other wording makes more sense to you.

Omitting an argument for a method in a block

I wonder, is it possible to do something similar in Ruby to what I can do in Scala or other languages:
someCollection.foreach(x => println(x)) // a full version
someCollection.foreach(println) // a short version
In Ruby I can do:
some_array.each { |x| puts x }
So how can I do this?
some_array.each { puts }
UPDATE:
I'm not talking about puts in particular, it just picked it for example. There might be some_other_method which takes one parameter.
some_array.map { some_other_method }
some_array.map(some_other_method) # ???
def some_other_method a
# ... doing something with a
end
If you look up the rules for implicit η-expansion in the SLS (§6.26.5), it should be immediately obvious that it relies crucially on static type information and thus cannot possibly work in Ruby.
You can, however, explicitly obtain a Method object via reflection. Method objects respond to to_proc and like any object that responds to to_proc can thus be passed as if they were blocks using the unary prefix & operator:
some_array.each(&method(:puts))
Not quite like that, unfortunately. You can send a method name to be called on each object, e.g.:
some_array.each &:print_myself
Which is equivalent to:
some_array.each {|x| x.print_myself}
But I don't know of a clean (read: built-in) way to do what you're asking for. (Edit: #Jörg's answer does this, though it doesn't really save you any typing. There is no automatic partial function application in Ruby)

Elegant way of duck-typing strings, symbols and arrays?

This is for an already existing public API that I cannot break, but I do wish to extend.
Currently the method takes a string or a symbol or anything else that makes sense when passed as the first parameter to send
I'd like to add the ability to send a list of strings, symbols, et cetera. I could just use is_a? Array, but there are other ways of sending lists, and that's not very ruby-ish.
I'll be calling map on the list, so the first inclination is to use respond_to? :map. But a string also responds to :map, so that won't work.
How about treating them all as Arrays? The behavior you want for Strings is the same as for an Array containing only that String:
def foo(obj, arg)
[*arg].each { |method| obj.send(method) }
end
The [*arg] trick works because the splat operator (*) turns a single element into itself or an Array into an inline list of its elements.
Later
This is basically just a syntactically sweetened version or Arnaud's answer, though there are subtle differences if you pass an Array containing other Arrays.
Later still
There's an additional difference having to do with foo's return value. If you call foo(bar, :baz), you might be surprised to get [baz] back. To solve this, you can add a Kestrel:
def foo(obj, arg)
returning(arg) do |args|
[*args].each { |method| obj.send(method) }
end
end
which will always return arg as passed. Or you could do returning(obj) so you could chain calls to foo. It's up to you what sort of return-value behavior you want.
A critical detail that was overlooked in all of the answers: strings do not respond to :map, so the simplest answer is in the original question: just use respond_to? :map.
Since Array and String are both Enumerables, there's not an elegant way to say "a thing that's an Enumberable, but not a String," at least not in the way being discussed.
What I would do is duck-type for Enumerable (responds_to? :[]) and then use a case statement, like so:
def foo(obj, arg)
if arg.respond_to?(:[])
case arg
when String then obj.send(arg)
else arg.each { |method_name| obj.send(method_name) }
end
end
end
or even cleaner:
def foo(obj, arg)
case arg
when String then obj.send(arg)
when Enumerable then arg.each { |method| obj.send(method) }
else nil
end
end
Perhaps the question wasn't clear enough, but a night's sleep showed me two clean ways to answer this question.
1: to_sym is available on String and Symbol and should be available on anything that quacks like a string.
if arg.respond_to? :to_sym
obj.send(arg, ...)
else
# do array stuff
end
2: send throws TypeError when passed an array.
begin
obj.send(arg, ...)
rescue TypeError
# do array stuff
end
I particularly like #2. I severely doubt any of the users of the old API are expecting TypeError to be raised by this method...
Let's say your function is named func
I would make an array from the parameters with
def func(param)
a = Array.new
a << param
a.flatten!
func_array(a)
end
You end up with implementing your function func_array for arrays only
with func("hello world") you'll get a.flatten! => [ "hello world" ]
with func(["hello", "world"] ) you'll get a.flatten! => [ "hello", "world" ]
Can you just switch behavior based on parameter.class.name? It's ugly, but if I understand correctly, you have a single method that you'll be passing multiple types to - you'll have to differentiate somehow.
Alternatively, just add a method that handles an array type parameter. It's slightly different behavior so an extra method might make sense.
Use Marshal to serialize your objects before sending these.
If you don't want to monkeypatch, just massage the list to an appropriate string before the send. If you don't mind monkeypatching or inheriting, but want to keep the same method signature:
class ToBePatched
alias_method :__old_takes_a_string, :takes_a_string
#since the old method wanted only a string, check for a string and call the old method
# otherwise do your business with the map on things that respond to a map.
def takes_a_string( string_or_mappable )
return __old_takes_a_string( string_or_mappable ) if String === string_or_mappable
raise ArgumentError unless string_or_mappable.responds_to?( :map )
# do whatever you wish to do
end
end
Between those 3 types I'd do this
is_array = var.respond_to?(:to_h)
is_string = var.respond_to?(:each_char)
is_symbol = var.respond_to?(:to_proc)
Should give a unique answer for [], :sym, 'str'

Resources