Blog
Crafting Rspec Steps with step_eval and DRYing them with a Helper: Part 1
By matthias
Speccing steps with step_eval, implementing them and finally DRYing up.
StepSpecr wraps the step performing functionality in a single method.
Through calling step_eval in an example the specified step will be evaluated in the context of the example. So you can do this:
1
2
3
4
5
6
7
8
9
|
describe "Given a step that sets @number to 7" do
it "should set number to 7" do
@number = 1
step_eval "Given a step that sets @number to 7", :spec
@number.should == 7
end
end
|
Getting started
The script snippets that follows are executable rspec examples. You'll need to have installed rspec and rails as gems.
One trivial passing example to verify the functionality.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
require 'rubygems'
require 'active_support'
require 'spec'
require 'spec/matchers'
require 'spec/story'
require 'spec/story/step'
# This is the function that performs the step.
def step_eval(stepname, stepgroupname)
type = stepname.split(/\s+/).first.to_s.downcase.to_sym
stepname = stepname.split(/\s+/)[1,100].join(" ")
stepgroup = steps_for stepgroupname
step = stepgroup.find(type, stepname)
if step == nil
raise Spec::Expectations::ExpectationNotMetError.new("Didn't find step: '#{stepname}'")
end
args = step.parse_args(stepname)
step.perform self, *args
end
# Here are the step implementations
steps_for :model do
end
# Here is the spec
describe "description" do
it "should description" do
end
end
|
Run it from TextMate:
Writing a first example
We want to have a step that takes a number and a name and that will create so much models (specified by name) as specified by number. Like this:
Given $count $models
Let's write an example: Given 1 article
1
2
3
4
5
6
7
8
9
10
|
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
end
|
Here is the executable version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
require 'rubygems'
require 'active_support'
require 'spec'
require 'spec/matchers'
require 'spec/story'
require 'spec/story/step'
# This is the function that performs the step.
def step_eval(stepname, stepgroupname)
type = stepname.split(/\s+/).first.to_s.downcase.to_sym
stepname = stepname.split(/\s+/)[1,100].join(" ")
stepgroup = steps_for stepgroupname
step = stepgroup.find(type, stepname)
if step == nil
raise Spec::Expectations::ExpectationNotMetError.new("Didn't find step: '#{stepname}'")
end
args = step.parse_args(stepname)
step.perform self, *args
end
# Here are the step implementations
steps_for :model do
end
# Here is the spec
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
end
|
Run it:

The runner didn't find the step. We have to declare it in the stepgroup:
1
2
3
4
5
6
7
|
steps_for :model do
Given "$count $models" do |count, model|
end
end
|
Executable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
require 'rubygems'
require 'active_support'
require 'spec'
require 'spec/matchers'
require 'spec/story'
require 'spec/story/step'
# This is the function that performs the step.
def step_eval(stepname, stepgroupname)
type = stepname.split(/\s+/).first.to_s.downcase.to_sym
stepname = stepname.split(/\s+/)[1,100].join(" ")
stepgroup = steps_for stepgroupname
step = stepgroup.find(type, stepname)
if step == nil
raise Spec::Expectations::ExpectationNotMetError.new("Didn't find step: '#{stepname}'")
end
args = step.parse_args(stepname)
step.perform self, *args
end
# Here are the step implementations
steps_for :model do
Given "$count $models" do |count, model|
end
end
# Here is the spec
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
end
|
Run it:
Implementing the step
The Mock (Article) expected to receive create. Let's call create on him:
1
2
3
4
5
6
7
8
|
steps_for :model do
Given "$count $models" do |count, model|
Article.create
end
end
|
Executable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
require 'rubygems'
require 'active_support'
require 'spec'
require 'spec/matchers'
require 'spec/story'
require 'spec/story/step'
# This is the function that performs the step.
def step_eval(stepname, stepgroupname)
type = stepname.split(/\s+/).first.to_s.downcase.to_sym
stepname = stepname.split(/\s+/)[1,100].join(" ")
stepgroup = steps_for stepgroupname
step = stepgroup.find(type, stepname)
if step == nil
raise Spec::Expectations::ExpectationNotMetError.new("Didn't find step: '#{stepname}'")
end
args = step.parse_args(stepname)
step.perform self, *args
end
# Here are the step implementations
steps_for :model do
Given "$count $models" do |count, model|
Article.create
end
end
# Here is the spec
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
end
|
Run:
Green Light!
Writing a second example
Our 'Given $count $models' should not only create 1 article every time we perform it. It should also create 2 memberships, 3 assets or 17 lists. Let's write another example to specify this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
it "should create 17 lists" do
class List
end
List.should_receive(:create).exactly(17).times
step_eval "Given 17 lists", :model
end
end
|
Executable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
require 'rubygems'
require 'active_support'
require 'spec'
require 'spec/matchers'
require 'spec/story'
require 'spec/story/step'
# This is the function that performs the step.
def step_eval(stepname, stepgroupname)
type = stepname.split(/\s+/).first.to_s.downcase.to_sym
stepname = stepname.split(/\s+/)[1,100].join(" ")
stepgroup = steps_for stepgroupname
step = stepgroup.find(type, stepname)
if step == nil
raise Spec::Expectations::ExpectationNotMetError.new("Didn't find step: '#{stepname}'")
end
args = step.parse_args(stepname)
step.perform self, *args
end
# Here are the step implementations
steps_for :model do
Given "$count $models" do |count, model|
Article.create
end
end
# Here is the spec
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
it "should create 17 lists" do
class List
end
List.should_receive(:create).exactly(17).times
step_eval "Given 17 lists", :model
end
end
|
Run the examples:

The failure message is 'undefined method `create' for Article:Class'. This is because in the second example the expectation is that the List class receives create but is the step implementation we are statically calling Article.create.
Implementing the step
We need to evaluate a constant that is specified by a string:
1
2
3
4
5
6
7
8
9
10
|
steps_for :model do
Given "$count $models" do |count, model|
klass = eval "#{model.singularize.camelize}"
klass.create
end
end
|
Let's try this. Heres the
Executable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
require 'rubygems'
require 'active_support'
require 'spec'
require 'spec/matchers'
require 'spec/story'
require 'spec/story/step'
# This is the function that performs the step.
def step_eval(stepname, stepgroupname)
type = stepname.split(/\s+/).first.to_s.downcase.to_sym
stepname = stepname.split(/\s+/)[1,100].join(" ")
stepgroup = steps_for stepgroupname
step = stepgroup.find(type, stepname)
if step == nil
raise Spec::Expectations::ExpectationNotMetError.new("Didn't find step: '#{stepname}'")
end
args = step.parse_args(stepname)
step.perform self, *args
end
# Here are the step implementations
steps_for :model do
Given "$count $models" do |count, model|
klass = eval "#{model.singularize.camelize}"
klass.create
end
end
# Here is the spec
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
it "should create 17 lists" do
class List
end
List.should_receive(:create).exactly(17).times
step_eval "Given 17 lists", :model
end
end
|
Run:

This is obvious: create has to be called 'count' times:
1
2
3
4
5
6
7
8
9
10
|
steps_for :model do
Given "$count $models" do |count, model|
klass = eval "#{model.singularize.camelize}"
count.to_i.times { klass.create }
end
end
|
Executable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
require 'rubygems'
require 'active_support'
require 'spec'
require 'spec/matchers'
require 'spec/story'
require 'spec/story/step'
# This is the function that performs the step.
def step_eval(stepname, stepgroupname)
type = stepname.split(/\s+/).first.to_s.downcase.to_sym
stepname = stepname.split(/\s+/)[1,100].join(" ")
stepgroup = steps_for stepgroupname
step = stepgroup.find(type, stepname)
if step == nil
raise Spec::Expectations::ExpectationNotMetError.new("Didn't find step: '#{stepname}'")
end
args = step.parse_args(stepname)
step.perform self, *args
end
# Here are the step implementations
steps_for :model do
Given "$count $models" do |count, model|
klass = eval "#{model.singularize.camelize}"
count.to_i.times { klass.create }
end
end
# Here is the spec
describe "Given $count $models" do
it "should create 1 article" do
class Article
end
Article.should_receive(:create).once
step_eval "Given 1 article", :model
end
it "should create 17 lists" do
class List
end
List.should_receive(:create).exactly(17).times
step_eval "Given 17 lists", :model
end
end
|
Run:
Green again!
-->Part 2
Sorry, comments are closed for this article.