rspec - how to change these simple "x.should == 'val' lines - ruby

For rspec change to use expect().to instead of .should, how would I change the folowing:
...
describe '#push_state' do
def process(klass)
state = OpenStruct.new(:namespace => "ROOT", :scope => :instance, :owner => "ROOT")
klass.new(state, nil).process
end
it "should push and return all old state info after block" do
class PushStateHandler1 < Handlers::Base
def process
push_state(:namespace => "FOO", :scope => :class, :owner => "BAR") do
namespace.should == "FOO"
scope.should == :class
owner.should == "BAR"
end
namespace.should == "ROOT"
owner.should == "ROOT"
scope.should == :instance
end
end
...
Every variety I have fails, usuaully with undefined method expect for #<PushStateHandler1:0x000000010a3470>
For example I've tried:
expect(namespace).to eq "FOO"
expect{(namespace) eq "FOO"}.to be_true

The expect method is defined in the scope of the it block. In this case, you've defined a class within that block, an instance method within that class and a block within that instance method. I don't know the specify Ruby scoping rules, but it's not surprising that the variables in that inner block are not interpreted in the scope of the outer block. The should method only worked because it's defined on Object.
So, to answer your questions, I don't know off hand how to translate from should to expect except to say that you somehow have to pass along and use the binding from the outer block to evaluate whatever expect expressions you're trying to use.

Related

Testing gets in rspec (user input)

My class has this #run method that so far is just this, to test the testing:
def run
puts "Enter 'class' to create a new class."
input = $stdin.gets.chomp
binding.pry
And in the tests so far I've got
allow($stdin).to receive(:gets).and_return 'class'
cli.run
Doing it this way I am able to see, in the pry session, that input has been set to 'class', as intended.
Is there a way to do with without adding $stdin to my call to gets in my method itself? i.e., input = gets.chomp
I've tried allow(cli.run).to receive(:gets).and_return 'class'
But then in the pry session, input is equal to the first line of the spec file!
You can avoid this as such:
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
describe 'gets' do
it 'belongs to Kernel' do
allow_any_instance_of(Kernel).to receive(:gets).and_return('class')
expect(run).to eq('class')
end
end
The method gets actually belongs to the Kernel module. (method(:gets).owner == Kernel). Since Kernel is included in Object and almost all ruby objects inherit from Object this will work.
Now if run is an instance method scoped in a Class I would recommend scoping the stubbing a bit more such that:
class Test
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
end
describe 'gets' do
it 'can be stubbed lower than that' do
allow_any_instance_of(Test).to receive(:gets).and_return('class')
expect(Test.new.run).to eq('class')
end
# or even
it 'or even lower than that' do
cli = Test.new
allow(cli).to receive(:gets).and_return('class')
expect(cli.run).to eq('class')
end
end
Example

Datamapper chaining conditions seem to be sharing conditions

I have two models;
class Foo
belongs_to :bar
end
class Bar
has 1, :foo
end
This all works fine, relationship working fine and so on. The requirement arose for us to override the "all" method on Foo, to always put a condition into any query. We did this like so;
class Foo
def self.all(opts = {})
super(opts.merge(:hidden => false))
end
end
And all that works too, but when I run the following command;
Foo.all.bar
It gives me the following error:
"condition :hidden does not map to a property in Bar"
That line worked totally fine before I overrode 'all'. I don't understand why it's applying "hidden" to the 'bar' object rather than the 'foo' object!
If anyone else encounters this, here is how I eventually fixed it. The problem was in the "query" object returned by datamapper - it holds a reference to a "model" and if that isn't the model your condition is on, you get the error I was getting. Basically the way around it is by doing a check against the model like so;
class << self
#Alias out the original all
alias :unfiltered_all :all
def all(opts = {})
query = unfiltered_all(opts)
if(query.model.ancestors.include? self)
return query.unfiltered_all(:hidden => false)
else
return query.all(:foo => { :hidden => false } )
end
end
end

How to use yield self within Rspec

This is a description of how to create a helper method in Rspec taken from the Rspec book (page 149). This example assumes that there is a method called 'set_status' which is triggered when the 'Thing' object is created.
Both sets of code create a new 'Thing' object, set the status, then do 'fancy_stuff'. The first set of code is perfect clear to me. One of the 'it' statements it triggered, which then calls the 'create_thing' method with options. A new 'Thing' object is created and the 'set_status' method is called with the 'options' attribute as the parameter.
The second set of code is similar. One of the 'it' statements is triggered, which then calls the 'given_thing_with' method while passing ':status' hash assignment as a parameter. Within the 'given_thing_with' method the 'yield' is triggered taking the 'Thing.new' as a parameter. This is where I am having trouble. When I try to run this code I get an error of "block given to yield". I understand that whatever attributes that are passed by yield will be returned to the 'thing' in pipe brace from the 'it' statement that called the 'given_thing_with' method. I can get the new
What I don't understand is why the code block is not called in the 'given_thing_with' method after the 'yield' command. In other words, I can't code in that block to run.
Thanks in advance for your help.
The remainder of this question is quoted directly from the Rspec book:
describe Thing do
def create_thing(options)
thing = Thing.new
thing.set_status(options[:status])
thing
end
it "should do something when ok" do
thing = create_thing(:status => 'ok')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
it "should do something else when not so good" do
thing = create_thing(:status => 'not so good')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
One idiom you can apply to clean this up even more is to yield self from initializers in your objects. Assuming that Thing's initialize() method does this and set_status() does as well, you can write the previous like this:
describe Thing do
def given_thing_with(options)
yield Thing.new do |thing|
thing.set_status(options[:status])
end
end
it "should do something when ok" do
given_thing_with(:status => 'ok') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
it "should do something else when not so good" do
given_thing_with(:status => 'not so good') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
end
The example in the book is a bit confusing because the implementation of Thing is not shown. To make this work you need to write Thing like so:
class Thing
def initialize
yield self
end
end
When given_thing_with is called it yields a new Thing, which will yield itself when it is constructed. This means that when the inner code block (the one containing thing.set_status) is executed it will have a reference to he newly built Thing.
There are 2 issues with the code from book.
1. Setting up the initializer to yield itself
When the Thing object is created, it needs an initializer and need yield itself.
class Thing
def initialize
yield self
end
end
However, this alone will still causes an error, at least on my system, which is Ruby 1.9.3. Specifically, the error is 'block given to yield (SyntaxError)'. This doesn't make much sense, since that is what we want it to do. Regarless, that is the error I get.
2. Fixing the 'block given to yield' error
This is not as obvious and has something to do with either Ruby or the 'yield' statement, but creating a block using 'do...end' as was written in the book and is shown below causes the error.
yield Thing.new do |thing|
thing.set_status(options[:status])
end
Fixing this error is simlpy a matter of creating the block using braces, '{...}', as is shown below.
yield Thing.new { |thing|
thing.set_status(options[:status])
}
This is not good form for multiline Ruby code, but it works.
Extra. How the series of yields works to set the parameters of the 'Thing' object
The problem is already fixed, but this explains how it works.
the "caller block" calls 'given_thing_with' method with a parameter
that method yields back to the "caller block" a new "Thing" and a block (I'll call it the "yield block")
to execute the "yield block", the Thing class needs the initialization and 'yield self', otherwise the 'set_status' method will never be run because the block will be ignored
the new "Thing" is already in the "caller block" and has it's status set and now the relevant method is executed

Why is the stubbed method not called?

It seems I understood something wrong. I have a class
module Spree
class OmnikassaPaymentResponse
#...
# Finds a payment with provided parameters trough ActiveRecord.
def payment(state = :processing)
Spree::Payment.find(:first, :conditions => { :amount => #amount, :order_id => #order_id, :state => state } ) || raise(ActiveRecord::RecordNotFound)
end
end
end
Which is specced in Rspec:
describe "#payment" do
it 'should try to find a Spree::Payment' do
Spree::Payment.any_instance.stub(:find).and_return(Spree::Payment.new)
Spree::Payment.any_instance.should_receive(:find)
Spree::OmnikassaPaymentResponse.new(#seal, #data).payment
end
end
This, however, always throws ActiveRecord::RecordNotFound. I expected any_instance.stub(:find).and_return() to make sure that whenever, wherever I call a #find on whatever instance I happen to have of Spree::Payment, it returns something.
In other words: I would expect the stub.and_return would avoid getting to || raise(ActiveRecord::RecordNotFound). But it does not.
Is my assumption wrong, my code? Something else?
In your case find is not an instance method, but a class method of Spree::Payment. That means you should stub it directly without any_instance like that:
Spree::Payment.stub(:find).and_return(Spree::Payment.new)

FactoryGirl in Rails - Associations w/ Unique Constraints

This question is an extension to the one raised here:
Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors
The answer offered has worked perfectly for me. Here's what it looks like:
# Creates a class variable for factories that should be only created once.
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key)
begin
##singletons[factory_key] = FactoryGirl.create(factory_key)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
##singletons[factory_key]
end
end
end
The issue that has come up for me is when I need to manually build an association to support a polymorphic association with a uniqueness constraint in a hook. For example:
class Matchup < ActiveRecord::Base
belongs_to :event
belongs_to :matchupable, :polymorphic => true
validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] }
end
class BaseballMatchup < ActiveRecord::Base
has_one :matchup, :as => :matchupable
end
FactoryGirl.define do
factory :matchup do
event { FactoryGirl::Singleton.execute(:event) }
matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) }
home_team_record '10-5'
away_team_record '9-6'
end
factory :baseball_matchup do
home_pitcher 'Joe Bloe'
home_pitcher_record '21-0'
home_pitcher_era 1.92
home_pitcher_arm 'R'
away_pitcher 'Jack John'
away_pitcher_record '0-21'
away_pitcher_era 9.92
away_pitcher_arm 'R'
after_build do |bm|
bm.matchup = Factory.create(:matchup, :matchupable => bm)
end
end
end
My current singleton implementation doesn't support calling FactoryGirl::Singleton.execute(:matchup, :matchupable => bm), only FactoryGirl::Singleton.execute(:matchup).
How would you recommend modifying the singleton factory to support a call such as FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) OR FactoryGirl::Singleton.execute(:matchup)?
Because right now, the above code will throw uniqueness validation error ("Event is already taken") everytime the hook is run on factory :baseball_matchup. Ultimately, this is what needs to be fixed so that there isn't more than one matchup or baseball_matchup in the DB.
As zetetic has mentioned, you can define a second parameter on your execute function to send the attributes to be used during the call to FactoryGirl.create, with a default value of an empty hash so it didn't override any of them in the case you don't use it (you don't need to check in this particular case if the attributes hash is empty).
Also notice that you don't need to define a begin..end block in this case, because there isn't anything to be done after your rescue, so you can simplify your method by defining the rescue as part of the method definition. The assignation on the case that the initialization was fine will also return the assigned value, so there is no need to explicitly access the hash again to return it. With all these changes, the code will end like:
# Creates a class variable for factories that should be only created once.
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key, attrs = {})
##singletons[factory_key] = FactoryGirl.create(factory_key, attrs)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
end
end
You need to do two things to make this work:
Accept attributes as an argument your execute method.
Key off of both the factory name and the attributes when creating the singleton factories.
Note that step 1 isn't sufficient to solve your problem. Even if you allow execute to accept attributes, the first call to execute(:matchup, attributes) will cache that result and return it any time you execute(:matchup), even if you attempt to pass different attributes to execute. That's why you also need to change what you're using as the hash key for your ##singletons hash.
Here's an implementation I tested out:
module FactoryGirl
class Singleton
##singletons = {}
def self.execute(factory_key, attributes = {})
# form a unique key for this factory and set of attributes
key = [factory_key.to_s, '?', attributes.to_query].join
begin
##singletons[key] = FactoryGirl.create(factory_key, attributes)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
##singletons[key]
end
end
end
The key is a string consisting of the factory name and a query string representation of the attributes hash (something like "matchup?event=6&matchupable=2"). I was able to create multiple different matchups with different attributes, but it respected the uniqueness of the event/matchupable combination.
> e = FactoryGirl.create(:event)
> bm = FactoryGirl.create(:baseball_matchup)
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> f = FactoryGirl.create(:event)
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm)
> m.id
3
Let me know if that doesn't work for you.
Ruby methods can have default values for arguments, so define your singleton method with an empty default options hash:
def self.execute(factory_key, options={})
Now you can call it both ways:
FactoryGirl::Singleton.execute(:matchup)
FactoryGirl::Singleton.execute(:matchup, :matchupable => bm)
within the method, test the options argument hash to see if anything hase been passed in:
if options.empty?
# no options specified
else
# options were specified
end

Resources