RSpec combine block matcher with non-block matcher - ruby

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.

Related

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

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.

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.

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.

Dynamically define many methods with same implementation but arbitrary arguments

I have many methods like these two:
def create_machine(name, os_type_id, settings_file='', groups=[], flags={})
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
args = method(__method__).parameters.map { |arg| arg[1] }
soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
VirtualBoxAPI.send_request(#cl.conn, soap_method, #this.merge(soap_message))
end
def register_machine(machine)
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
args = method(__method__).parameters.map { |arg| arg[1] }
soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
VirtualBoxAPI.send_request(#cl.conn, soap_method, #this.merge(soap_message))
end
They have the same implementation but different number of different arguments. There will be tens of such methods in each of tens of classes. So I thought I'd use some meta-programming to minimize the code repetition.
I was trying to do this via define_method and wanted to end up in something like this:
vb_method :create_machine, :args => [:name, :os_type_id], :optional_args => [:settings_file, :groups, :flags]
But I can't find a way to pass arbitrary number of named (non-splat) arguments to define_method (I thought splat argument will make documenting the methods hard to impossible also will make the resulting API inconvenient).
What would be the best way to deal with this (using Ruby 2.0)?
UPD
Another way to do this is defining a method vb_method:
def vb_method(*vb_meths)
vb_meths.each do |meth|
define_method(meth) do |message={}|
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{meth}".to_sym
VirtualBoxAPI.send_request(#cl.conn, soap_method, #this.merge(message))
end
end
end
And then the class would have a call like this:
vb_method :create_machine, :register_machine
But is this case I will need to always call the methods with hash as an argument:
machine = vb.create_machine(name: 'my_vm', os_type_id: 'Windows95')
And that's exactly what I'm trying to avoid because I think in this case the resulting API can't be documented and is not convenient to use.
Stop trying to avoid option hashes. That's the "Ruby way" of doing things. They aren't impossible to document and several mainstream Ruby libraries use them this way (the first that come to mind are ActiveRecord and Mysql2).
Note that you can provide a default argument to the option hash, which serves as documentation and allows you to reduce code repetition.
Also, think about how your code would work if you could (somehow) pass an arbitrary number of named arguments to define_method. How would users remember which arguments are which? They would need to memorize the order and meaning of all the different positional arguments to all the different methods defined this way. When you have many similar methods with arguments of varying meanings, it's very difficult to keep everything straight. Keyword arguments (which is essentially what Ruby's option hashes are) were specifically created to avoid this situation.
If you're worried about error checking, define a helper method that checks the option hash for missing/unrecognized keys and raises an informative exception:
def validate_options(known, opts)
opts.each_key { |opt| raise "Unknown option: #{opt}" unless known.include?(opt) }
known.each { |opt, required| raise "Missing required option: #{opt}" if required and not opts.include?(opt) }
end

RSpec attribute hash

Can someone explain to me what is wrong here. Learning RSpec - I am receiving a failed test with message - expected: "Miller" got: nil. I thought that the second 'before block' would simply merge the middle_name with the original #valid_attributes. What is the correct way to do this. I know that I can simply say p.middle_name to assign it but I am trying to learn the concepts of RSpec.
describe Person do
describe "Validations" do
subject { p }
before { #valid_attributes={first_name: "Joe", last_name: "Sample"} }
...
context "with optional middle name" do
let(:p) { Person.new(#valid_attributes) }
before { #valid_attributes.merge({middle_name: "Miller"}) }
its(:middle_name) { should eq("Miller") }
end
end
end
Your problem: Hash#merge returns a new hash, you want Hash#update.
How to do it better? This is not just an advice for testing, but for programming in general: if you update and reuse variables you're gonna hit some problems with state; take a more functional approach. In this particular case you should use factory_girl or similar gem to easily create objects without having a #valid_attributes being updated who knows where.

Resources