Blog

Crafting Rspec Steps with step_eval and DRYing them with a Helper Part 2

By matthias

<-- Part 1

Write an Example for another step

What if we get rid of all those articles and lists?
We'll need another step:

When I destroy all $models

Start by writing the example:

1
2
3
4
5
6
7
8
9

  describe "When I destroy all $models" do
    it "should destroy all Articles" do
      class Article
      end
      Article.should_receive(:destroy_all)
      step_eval "When I destroy all articles", :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
53
54
55
56
57
58
59
60
61
62
63

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

describe "When I destroy all $models" do
  it "should destroy all Articles" do
    class Article
    end
    Article.should_receive(:destroy_all)
    step_eval "When I destroy all articles", :model
  end
end

Run:

Implement the second step

We have to declare the step in the stepgroup:

1
2
3
4
5
6
7
8
9
10
11
12

  steps_for :model do

     Given "$count $models" do |count, model|
       klass = eval "#{model.singularize.camelize}"
       count.to_i.times { klass.create }
     end

     When "I destroy all $models" do |model|
     end

   end

Again 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
55
56
57
58
59
60
61
62
63
64
65
66

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
   
   When "I destroy all $models" do |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
  
  it "should create 17 lists" do
    class List
    end
    List.should_receive(:create).exactly(17).times
    step_eval "Given 17 lists", :model
  end
end

describe "When I destroy all $models" do
  it "should destroy all Articles" do
    class Article
    end
    Article.should_receive(:destroy_all)
    step_eval "When I destroy all articles", :model
  end
end

Run it:

This is obvious again. We need to call List.destroy_all in the step implementation. But this time we know that we will need a more flexible solution and we should reuse the class_from_string_thing from the first step.

DRYing up Steps and speccing a Helper

In our second step we'll need exactly the same first line of code as in the first step. We will only temporary copy and paste it but then write a helper method: (Remember that a step implementation is not an example and thus the clarity-degree may be 20% lower.)

Getting green before we do the next thing

Let's get the example to pass:
1
2
3
4
5
6
7
8
9
10
11
12
13
14

  steps_for :model do

     Given "$count $models" do |count, model|
       klass = eval "#{model.singularize.camelize}"
       count.to_i.times { klass.create }
     end

     When "I destroy all $models" do |model|
       klass = eval "#{model.singularize.camelize}"
       klass.destroy_all
     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
55
56
57
58
59
60
61
62
63
64
65
66
67
68

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
   
   When "I destroy all $models" do |model|
     klass = eval "#{model.singularize.camelize}"
     klass.destroy_all
   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

describe "When I destroy all $models" do
  it "should destroy all Articles" do
    class Article
    end
    Article.should_receive(:destroy_all)
    step_eval "When I destroy all articles", :model
  end
end

Run it:

Green! Let's spec this helper.

We will call our helper module Helper and the method 'class_from_string' and add this infrastructure upfront:

 module Helper
   def class_from_string(name)
   end
 end

Writing a first example for the helper:

1
2
3
4
5
6
7
8

describe Helper do
  describe "#class_from_string" do
    it "'article' -> Article" do
      class_from_string("article").should == Article
    end
  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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

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 is the DRYing Helper

module Helper
  def class_from_string(name)
    
  end
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
   
   When "I destroy all $models" do |model|
     klass = eval "#{model.singularize.camelize}"
     klass.destroy_all
   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

describe "When I destroy all $models" do
  it "should destroy all Articles" do
    class Article
    end
    Article.should_receive(:destroy_all)
    step_eval "When I destroy all articles", :model
  end
end

describe Helper do
  describe "#class_from_string" do
    it "'article' -> Article" do
      class_from_string("article").should == Article
    end
  end
end

Run it:

We already have done the implementation in the steps:
1
2
3
4
5
6

module Helper
  def class_from_string(name)
    eval "#{name.singularize.camelize}"
  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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

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 is the DRYing Helper

module Helper
  def class_from_string(name)
    eval "#{name.singularize.camelize}"
  end
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
   
   When "I destroy all $models" do |model|
     klass = eval "#{model.singularize.camelize}"
     klass.destroy_all
   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

describe "When I destroy all $models" do
  it "should destroy all Articles" do
    class Article
    end
    Article.should_receive(:destroy_all)
    step_eval "When I destroy all articles", :model
  end
end

describe Helper do
  describe "#class_from_string" do
    it "'article' -> Article" do
      class_from_string("article").should == Article
    end
  end
end

Run it:

Green light. Let's integrate this in Part 3.

--> Part 3

 

Sorry, comments are closed for this article.

Blog