I currently have something like:
it 'assigns #competition' do
expect(assigns(:competition)).to be_a_new(Competition)
end
Is there a shorter version of this using the it { should ... } type syntax?
I don't know that it's shorter, but you can use:
subject {assigns(:competition)}
it {should be_a_new(Competition)}
or you can use:
it {expect(assigns(:competition)).to be_a_new(Competition)}
In both cases, the shortening is coming from the elimination of the string argument to it, which is independent of the use of should.
By now the RSpec documentation suggests to use is_expected.to, as in:
describe [1, 2, 3] do
it { is_expected.to be_an Array }
it { is_expected.not_to include 4 }
end
cf. http://www.rubydoc.info/gems/rspec-core/RSpec/Core/MemoizedHelpers#is_expected-instance_method
Related
I recently learn about rspec test in rails and as following the link https://relishapp.com/rspec/rspec-core/v/3-6/docs/subject/explicit-subject that said "The subject is memoized within an example but not across examples " with the code below:
RSpec.describe Array do
# This uses a context local variable. As you can see from the
# specs, it can mutate across examples. Use with caution.
element_list = [1, 2, 3]
subject { element_list.pop }
it "is memoized across calls (i.e. the block is invoked once)" do
expect {
3.times { subject }
}.to change{ element_list }.from([1, 2, 3]).to([1, 2])
expect(subject).to eq(3)
end
it "is not memoized across examples" do
expect{ subject }.to change{ element_list }.from([1, 2]).to([1])
expect(subject).to eq(2)
end
end
Can anyone explain to me:
why 3.times { subject } exec only once element_list.pop
the sentence 'it "is not memoized across examples"' meant element_list still [1, 2, 3] but in this example, it is only [1, 2] ?
Thank you.
First, by way of clarification, the RSpec subject helper is nothing more than a special case of RSpec's let helper. Using subject { element_list.pop } is equivalent to let(:subject) { element_list.pop }.
Like any other let, subject is evaluated once per example. If subject has a value, that value is returned without re-evaluation. The general term for this is "memoization."
Ruby's ||= operator does the same thing. It says, "if a value exists in that variable, return the value, otherwise evaluate the expression, assign it to the variable, and return the value." You can see this concept in action in the console with this example:
>> element_list = [1, 2, 3]
>> subject ||= element_list.pop
=> 3
>> element_list
=> [1, 2]
>> subject ||= element_list.pop
=> 3
>> element_list
=> [1, 2]
>> subject
=> 3
The fact that subject is not memoized across examples means that its value is reset when a new example is executed. So for your next it block, subject will start out unassigned and its expression will be re-evaluated when it is first used in that next it block.
Im testing if my method populate() returns a non-null value (which it does, it returns an integer > 0) but having troubles to correctly write it. I have:
describe House::Room do
describe '.populate' do
let(:info) {
$info = {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to eq(nil)
end
end
end
You'll need to change the let assignment to:
describe House::Room do
describe '.populate' do
let(:info) {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to be(nil)
end
end
end
That should make your code work as you expect.
However, you could also use another matcher, like 'be_within' if you wanted to be more specific, or write several expect statements in the same test, like 'expect to be an integer', 'expect to be greater than 0', etc... There is no limit to the number of expect statements you can have in an 'it' block, the test will only pass if all of the expectations are fulfilled. (That said, I believe best practice would be to split it up into individual tests.)
:speedis simply a numeric model attribute.
Both of these are passing:
it { is_expected.to allow_value( '1').for(:speed) }
it { is_expected.not_to allow_value( '1', 'fff' ).for(:speed) }
With a minor change, the second one is not passing:
it { is_expected.to allow_value( '1').for(:speed) }
it { is_expected.not_to allow_value( '1' ).for(:speed) }
Apparently, if there's a single passing value in the not_to allow_value expression the whole list of values passes.
I'm not quite understanding if it's working as intended and I'm doing it wrong, or if it's a bug.
It seems like it's a bug according to how the documentation explains it.
I want to test an iterator using rspec. It seems to me that the only possible yield matcher is yield_successive_args (according to https://www.relishapp.com/rspec/rspec-expectations/v/3-0/docs/built-in-matchers/yield-matchers). The other matchers are used only for single yielding.
But yield_successive_args fails if the yielding is in other order than specified.
Is there any method or nice workaround for testing iterator that yields in any order?
Something like the following:
expect { |b| array.each(&b) }.to yield_multiple_args_in_any_order(1, 2, 3)
Here is the matcher I came up for this problem, it's fairly simple, and should work with a good degree of efficiency.
require 'set'
RSpec::Matchers.define :yield_in_any_order do |*values|
expected_yields = Set[*values]
actual_yields = Set[]
match do |blk|
blk[->(x){ actual_yields << x }] # ***
expected_yields == actual_yields # ***
end
failure_message do |actual|
"expected to receive #{surface_descriptions_in expected_yields} "\
"but #{surface_descriptions_in actual_yields} were yielded."
end
failure_message_when_negated do |actual|
"expected not to have all of "\
"#{surface_descriptions_in expected_yields} yielded."
end
def supports_block_expectations?
true
end
end
I've highlighted the lines containing most of the important logic with # ***. It's a pretty straightforward implementation.
Usage
Just put it in a file, under spec/support/matchers/, and make sure you require it from the specs that need it. Most of the time, people just add a line like this:
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
to their spec_helper.rb but if you have a lot of support files, and they aren't all needed everywhere, this can get a bit much, so you may want to only include it where it is used.
Then, in the specs themselves, the usage is like that of any other yielding matcher:
class Iterator
def custom_iterator
(1..10).to_a.shuffle.each { |x| yield x }
end
end
describe "Matcher" do
it "works" do
iter = Iterator.new
expect { |b| iter.custom_iterator(&b) }.to yield_in_any_order(*(1..10))
end
end
This can be solved in plain Ruby using a set intersection of arrays:
array1 = [3, 2, 4]
array2 = [4, 3, 2]
expect(array1).to eq (array1 & array2)
# for an enumerator:
enumerator = array1.each
expect(enumerator.to_a).to eq (enumerator.to_a & array2)
The intersection (&) will return items that are present in both collections, keeping the order of the first argument.
I have a bunch of very repetitive rspec tests that all have the same format:
it "inserts the correct ATTRIBUTE_NAME" do
#o.ATTRIBUTE_NAME.should eql(VALUE)
end
It would be nice if I could just make one line tests like:
compare_value(ATTRIBUTE_NAME, VALUE)
But shoulda doesn't seem to be geared toward these types of tests. Are there other alternatives?
Sometimes I regret exposing subject as an end-user device. It was introduced to support extensions (like shoulda matchers), so you can write examples like:
it { should do_something }
Examples like this, however, do not read well:
it { subject.attribute.should do_something }
If you're going to use subject explicitly, and then reference it explicitly in the example, I recommend using specify instead of it:
specify { subject.attribute.should do_something }
The underlying semantics are the same, but this ^^ can be read aloud.
I would write a custom RSpec helper if you want it to read more clearly and be only 1 line. Suppose we have the following class we want to test:
class MyObject
attr_accessor :first, :last, :phone
def initialize first = nil, last = nil, phone = nil
self.first = first
self.last = last
self.phone = phone
end
end
We could write the following matcher:
RSpec::Matchers.define :have_value do |attribute, expected|
match do |obj|
obj.send(attribute) == expected
end
description do
"have value #{expected} for attribute #{attribute}"
end
end
Then to write the tests we could do something like:
describe MyObject do
h = {:first => 'wes', :last => 'bailey', :phone => '111.111.1111'}
subject { MyObject.new h[:first], h[:last], h[:phone] }
h.each do |k,v|
it { should have_value k, v}
end
end
If you put all of this in a file call matcher.rb and run it the following is output:
> rspec -cfn matcher.rb
MyObject
should have value wes for attribute first
should have value bailey for attribute last
should have value 111.111.1111 for attribute phone
Finished in 0.00143 seconds
3 examples, 0 failures
I found this which works great:
specify { #o.attribute.should eql(val) }
subject { #o }
it { attribute.should == value }