saki 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,17 +1,17 @@
1
1
  # Saki - For times when you can't swallow Cucumber
2
2
 
3
- Saki lets you do acceptance testing on top of RSpec. It is considerably more terse than Cucumber, but does not sacrifice readability. Saki also does not use Given/When/Then syntax because the thought is that there is little return other than familiarity for Cucumber users.
4
-
5
- Are you tired of having DRY code, but tests that seem to babble on "for the length of a bible"? Me too. How about RSpec code that is hard to follow, when Ruby itself is simple to follow? I hate it too.
3
+ Are you tired of having DRY code, but tests that seem to babble on "for the length of a bible"? Me too. How about test code that is hard to follow, when Ruby itself is clear as day? I hate it too.
6
4
 
7
5
  Enter Saki stage left.
8
6
 
7
+ Saki lets you do acceptance testing on top of RSpec. It is considerably more terse than Cucumber, but does not sacrifice readability. Saki also does not use Given/When/Then syntax because the thought is that there is little return other than familiarity for Cucumber users.
8
+
9
9
  ## How terse is it?
10
10
 
11
11
  Well, here's a sample that sets up contexts that create a user and then visit an edit path for that user.
12
12
 
13
13
  with_existing :user do
14
- on_visiting edit_path_for(:user) do
14
+ on_visiting edit_user_path do
15
15
  it { should let_me_edit(@user) }
16
16
  end
17
17
  end
@@ -21,25 +21,31 @@ This code basically injects some "before blocks", so it would look like this in
21
21
  context "a user exists" do
22
22
  before { @user = Factory :user }
23
23
  context "I visit the page for editing that user" do
24
- before { visit edit_user_path(:user) }
24
+ before { visit "/users/#{@user.id}/user" }
25
25
  it { should let_me_edit(@user) }
26
26
  end
27
27
  end
28
28
 
29
- I believe the Saki example has the following benefits:
29
+ I believe the Saki example has the following benefits over vanilla RSpec:
30
30
 
31
31
  * It saves two lines of code.
32
- * It standardizes the code. Whereas a context string might accidentally get out of sync with the code (unmaintained comments, anyone), with Saki this would probably cause a test to fail.
32
+ * It standardizes the code. Whereas a context string might accidentally get out of sync with the code (unmaintained comments, anyone?), with Saki this would probably cause a test to fail.
33
33
  * It is much more expressive. You read the test and you immediately know what it does.
34
- * Any exceptions to the rule (complicated setups, etc.) now stick out like sore thumbs, as they should.
34
+ * Any exceptions to the rule (complicated setups, etc.) would now stick out like sore thumbs, as they should.
35
35
 
36
- The only assumption is that you are using factories instead of fixtures. You also get more out of it in a conventional RESTful application.
36
+ The only assumption is that you are using factories instead of fixtures. You also get more out of it in a conventional RESTful application, where the paths don't have too many gotchas.
37
37
 
38
- ## What class-level methods does it use (for setting up contexts)?
38
+ ## Methods provided for setting up contexts
39
39
 
40
- `with_existing` takes a factory name as a symbol and assigns its created object to on instance variable with the same name.
40
+ `with_existing` takes a factory name as a symbol and assigns its created object to an instance variable with the same name. It also takes options so you can have blocks start with:
41
41
 
42
- `on_visiting` takes a path as a string, or a lambda that executes within a before block to set up the path. It also takes a symbol which is the name of a method name. This is useful when the code is dependent on an instance variable for path creation.
42
+ with_existing :user, :state => "happy" do...
43
+
44
+ `on_visiting` preferably uses some dynamic functions for establishing a path: `new_X_path`, `Xs_path`, `edit_X_path`, `X_path` and `new_X_path`. In these cases, substitute X for the resource name (e.g. `new_user_path`). In cases where the resource is nested, it has a :parent => parent_resource option. This lets you set up blocks like:
45
+
46
+ on_visiting auctions_path(:parent => :user) do ...
47
+
48
+ `on_visiting` also takes a path as a string, or a lambda that executes within a before block to set up the path. It also takes a symbol which is the name of a method name. This is useful when the code is dependent on an instance variable for path creation.
43
49
 
44
50
  path_for_user = lambda { user_path(@user) }
45
51
 
@@ -53,13 +59,9 @@ or you can do
53
59
 
54
60
  on_visiting :my_user_path do ...
55
61
 
56
- `on_visiting` has several helper functions for establishing a path: `create_path_for`, `index_path_for`, `edit_path_for`, `show_path_for` and `new_path_for`. These paths all take resource names for establishing a path. In cases where the resource is nested, it has a :parent => parent_resource option. This lets you set up blocks like:
57
-
58
- on_visiting index_path_for(:auction)
59
-
60
62
  `on_following_link_to` works the same as on_visiting, but it first validates that the link exists, and then follows it.
61
63
 
62
- `where` is a function taking as a parameter lambda to execute in the before block.
64
+ `where` is a function taking as a parameter a lambda to execute in the before block.
63
65
 
64
66
  def self.creating_a_user
65
67
  lambda {
@@ -69,7 +71,7 @@ or you can do
69
71
  }
70
72
  end
71
73
 
72
- on_following_link_to create_path_for(:user) do
74
+ on_following_link_to new_user_path do
73
75
  where creating_a_user do
74
76
  specify { page.should have_content(@user.email) }
75
77
  end
@@ -77,7 +79,7 @@ or you can do
77
79
 
78
80
  Obviously the return for this is where you have functions acting as "reusable steps" in the style of Cucumber. In addition your "before blocks" are more expressive.
79
81
 
80
- Finally, to simplify setting up integration tests, anything you wrap in an `integrate` block (like `describe`) sets the test type to acceptance.
82
+ Finally, to simplify setting up integration tests, anything you wrap in an `integrate` block (like `describe`) sets the test type to acceptance. This is the default `describe` function of the generators, but feel free to use the regular describe block as long as you set its :type option to :acceptance.
81
83
 
82
84
  ## Installation
83
85
 
@@ -89,11 +91,28 @@ Then to fill out the directories run:
89
91
 
90
92
  rails generate saki:install
91
93
 
92
- Then, as long as your acceptance specs require the acceptance_helper file you should be good to go.
93
-
94
- ## What assumptions does it make?
94
+ You can generate new acceptance tests with `rails generate saki:spec SPEC_NAME`. This automatically generates tests like
95
+
96
+ require File.dirname(__FILE__) + '/acceptance_helper'
95
97
 
96
- It assumes that you are using factory_girl and capybara or webrat, though it probably would work fine with other test factories. If you need another factory in the mix, just redefine the `default_factory` method to behave how you want.
98
+ integrate "author resource" do
99
+
100
+ on_visiting new_author_path do
101
+ specify { fail "not implemented" }
102
+ end
103
+
104
+ with_existing :author do
105
+ on_visiting edit_author_path do
106
+ specify { fail "not implemented" }
107
+ end
108
+ on_visiting author_path do
109
+ specify { fail "not implemented" }
110
+ end
111
+ on_visiting authors_path do
112
+ specify { fail "not implemented" }
113
+ end
114
+ end
115
+ end
97
116
 
98
117
  ## References
99
118
 
@@ -101,7 +120,7 @@ The motivation behind my migration from Cucumber and to Saki, are described in b
101
120
 
102
121
  ## Thanks
103
122
 
104
- The generators are stolen directly from Steak with some minor adjustments.
123
+ The generators are stolen directly from Steak with some adjustments.
105
124
 
106
125
  ## Note on Patches/Pull Requests
107
126
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.1.0
@@ -0,0 +1,31 @@
1
+ require 'rails/generators'
2
+
3
+ module Saki
4
+ class SpecGenerator < Rails::Generators::NamedBase
5
+
6
+ def resource_name
7
+ file_name.tableize.singularize
8
+ end
9
+
10
+ source_root File.join(File.dirname(__FILE__), 'templates')
11
+
12
+ desc <<-DESC
13
+ Description:
14
+ Create an acceptance spec for the feature NAME in the
15
+ 'spec/acceptance' folder.
16
+
17
+ Example:
18
+ `rails generate saki:spec author`
19
+
20
+ Creates an acceptance spec for the "author" feature:
21
+ spec/acceptance/author_spec.rb
22
+ DESC
23
+
24
+ def manifest
25
+ empty_directory File.join('spec/acceptance', class_path)
26
+ file_name.gsub!(/_spec$/,"")
27
+ template 'acceptance_spec.rb', File.join('spec/acceptance', class_path, "#{file_name}_spec.rb")
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/acceptance_helper'
2
+
3
+ integrate "<%= resource_name %> resource" do
4
+
5
+ on_visiting new_<%= resource_name %>_path do
6
+ specify { fail "not implemented" }
7
+ end
8
+
9
+ with_existing :<%= resource_name %> do
10
+ on_visiting edit_<%= resource_name %>_path do
11
+ specify { fail "not implemented" }
12
+ end
13
+ on_visiting <%= resource_name %>_path do
14
+ specify { fail "not implemented" }
15
+ end
16
+ on_visiting <%= resource_name.pluralize %>_path do
17
+ specify { fail "not implemented" }
18
+ end
19
+ end
20
+
21
+ end
@@ -4,8 +4,8 @@ module Saki
4
4
  module AcceptanceHelpers
5
5
  extend ActiveSupport::Concern
6
6
 
7
- def default_factory(name)
8
- Factory name
7
+ def default_factory(name, opts = {})
8
+ Factory name, opts
9
9
  end
10
10
 
11
11
  def get_path(path)
@@ -18,7 +18,7 @@ module Saki
18
18
  end
19
19
  end
20
20
 
21
- def add_opts(link, opts)
21
+ def add_opts(link, opts = {})
22
22
  if opts[:parent]
23
23
  link = "/#{opts[:parent].class.to_s.tableize}/#{opts[:parent].id}" + link
24
24
  end
@@ -64,17 +64,14 @@ module Saki
64
64
  else
65
65
  current_path.should match(page_name)
66
66
  end
67
- end
68
-
69
- def index_path_for(model)
70
- "/#{model}"
71
67
  end
72
68
 
73
-
74
69
  module ClassMethods
75
- def with_existing resource, &block
70
+ def with_existing resource, opts={}, &block
76
71
  context "with exisiting #{resource}" do
77
- before { eval "@#{resource} = default_factory :#{resource}" }
72
+ before do
73
+ instance_variable_set "@#{resource}", default_factory(resource, opts)
74
+ end
78
75
  module_eval &block
79
76
  end
80
77
  end
@@ -99,6 +96,7 @@ module Saki
99
96
  end
100
97
  end
101
98
 
99
+
102
100
  def add_opts(link, opts, context)
103
101
  if opts[:parent]
104
102
  "/#{opts[:parent].to_s.pluralize}/#{(context.instance_variable_get('@' + opts[:parent].to_s)).id}" + link
@@ -112,7 +110,7 @@ module Saki
112
110
  add_opts "/#{resource.to_s.pluralize}/#{(context.instance_variable_get('@' + resource.to_s)).id}/edit", opts, context
113
111
  end
114
112
  end
115
-
113
+
116
114
  def show_path_for(resource, opts = {})
117
115
  lambda do |context|
118
116
  add_opts "/#{resource.to_s.pluralize}/#{(context.instance_variable_get('@' + resource.to_s)).id}", opts, context
@@ -142,15 +140,43 @@ module Saki
142
140
  end
143
141
 
144
142
  class RSpec::Core::ExampleGroup
145
- def method_missing(methId)
143
+
144
+ def self.method_missing(methId, *args)
145
+ parse_opts = lambda {|link , opts, context|
146
+ opts ||= {}
147
+ if opts[:parent]
148
+ "/#{opts[:parent].to_s.pluralize}/#{(context.instance_variable_get('@' + opts[:parent].to_s)).id}" + link
149
+ else
150
+ link
151
+ end
152
+ }
153
+
146
154
  str = methId.id2name
147
- if str.match /(.*)_path/
148
- index_path_for($1)
155
+ if str.match /new_(.*)_path/
156
+ lambda { |context|
157
+ parse_opts.call "/#{$1.pluralize}/new", args.first, context
158
+ }
159
+ elsif str.match /edit_(.*)_path/
160
+ lambda { |context|
161
+ model = context.instance_variable_get "@#{$1}"
162
+ parse_opts.call "/#{model.class.to_s.tableize}/#{model.id}/edit", args.first, context
163
+ }
164
+ elsif str.match /(.*)_path/
165
+ pluralized = $1.pluralize
166
+ if pluralized == $1
167
+ lambda { |context| parse_opts.call "/#{$1}", args.first, context }
168
+ else
169
+ lambda { |context|
170
+ model = context.instance_variable_get "@#{$1}"
171
+ parse_opts.call "/#{model.class.to_s.tableize}/#{model.id}", args.first, context
172
+ }
173
+ end
149
174
  else
150
175
  super(methId, [])
151
176
  end
152
177
 
153
178
  end
179
+
154
180
  end
155
181
 
156
182
  module RSpec::Core::ObjectExtensions
@@ -161,6 +187,4 @@ module RSpec::Core::ObjectExtensions
161
187
  end
162
188
  end
163
189
 
164
-
165
-
166
190
  RSpec.configuration.include Saki::AcceptanceHelpers, :type => :acceptance
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{saki}
8
- s.version = "0.0.4"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Nate Kidwell"]
12
- s.date = %q{2010-08-30}
12
+ s.date = %q{2010-08-31}
13
13
  s.description = %q{Cucumber scenarios are long and confusing sometimes. Release yourself from the tyranny of client-centered specing!}
14
14
  s.email = %q{nate@ludicast.com}
15
15
  s.extra_rdoc_files = [
@@ -24,7 +24,9 @@ Gem::Specification.new do |s|
24
24
  "Rakefile",
25
25
  "VERSION",
26
26
  "lib/generators/saki/install_generator.rb",
27
+ "lib/generators/saki/spec_generator.rb",
27
28
  "lib/generators/saki/templates/acceptance_helper.rb",
29
+ "lib/generators/saki/templates/acceptance_spec.rb",
28
30
  "lib/generators/saki/templates/helpers.rb",
29
31
  "lib/saki.rb",
30
32
  "saki.gemspec",
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saki
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 4
10
- version: 0.0.4
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Nate Kidwell
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-30 00:00:00 -04:00
18
+ date: 2010-08-31 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -49,7 +49,9 @@ files:
49
49
  - Rakefile
50
50
  - VERSION
51
51
  - lib/generators/saki/install_generator.rb
52
+ - lib/generators/saki/spec_generator.rb
52
53
  - lib/generators/saki/templates/acceptance_helper.rb
54
+ - lib/generators/saki/templates/acceptance_spec.rb
53
55
  - lib/generators/saki/templates/helpers.rb
54
56
  - lib/saki.rb
55
57
  - saki.gemspec