Rspec expect(method) vs expect(method).to be_true - ruby-on-rails

I've just started diving into TDD with RSpec and Rails and I've found that if I'm testing a method that should come out to be true that the test will work if i use expect(method) instead of expect(method).to be_true.
it "is true" do
expect(subject).to be_true
#Works the same as...
expect(subject)
end
Is there any reason to not use the expect without .to, will it break the tests or not catch certain failures? Just wondering.

You are not achieving what you think you are. expect(false) does not fail. expect(true) only succeeds because nothing is being tested.
For example if you had a spec with the following:
describe "something" do
it "does something" do
false
puts expect(false)
# anything that evaluates to false
end
end
You would get something like this when you run rspec:
#<RSpec::Expectations::ExpectationTarget:0x007ff0b35a28b8\>
.
Finished in 0.00061 seconds
1 example, 0 failures
Essentially you are not asserting anything and expect(subject) evaluates to ExpectationTarget.

I think it is a coincidence that the latter works.
Every expect statement evaluates to true or false one way or the other.
For example
arr = ['a', 'b', 'c']
expect(arr).to include('a')
will return true because arr includes 'a'.
So in your case expect(subject) and expect(subject).to be_true is somewhat equivalent to the following:
bool = true
if bool
# Do something here
if bool == true
# Do something here
To make a long story short, it doesn't matter, but I would prefer expect(subject).to be_true for readability purposes.

Related

Testing if a method returns a `non-null` value

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.)

Rspec for Validation

In Model
validates :max_panels, :if => :depot?, :numericality => true
I am writing an rspec for the above validation and found something confusing
it { should validate_numericality_of(:max_panels) if :depot? }
When ran this test case got error like
1) Site spec for valid sites - Validations
Failure/Error: it { should validate_numericality_of(:max_panels) if :depot? }
Expected errors to include "is not a number" when max_panels is set to "abcd", got errors: ["format can't be blank (nil)", "illumination can't be blank (nil)", "illumination_period can't be blank (nil)", "vertical_size must be between 0.1 and 30 metres (nil)", "horizontal_size must be between 0.1 and 200 metres (nil)", "site_authorisation_id must have a valid authorisation (nil)"]
But when i added unless in my test case it got passed, Can anybody please explain me regarding it as i am new to Rspec. Also suggest how i can write the correct rspec for above validation.
it { should validate_numericality_of(:max_panels) if :depot? unless true }
Brief looking at the source of validate_numericality_of matcher shows that it doesn't contain explicit support for :if conditions. This may be handled by base matchers, but anyway, here's an alternative idea about the testing: prepare object, attempt validation and check error messages.
Something along the lines of:
describe 'numericality validation' do
subject(:instance) { described_class.new(params) }
before { instance.valid? }
context 'when depot' do
let(:params) { { max_panels: 'abcd', depot: true} }
it { expect(instance.errors.messages[:max_panels]).to eq 'is not a number' }
end
context 'when not depot' do
let(:params) { { max_panels: 'abcd', depot: false} }
it { expect(instance.errors.messages[:max_panels]).to eq nil }
end
end
unless true will never happen, so your it is likely not running the should and looks as though it passed.
unless is not rspec, that is pure ruby. You have told rspec you have a test (it), and that the should will never run. This will show as a passed test since the it did not fail.
Another part of this is to not use conditionals in your tests. They should be predictable. Don't use if or unless to determine whether or not to run assertions.
Lastly: Your error from rspec shows a list of validation errors, not of them say anything about what you're asserting. This test has multiple issues, but is flawed even in the should

ruby rspec conversion from "should" to "expect" with block

I'm handling a set of rspec programs and pc seems to be forcing me to convert "should" questions to "expect".
Have been able to handle most, but having problems with the following rspec setup.
Most of the other 'should' formatting involves an answer should == something and is easily converted to expect(passed_in_value).to eql(returned_value).
In this case though, I believe it is passing in a block to add to a given number, however, i and unable to just convert it to
expect(end).to eql(6) or whatever the returned value should be.
Take a look and if you have any thoughts, please pass them on
it "adds one to the value returned by the default block" do
adder do
5
end.should == 6
end
it "adds 3 to the value returned by the default block" do
adder(3) do
5
end.should == 8
end
There're several methods to do that.
result = adder(3) do
5
end
expect(result).to eq(8)
expect do
adder(3) do
5
end
end.to eq(8)
block = -> do
5
end
expect(adder 3, &block).to eq(8)
Example from comments with respond_to:
it "has a #sum method" do
[].should respond_to(:sum) #old syntax
expect([]).to respond_to(:sum) #new syntax
end

Rspec-Rails: Testing a method with a lot of combinations of arguments

I have a method that I want to test for different paramters if it does the right thing.
What I am doing right now is
def test_method_with(arg1, arg2, match)
it "method should #{match.inspect} when arg2 = '#{arg2}'" do
method(arg1, FIXEDARG, arg2).should == match
end
end
context "method with no info in arg1" do
before (:each) do
#ex_string = "no info"
end
test_method_with(#ex_string, "foo").should == "res1"}
test_method_with(#ex_string, "bar").should == "res1"}
test_method_with(#ex_string, "foobar").should == "res1"}
test_method_with(#ex_string, "foobar2").should == "res2"}
test_method_with(#ex_string, "barbar").should == "res2"}
test_method_with(#ex_string, nil).should == nil}
end
But this is really not so DRY to repeat the method over and over again... What would be a better way to accomplish this? More in the way the "table" option of cucumber does it (it is just about the right behaviour of a helper method, so to use cucumber does not seem right).
Your method expects 3 arguments, but you're passing it two.
That being said, you can write a loop to call it multiple times, like this:
#don't know what arg2 really is, so I'm keeping that name
[ {arg2: 'foo', expected: 'res1'},
{arg2: 'bar', expected: 'res1'},
#remaining scenarios not shown here
].each do |example|
it "matches when passed some fixed arg and #{example[:arg2]}" do
method(#ex_string, SOME_CONSTANT_I_GUESS,example[:arg2]).should == example[:expected]
end
end
This way, you only have one example (aka the it call) and your examples are extracted to a data table (the array containing the hashes).
I think your approach is fine if you remove the passing of the instance variable #ex_string. (And the match occurring only in test_method_with as Kenrick suggests.) That said you could use a custom matcher:
RSpec::Matchers.define :match_with_method do |arg2, expected|
match do
method(subject, arg2) == expected
end
failure_message_for_should do
"call to method with #{arg2} does not match #{expected}"
end
end
it 'should match method' do
"no info".should match_with_method "foo", "res1"
end
matchers can be placed in the spec helper file for access from several specs.

RSpec: Match an array of strings by regex

I am testing validation of my models with rspec and am expecting an error message. However, the exact text of the message is likely to change, so I want to be a bit more forgiving and only check for a partial message.
Since the Spec::Matchers::include method only works for strings and collections, I'm currently using this construct:
#user.errors[:password].any?{|m|m.match(/is too short/)}.should be_true
This works but seems a bit cumbersome to me. Is there a better (i.e., faster or more ruby-like) way to check an array for the inclusion of a string by regex, or perhaps an rspec matcher that does just this?
I would recommend doing
#user.errors[:password].to_s.should =~ /is too short/
Simply because it will give you a more helpful error when it fails. If you use be_any then you get a message like this...
Failure/Error: #user.errors[:password].should be_any{ |m| m =~ /is too short/}
expected any? to return true, got false
However, if you use the to_s method then you will get something like this:
Failure/Error: #user.errors[:password].to_s.should =~ /is too short/
expected: /is to short/
got: "[]" (using =~)
Diff:
## -1,2 +1,2 ##
-/is too short/
+"[]"
So you can see the reason for the failure and don't have to go digging much to figure out why it is failing.
Using RSpec 3 expect syntax with matchers composing:
To match all:
expect(#user.errors[:password]).to all(match /some message/)
To match any:
expect(#user.errors[:password]).to include(match /some message/)
expect(#user.errors[:password]).to include a_string_matching /some message/
You can put the following code in spec/support/custom_matchers.rb
RSpec::Matchers.define :include_regex do |regex|
match do |actual|
actual.find { |str| str =~ regex }
end
end
Now you can use it like this:
#user.errors.should include_regex(/is_too_short/)
and be sure you have something like this in spec/spec_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
I don't think it makes a performance difference, but a more RSpec-like solution would be
#user.errors[:password].should be_any { |m| m =~ /is too short/ }
Both above answer are good. I would, however, use the newer Rspec expect syntax
#user.errors[:password].to_s.should =~ /is too short/
becomes
expect(#user.errors[:password].to_s).to match(/is too short/)
More great info here: http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
My solution to this is similar to #muirbot's. I use a custom matcher. However, I use the real include matcher, but augment it with the custom matcher as an argument. Load this somewhere before your suite runs (e.g. in spec/support/matchers.rb, in turn loaded by spec/spec_helper.rb):
RSpec::Matchers.define(:a_string_matching) do |expected|
match do |actual|
actual =~ expected
end
end
Then your expectation can be written like this:
expect(#user.errors[:password]).to include(a_string_matching(/is too short/))
Just another option
#user.errors[:password].grep /is too short/

Resources