In rspec 1 I could do
describe "Something", :shared => true do
include SomeModule # which has the :a_method method
def a_method(options)
super(options.merge(:option => #attr)
end
it "foofoofoo" do
end
end
describe "Something else" do
before(:each) do
#attr = :s_else
end
it_should_behave_like "Something"
it "barbarbar" do
a_method(:name => "something else")
Something.find("something else").name.should == "Something else"
end
...
end
That is, I could use :shared => true to not only refactor examples but also share method definitions and attributes. I realize the example is contrived, but how would one write it in rspec >= 2 without touching the SomeModule module or the Something class?
You can do this with shared_examples_for
shared_examples_for "something" do
include SomeModule # which has the :a_method method
def a_method(options)
super(options.merge(:option => #attr))
end
it "foofoofoo" do
end
end
And call with it_behaves_like:
it_behaves_like "something"
EDIT
Joao correctly points out that this fails to include SomeModule for the examples in the describe block. The include would have to take place outside the shared example group, e.g. at the top of the spec file
include SomeModule # which has the :a_method method
# ...
shared_examples_for "something" do
def a_method(options)
super(options.merge(:option => #attr))
end
it "foofoofoo" do
end
end
David Chelimsky discusses some new features of shared examples in RSpec 2 which may be pertinent in this blog post.
Related
I have a class Foo:
class Foo
def a
"something" if true
end
end
I want an add_statement method to add new statements to the method, keeping the old implementation. Is it possible?
I want to do something like this:
foo = Foo.new
foo.extend_method(:a, &block)
so now the source of my a method should be something like this:
def a
"something" if true
&block
end
where &block is the code I passed as argument in extend_method.
You want to use alias_method_chain, or Module#prepend.
There are many tutorials / documentation about them on the net, for example "AVOIDING ALIAS_METHOD_CHAIN IN RUBY" or "Module.prepend: a super story".
For your specific example (when you want to extend the function outside of the class), you have to use alias_method_chain, added in a new module, and included in your original class using Foo.send.
You can find more details and examples in "When to use alias_method_chain".
Here is an aspect oriented example:
require 'aspector'
class Foo
def a
puts "in Foo.a"
end
end
aspect = Aspector do
before :a do
puts 'this should print first'
end
after :a do |result_of_a|
puts 'this should print last'
end
end
puts "\n\nplain instance"
foo = Foo.new
foo.a
puts "\n\napply to an instance"
aspect.apply(foo)
foo.a
puts "\n\napply to all instances of Foo"
aspect.apply(Foo)
Foo.new.a
You will need to 'gem install aspector'.
I'm trying to understand Ruby's refinements feature, and I've encountered a scenario I don't understand.
Take this example code:
class Traveller
def what_are_you
puts "I'm a Backpacker"
end
def self.preferred_accommodation
puts "Hostels"
end
end
module Refinements
module Money
def what_are_you
puts "I'm a cashed-up hedonist!"
end
module ClassMethods
def preferred_accommodation
puts "Expensive Hotels"
end
end
def self.included(base)
base.extend ClassMethods
end
end
refine Traveller do
include Money
end
end
Now, when I do this in the REPL:
Traveller.new.what_are_you # => I'm a Backpacker
Traveller.preferred_accommodation # => Hostels
using Refinements
Traveller.new.what_are_you # => I'm a cashed-up hedonist!
Traveller.preferred_accommodation # => Hostels (???)
Why is #what_are_you refined, but .preferred_accommodation is not?
As #MasashiMiyazaki explained, you have to refine two classes: Traveller and Traveller's singleton class. That actually allows you to simplify your code quite a bit:
module Money
refine Traveller do
def what_are_you
puts "I'm a cashed-up hedonist!"
end
end
refine Traveller.singleton_class do
def preferred_accommodation
puts "Expensive Hotels"
end
end
end
Traveller.new.what_are_you #=> I'm a Backpacker
Traveller.preferred_accommodation #=> Hostels
using Money
Traveller.new.what_are_you #=> I'm a cashed-up hedonist!
Traveller.preferred_accommodation #=> Expensive Hotels
Moreover, by putting the above three statements in a module, the refined versions of the two methods are confined to that module:
module M
using Money
Traveller.new.what_are_you #=> I'm a cashed-up hedonist!
Traveller.preferred_accommodation #=> Expensive Hotels
end
Traveller.new.what_are_you #=> I'm a Backpacker
Traveller.preferred_accommodation #=> Hostels
You need to call refine Traveller with singleton_class scope to overwrite class methods. By adding the following code to your Refinements module instead of self.included, you can get the expected result.
module Refinements
refine Traveller.singleton_class do
include Money::ClassMethods
end
end
This article(http://timelessrepo.com/refinements-in-ruby) will help you to understand Refinements more.
I would like to know what happened behind this snippet
How Ruby implements this syntax ?
feature "Course" do DO_SOMETHING end
scenario "A Course without name should not be accepted" do
end
Please give me some concrete direction or example
feature "Course" do
let(:school) {School.make!}
context "Logged in" do
before(:each) do
switch_to_subdomain(school)
end
context "In the new course form" do
before(:each) do
click_link("Courses")
click_link("New course")
end
scenario "New course" do
end
scenario "A Course without name should not be accepted" do
end
scenario "A new course should not be created if there is another one with the same name in the same school" do
end
end
end
end
In ruby, you can pass a block as the last parameter to a method, which can then either call it using yield, or treat it as an explicit variable, moving it around etc.
def scenario(name)
puts name
if block_given?
yield
end
end
def feature(name, &block)
puts name
if block_given?
scenario("called from feature", &block)
end
end
scenario("test") do puts "this" end
# => test
# => this
feature("test") do puts "this" end
# => test
# => called from feature
# => this
Also, since parentheses are optional in ruby, you can drop them receiving this syntax:
scenario "test" do
puts "this"
end
# => test
# => this
feature "test" do
puts "this"
end
# => test
# => called from feature
# => this
How can I mix a module into an rspec context (aka describe), such that the module's constants are available to the spec?
module Foo
FOO = 1
end
describe 'constants in rspec' do
include Foo
p const_get(:FOO) # => 1
p FOO # uninitialized constant FOO (NameError)
end
That const_get can retrieve the constant when the name of the constant cannot is interesting. What's causing rspec's curious behavior?
I am using MRI 1.9.1 and rspec 2.8.0. The symptoms are the same with MRI 1.8.7.
You want extend, not include. This works in Ruby 1.9.3, for instance:
module Foo
X = 123
end
describe "specs with modules extended" do
extend Foo
p X # => 123
end
Alternatively, if you want to reuse an RSpec context across different tests, use shared_context:
shared_context "with an apple" do
let(:apple) { Apple.new }
end
describe FruitBasket do
include_context "with an apple"
it "should be able to hold apples" do
expect { subject.add apple }.to change(subject, :size).by(1)
end
end
If you want to reuse specs across different contexts, use shared_examples and it_behaves_like:
shared_examples "a collection" do
let(:collection) { described_class.new([7, 2, 4]) }
context "initialized with 3 items" do
it "says it has three items" do
collection.size.should eq(3)
end
end
end
describe Array do
it_behaves_like "a collection"
end
describe Set do
it_behaves_like "a collection"
end
You can use RSpec's shared_context:
shared_context 'constants' do
FOO = 1
end
describe Model do
include_context 'constants'
p FOO # => 1
end
Is it possible to temporarily apply certain methods to a class for tests? I want to be able to run specs depending on many ways to apply it. While I could make a bunch of fixtures with different settings, I find it easier to just class_eval the model in the tests. For example:
describe "some context"
before do
Page.class_eval do
my_applying_method :some => :option
end
end
it "should..."
end
Then in another context block:
describe "another context without the gem applied"
before do
Page.class_eval do
# nothing here since I want to page to be as is
end
end
it "should do something else..."
end
But the problem with the last context block is that it has a modified class (modified in the context block above). Is it possible to reset a class after class_eval? How?
Thanks!
I hope there is a better way to do that but you can use this(and it's with a warning at the Foo = Foo_old line):
module Bar
def baz
end
end
class Foo
end
puts Foo.method_defined? :baz #=> false
Foo_old = Foo.dup # create a copy of our class
Foo.class_eval do
include Bar
end
puts Foo.method_defined? :baz #=> true
Foo = Foo_old
puts Foo.method_defined? :baz #=> false
You haven't clarified how you are modifying the class.
The remix library allows you to temporarily include a module and properly uninclude it later.
In general, it's probably safest to just duplicate the class and test the duplicate:
irb(main):001:0> class Foo; end
#=> nil
irb(main):002:0> Foo.dup.class_eval{ #x = 42 }
#=> 42
irb(main):003:0> Foo.class_eval{ #x }
#=> nil