Skip to content

Commit

Permalink
Allow to use each without attribute to test each element of subject.
Browse files Browse the repository at this point in the history
  • Loading branch information
ZenCocoon committed Oct 23, 2011
1 parent 36fec14 commit cdb2f5c
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 60 deletions.
25 changes: 25 additions & 0 deletions README.md
Expand Up @@ -2,6 +2,7 @@

rspec-subject-extensions adds `each` short-hand to generate a nested example group with
a single example that specifies the expected value of each attribute of the subject.
If no attribute is given, each element of the subject will be used.

## Documentation

Expand Down Expand Up @@ -30,6 +31,8 @@ tracker](https://github.com/ZenCocoon/rspec-subject-extensions/issues).

### Each

#### Using the singular name of the attributes

Creates a nested example group named by `each` and the submitted `attribute`,
and then generates an example for each attribute using the submitted block.

Expand All @@ -51,6 +54,28 @@ and then generates an example for each attribute using the submitted block.

The `attribute` can be a `Symbol` or a `String`.

#### Using no attribute. Ideal to test scopes

Creates a nested example group and then generates an example
for each instance using the submitted block.

# This ...
describe Object do
subject { Object.visible }
each { should be_visible }
end

# ... generates the same runtime structure as this:
describe Object do
describe "each instance" do
it "should be visible" do
subject.each do |element|
element.should be_visible
end
end
end
end

## Also see

* [http://github.com/rspec/rspec](http://github.com/rspec/rspec)
Expand Down
29 changes: 27 additions & 2 deletions features/README.md
@@ -1,9 +1,12 @@
rspec-subject-extensions adds `each` short-hand to generate a nested example group with
a single example that specifies the expected value of each attribute of the subject.
If no attribute is given, each element of the subject will be used.

## Each

Creates a nested example group named by +each+ and the submitted +attribute+,
#### Using the singular name of the attributes

Creates a nested example group named by `each` and the submitted `attribute`,
and then generates an example for each attribute using the submitted block.

# This ...
Expand All @@ -22,7 +25,29 @@ and then generates an example for each attribute using the submitted block.
end
end

The +attribute+ can be a +Symbol+ or a +String+.
The `attribute` can be a `Symbol` or a `String`.

#### Using no attribute. Ideal to test scopes

Creates a nested example group and then generates an example
for each instance using the submitted block.

# This ...
describe Object do
subject { Object.visible }
each { should be_visible }
end

# ... generates the same runtime structure as this:
describe Object do
describe "each instance" do
it "should be visible" do
subject.each do |element|
element.should be_visible
end
end
end
end

## Issues

Expand Down
33 changes: 33 additions & 0 deletions features/class_methods/each_element_of_subject.feature
@@ -0,0 +1,33 @@
Feature: each element of subject

Use the each() method as a short-hand to generate a nested example group with
a single example that specifies the expected value of each element of the
subject. This can be used with an implicit or explicit subject.

each() requires a block representing the example.

each { should be_visible }

Scenario: specify value of each element
Given a file named "example_spec.rb" with:
"""
require 'rspec-subject-extensions'
class Post
def visible?
true
end
end
describe Post do
subject { [Post.new, Post.new] }
each { should be_visible }
end
"""
When I run `rspec example_spec.rb --format documentation`
Then the output should contain:
"""
Post
each instance
should be visible
"""
80 changes: 62 additions & 18 deletions lib/rspec-subject-extensions/class_methods.rb
Expand Up @@ -3,18 +3,19 @@
module RSpecSubjectExtensions
module ClassMethods
# Creates a nested example group named by +each+ and the submitted +attribute+,
# and then generates an example for each attribute using the submitted block.
# or the +instances+ themselves if none given, and then generates an example for
# each of them using the submitted block.
#
# @param [Symbol, String] attribute
# @param optional [Symbol, String] attribute
# The singular name of the subject method containing all the attributes.
#
# @yield
# Example to run against each attribute.
# Example to run against each attribute (or instance if no attribute given).
#
# @raise [NoMethodError]
# The subject doesn't respond to the pluralized version of the attribute or it doesn't respond to each.
#
# @example
# @example Using the singular name of the attributes
# # This ...
# describe Object do
# each(:item) { should be_an(Integer) }
Expand All @@ -30,13 +31,38 @@ module ClassMethods
# end
# end
# end
def each(attribute, &block)
describe("each #{attribute}") do
attribute = attribute.to_s.pluralize
#
# @example Using no attribute. Ideal to test scopes
# # This ...
# describe Object do
# subject { Object.visible }
# each { should be_visible }
# end
#
# # ... generates the same runtime structure as this:
# describe Object do
# describe "each instance" do
# it "should be visible" do
# subject.each do |element|
# element.should be_visible
# end
# end
# end
# end
def each(attribute = nil, &block)
if attribute
each_with_attribute(attribute, &block)
else
each_without_attribute(&block)
end
end

private

example do
if subject.respond_to?(attribute) && subject.send(attribute).respond_to?(:each)
subject.send(attribute).each do |item|
def each_without_attribute(&block)
describe("each instance") do
example do
subject.each do |item|
self.class.class_eval do
define_method(:subject) do
@_subject = item
Expand All @@ -45,18 +71,36 @@ def each(attribute, &block)

instance_eval(&block)
end
else
self.class.class_eval do
define_method(:subject) do
@_subject ||= super().send(attribute).each
end
end
end

def each_with_attribute(attribute, &block)
describe("each #{attribute}") do
attribute = attribute.to_s.pluralize

example do
if subject.respond_to?(attribute) && subject.send(attribute).respond_to?(:each)
subject.send(attribute).each do |item|
self.class.class_eval do
define_method(:subject) do
@_subject = item
end
end

instance_eval(&block)
end
else
self.class.class_eval do
define_method(:subject) do
@_subject ||= super().send(attribute).each
end
end
end

instance_eval(&block)
instance_eval(&block)
end
end
end
end
end

end
end
112 changes: 72 additions & 40 deletions spec/rspec-subject-extensions/class_methods_spec.rb
Expand Up @@ -8,63 +8,95 @@ module RSpecSubjectExtensions::ClassMethods
end

describe "#each" do
it "should satisfy expectation" do
group = RSpec::Core::ExampleGroup.describe("object") do
subject {
Class.new do
def items
[1, 2]
end
end.new
}
context "with attribute" do
it "should satisfy expectation" do
group = RSpec::Core::ExampleGroup.describe("object") do
subject {
Class.new do
def items
[1, 2]
end
end.new
}

each(:item) { should be_an(Integer) }
each(:item) { should be_an(Integer) }
end
group.run(NullObject.new).should be_true
end
group.run(NullObject.new).should be_true
end

it "fails when expectation should fail" do
group = RSpec::Core::ExampleGroup.describe("object") do
subject {
it "fails when expectation should fail" do
group = RSpec::Core::ExampleGroup.describe("object") do
subject {
Class.new do
def items
[1, 'a']
end
end.new
}

each(:item) { should be_an(Integer) }
end
group.run(NullObject.new).should be_false
end

context "when it doesn't respond to the pluralized version of the attribute" do
subject { Object.new }

context "it raises an error" do
each(:item) do
expect do
should be_an(Integer)
end.to raise_error(NoMethodError)
end
end
end

context "when it doesn't return an object responding to each" do
subject do
Class.new do
def items
[1, 'a']
1
end
end.new
}
end

each(:item) { should be_an(Integer) }
context "it raises an error" do
each(:item) do
expect do
should be_an(Integer)
end.to raise_error(NoMethodError)
end
end
end
group.run(NullObject.new).should be_false
end

context "when it doesn't respond to the pluralized version of the attribute" do
subject { Object.new }

context "it raises an error" do
each(:item) do
expect do
should be_an(Integer)
end.to raise_error(NoMethodError)
context "without attribute" do
it "should satisfy expectation" do
group = RSpec::Core::ExampleGroup.describe("object") do
subject { [1, 2] }
each { should be_an(Integer) }
end
group.run(NullObject.new).should be_true
end
end

context "when it doesn't return an object responding to each" do
subject do
Class.new do
def items
1
end
end.new
it "fails when expectation should fail" do
group = RSpec::Core::ExampleGroup.describe("object") do
subject { [1, 'a'] }
each { should be_an(Integer) }
end
group.run(NullObject.new).should be_false
end

context "it raises an error" do
each(:item) do
expect do
should be_an(Integer)
end.to raise_error(NoMethodError)
it "fails when the object doesn't respond to each" do
group = RSpec::Core::ExampleGroup.describe("object") do
subject { 1 }
each do
expect {
should be_an(Integer)
}.to raise_error(NoMethodError)
end
end
group.run(NullObject.new).should be_false
end
end
end
Expand Down

0 comments on commit cdb2f5c

Please sign in to comment.