Is there are a difference between change(receiver, message) and change { block } - ruby

I found that assertion method change used in two different ways
expect { createRecord.call }.to change(Record, :count).by(1)
vs
expect { createRecord.call }.to change { Record.count }.by(1)
I tried to dig into source code, and found that passed block will be called if block provided.
Without block message will be "sent" to the receiver.
I was wonder are there some scenarios where one should be preferred over another?

Not everything maps so neatly to the send approach. For example:
expect { createRecord.call }.to change { Record.count(OtherRecord.param) }.by(1)
Where there's no way to represent that as a simple send(*args) as:
expect { createRecord.call }.to change(Record, :count, OtherRecord.param).by(1)
This evaluates OtherRecord.param as the expect line executes, not at the right before and after interval.
It's provided for feature-completeness and to give you complete control.
In short the block form runs the exact block twice while the other evaluates the arguments once and makes a send call twice.

Related

RSpec combine block matcher with non-block matcher

I would like to check if my class is creating a new object and returning an instance of it. My idea was to combine change matcher with be_instance_of matcher but RSpec doesn't allow to do it.
expect { subject.call }.to change { Model.count }.by(1).and be_an_instance_of(Model)
I don't want to split it into two different expects with one without the block to avoid multiple invocations of the same method.
What is the general approach to this kind of situations? How should I handle my case?
You can define subject as subject.call and do this:
specify do
expect { subject }.to change { Model.count }.by(1)
expect(subject).to be_an_instance_of(Model)
end
So if previously your subject was
subject { Foo.new }
make it
subject { Foo.new.call }
let and subject calls are memoized, so it'll be only called once.
There is only one problem: if the first expectation fails - the second will not run (and this is SomethingToAvoidInSpecs™) so consider aggregating failures to remedy it.

Difference between if and case - ruby

This code
if response != Net::HTTPNoContent
raise Exception
end
puts "OK"
prints an exception, whereas this code
case response
when Net::HTTPNoContent
puts "OK"
else
raise Exception
end
prints "OK" to the console. I'd like to know what's going on.
If you need more details, let me know.
Your response variable is, I assume, a response object returned from using net/http. That object's type will be Net::HTTPNoContent.
In your first if variant, you are checking to see if your response object is equal to the Net::HTTPNoContent class. It's not going to be. The net/http library isn't going to return a class when your request is sent, it's going to return an object containing all of the information about your request's response (and will be of type Net::HTTPNoContent, or some other class depending on the result of the request).
In your case variant, however, things work a little differently. Ruby tries to be intelligent about what it does with the predicates you give each when branch. It will do an triple-equals (===) (like #is_a?, in this example, though it does other things) comparison against each branch, which evaluates to true if the object in question's class is (or is descended from) the class specified in the branch (or, of course, if the object is indeed equal).
(In Ruby, classes are objects too, which is why you can compare response to the class itself, like in your if version, and it still make sense to the interpreter.)
So this is best explained by rewriting your if version:
if !response.is_a?(Net::HTTPNoContent)
raise Exception
end
puts "OK"
Simple example:
a = 'a'
if a != String
p '-'
else
p '+'
end
case a
when String
p '+'
else
p '-'
end
Return:
#=> -
#=> +
It means that in first example you try to check if value of some response is equal to class. When you use case when String it would check if a.is_a? String

How to pass a block

For the sake of simplicity, I've tried to abstract the problem down to its core elements. I've included a small piece of functionality wherein I use Socket to show that I want to pass the block further down into a method which is a black box for all intents and purposes. I'm also passing a constant True for the sake of showing I want to pass arguments as well as a yield block.
With all that being said, if I small have a hierarchy of calls as such:
def foo(use_local_source)
if use_local_source
Socket.unix("/var/run/my.sock") &yield
else
Socket.tcp("my.remote.com",1234) &yield
end
end
foo(True) { |socket|
name = socket.read
puts "Hi #{name}, I'm from foo."
}
How can I pass the implicitly declared block right down through foo and into Socket as if I were calling Socket.tcp(...) { ... } directly.
I know I could set it as an argument, but it doesn't feel idiomatic to Ruby. Is this also untrue and I should pass it as an argument? I've tried combinations of & and *, and I get a range of exception.
def foo(use_local_source)
if use_local_source
yield Socket.unix("/var/run/my.sock")
else
yield Socket.tcp("my.remote.com",1234)
end
end
From the docs for yield:
Yields control back to the context that resumed the fiber, passing along any arguments that were passed to it.

Why is negative specific exception expectation deprecated in RSpec?

Seems like I am permitted to specify exception class for .to but not for .not_to?
What are exact reasons of this?
Failure/Error: expect{ smth }.not_to raise_exception SomeExceptionClass
ArgumentError:
`expect { }.not_to raise_error(SpecificErrorClass)` is not valid, use `expect { }.not_to raise_error` (with no args) instead
To expand on Nakilon's answer a little:
This is a design decision on their part. It would appear that they think this is not a good test to specify, because if you expect that a certain error not be raised, then the test will pass if:
no error is raised
some other error is raised
...which is at least, imprecise. Probably your code only wants to do one of these things.
That appears to be the reasoning, anyway -- I wouldn't like to say how fair it was.
From the changelog:
Make expect { }.to_not raise_error(SomeSpecificClass, message), expect { }.to_not raise_error(SomeSpecificClass) and expect { }.to_not raise_error(message) invalid, since they are prone to hiding failures.
Instead, use expect { }.to_not raise_error (with no args). (Sam
Phippen)
Seems like they realised that there was no reason to deprecate the exception class parameter, so it currently works: https://www.relishapp.com/rspec/rspec-expectations/v/3-5/docs/built-in-matchers/raise-error-matcher
For example, in Selenium, after checking for some condition to become true:
Selenium::WebDriver::Wait.new(
message: message,
timeout: timeout,
).until do
f() == x
end
it's now easy to also wait that it does not become false in next seconds by negating the timeout exception:
begin
Selenium::WebDriver::Wait.new(
timeout: timeout,
).until do
f() != x
end
raise message
rescue Selenium::WebDriver::Error::TimeOutError
end

How to test Ruby class with default parameters using RSpec

I have a class called Grid:
class Grid
attr_reader :rows, :columns
def initialize(rows=20, columns=20)
#rows = rows
#columns = columns
end
end
I want to test that the rows and columns fields return 20 by default and return whatever integer is supplied otherwise. I don't know the best way to do this.
I "solved" this by creating two Grid instances. One has supplied values for rows and columns. The other one does not have supplied values and thus uses the default values of 20.
require_relative 'spec_helper.rb'
describe Grid do
let(:grid) {Grid.new(15, 15)}
let(:gridNoParameters) {Grid.new()}
subject { grid }
describe "#rows" do
its(:rows) { should eq(15) }
context "when parameter not supplied" do
subject { gridNoParameters }
its(:rows) { should eq(20) }
end
end
describe "#columns" do
its(:columns) { should eq(15) }
context "when parameter not supplied" do
subject { gridNoParameters }
its(:columns) { should eq(20) }
end
end
Is this the best way to test? Any help is appreciated as I am pretty new to Rspec and test driven development.
I would lay out the spec like this:
describe Grid do
context "#initialize" do
context "with parameters" do
let(:grid) { Grid.new(15, 15) }
it "should use specified values" do
expect(grid.rows).to eq 15
expect(grid.columns).to eq 15
end
end
context "without parameters" do
let(:grid) { Grid.new }
it "should use defaults" do
expect(grid.rows).to eq 15
expect(grid.columns).to eq 15
end
end
end
end
Take a look at BetterSpecs for ideas how to organize specs, and to use expect notation, etc. I'm not a huge fan of using subject here, but it's up to you, as it is personal preference.
I think your approach is fine.
You're testing the default
You're testing the non-default
This particular test strikes me as a bit defensive/paranoid, but if it's critical that a test fails if/when someone accidentally changes or removes the default values, then I guess this test is fine.
Testing constructors is always a little weird to me, if all the constructor does is copy the input parameters to the instance variables. It borders on testing that the = operator in Ruby actually works, which is a bit silly. It also borders on what I call, "testing against typos," where the test will only fail if a developer makes a blindingly obvious mistake or removes a default from a method without thinking it through. The thing is, I don't believe that any amount of testing can successfully protect against sloppiness.
There are more complex situations where I might be this careful, but the class you're presenting as an example here is so simple and so straightforward that I don't think it needs anything more than this, personally.

Resources