Blog

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

By matthias

<-- Part 2
<-- Part 1

Green light. Let's integrate the helper.

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

  steps_for :model do

     Given "$count $models" do |count, model|
       count.to_i.times { class_from_string(model).create }
     end

     When "I destroy all $models" do |model|
       class_from_string(model).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
69
70
71
72
73
74
75
76
77
78
79
80
81

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|
     count.to_i.times { class_from_string(model).create }
   end
   
   When "I destroy all $models" do |model|
     class_from_string(model).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:

Whoops! We have to extend our step implementations with the new functionality:

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

   steps_for :model do

      Given "$count $models" do |count, model|
        extend Helper
        count.to_i.times { class_from_string(model).create }
      end

      When "I destroy all $models" do |model|
        extend Helper
        class_from_string(model).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
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|
       extend Helper
       count.to_i.times { class_from_string(model).create }
     end

     When "I destroy all $models" do |model|
       extend Helper
       class_from_string(model).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!

Extending every single step with the Helper module is only necessary in this script. If you use the step with the real story runner you can mix the Helper into the Spec::Story::World: (eg. in stories/helper.rb)

1
2
3
4
5
6
7
8

 module Helper
    def class_from_string(name)
      eval "#{name.singularize.camelize}"
    end
 end

 Spec::Story::World.module_eval { include Helper }

Now we have two carefully crafted steps that are dryed up with a helper and everything gets automatically tested so you can rely on them and reuse them. That's it for today.

 

Sorry, comments are closed for this article.

Blog