saki 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +43 -24
- data/VERSION +1 -1
- data/lib/generators/saki/spec_generator.rb +31 -0
- data/lib/generators/saki/templates/acceptance_spec.rb +21 -0
- data/lib/saki.rb +40 -16
- data/saki.gemspec +4 -2
- metadata +6 -4
data/README.markdown
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# Saki - For times when you can't swallow Cucumber
|
2
2
|
|
3
|
-
|
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
|
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
|
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
|
-
##
|
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
|
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
|
-
|
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
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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
|
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
|
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
|
data/lib/saki.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
data/saki.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{saki}
|
8
|
-
s.version = "0.0
|
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-
|
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:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
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-
|
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
|