Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ Note that the generators are there to help you get started, but they are no
substitute for writing your own examples, and they are only guaranteed to work
out of the box for the default scenario (`ActiveRecord` + `Webrat`).

#### Custom Generator Workflow

To plug in a custom generator for one or more example types:
In config/application.rb:

config.app_generators do |g|
g.routing_specs = :my_fancy_routing_specs
g.controller_specs = :my_fancy_controller_specs
g.view_specs = :my_fancy_view_specs
end

These generators should all live in lib/generators/rspec and the classes should be
declared as `Rspec::Generators::MyFancyRoutingSpecsGenerator`, and so on. Make copies
of the ones from this git repo to use as templates.

### Autotest

The `rspec:install` generator creates an `./autotest/discover.rb` file, which
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require 'generators/rspec'

module Rspec
module Generators
class ControllerSpecsGenerator < Base
class_option :controller_specs, :type => :boolean, :default => true
class_option :orm, :desc => "ORM used to generate the controller"
class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"
def generate_controller_spec
return unless options[:controller_specs]

template 'controller_spec.rb',
File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb")
end
protected
def params
"{'these' => 'params'}"
end

# Returns the name of the mock. For example, if the file name is user,
# it returns mock_user.
#
# If a hash is given, it uses the hash key as the ORM method and the
# value as response. So, for ActiveRecord and file name "User":
#
# mock_file_name(:save => true)
# #=> mock_user(:save => true)
#
# If another ORM is being used and another method instead of save is
# called, it will be the one used.
#
def mock_file_name(hash=nil)
if hash
method, and_return = hash.to_a.first
method = orm_instance.send(method).split('.').last.gsub(/\(.*?\)/, '')
"mock_#{file_name}(:#{method} => #{and_return})"
else
"mock_#{file_name}"
end
end

# Receives the ORM chain and convert to expects. For ActiveRecord:
#
# should! orm_class.find(User, "37")
# #=> User.should_receive(:find).with(37)
#
# For Datamapper:
#
# should! orm_class.find(User, "37")
# #=> User.should_receive(:get).with(37)
#
def should_receive!(chain)
stub_or_should_chain(:should_receive, chain)
end

# Receives the ORM chain and convert to stub. For ActiveRecord:
#
# stub! orm_class.find(User, "37")
# #=> User.stub!(:find).with(37)
#
# For Datamapper:
#
# stub! orm_class.find(User, "37")
# #=> User.stub!(:get).with(37)
#
def stub!(chain)
stub_or_should_chain(:stub, chain)
end

def stub_or_should_chain(mode, chain)
receiver, method = chain.split(".")
method.gsub!(/\((.*?)\)/, '')

response = "#{receiver}.#{mode}(:#{method})"
response << ".with(#{$1})" unless $1.blank?
response
end

end
end
end
17 changes: 17 additions & 0 deletions lib/generators/rspec/routing_specs/routing_specs_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'generators/rspec'

module Rspec
module Generators
class RoutingSpecsGenerator < Base
include Rails::Generators::ResourceHelpers
class_option :routing_specs, :type => :boolean, :default => true

def generate_routing_spec
return unless options[:routing_specs]

template 'routing_spec.rb',
File.join('spec/routing', controller_class_path, "#{controller_file_name}_routing_spec.rb")
end
end
end
end
106 changes: 3 additions & 103 deletions lib/generators/rspec/scaffold/scaffold_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,123 +18,23 @@ class ScaffoldGenerator < Base
class_option :helper_specs, :type => :boolean, :default => true, :desc => "Generate helper specs"
class_option :routing_specs, :type => :boolean, :default => true, :desc => "Generate routing specs"

def generate_controller_spec
return unless options[:controller_specs]
hook_for :controller_specs
hook_for :view_specs

template 'controller_spec.rb',
File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb")
end

def generate_view_specs
return unless options[:view_specs]

copy_view :edit
copy_view :index unless options[:singleton]
copy_view :new
copy_view :show
end

# Invoke the helper using the controller name (pluralized)
hook_for :helper, :as => :scaffold do |invoked|
invoke invoked, [ controller_name ]
end

def generate_routing_spec
return unless options[:routing_specs]

template 'routing_spec.rb',
File.join('spec/routing', controller_class_path, "#{controller_file_name}_routing_spec.rb")
end
hook_for :routing_specs

hook_for :integration_tool, :as => :integration

protected

def webrat?
options[:webrat_matchers] || @webrat_matchers_requested
end

def copy_view(view)
template "#{view}_spec.rb",
File.join("spec/views", controller_file_path, "#{view}.html.#{options[:template_engine]}_spec.rb")
end

def params
"{'these' => 'params'}"
end

# Returns the name of the mock. For example, if the file name is user,
# it returns mock_user.
#
# If a hash is given, it uses the hash key as the ORM method and the
# value as response. So, for ActiveRecord and file name "User":
#
# mock_file_name(:save => true)
# #=> mock_user(:save => true)
#
# If another ORM is being used and another method instead of save is
# called, it will be the one used.
#
def mock_file_name(hash=nil)
if hash
method, and_return = hash.to_a.first
method = orm_instance.send(method).split('.').last.gsub(/\(.*?\)/, '')
"mock_#{file_name}(:#{method} => #{and_return})"
else
"mock_#{file_name}"
end
end

# Receives the ORM chain and convert to expects. For ActiveRecord:
#
# should! orm_class.find(User, "37")
# #=> User.should_receive(:find).with(37)
#
# For Datamapper:
#
# should! orm_class.find(User, "37")
# #=> User.should_receive(:get).with(37)
#
def should_receive!(chain)
stub_or_should_chain(:should_receive, chain)
end

# Receives the ORM chain and convert to stub. For ActiveRecord:
#
# stub! orm_class.find(User, "37")
# #=> User.stub!(:find).with(37)
#
# For Datamapper:
#
# stub! orm_class.find(User, "37")
# #=> User.stub!(:get).with(37)
#
def stub!(chain)
stub_or_should_chain(:stub, chain)
end

def stub_or_should_chain(mode, chain)
receiver, method = chain.split(".")
method.gsub!(/\((.*?)\)/, '')

response = "#{receiver}.#{mode}(:#{method})"
response << ".with(#{$1})" unless $1.blank?
response
end

def value_for(attribute)
case attribute.type
when :string
"#{attribute.name.titleize}".inspect
else
attribute.default.inspect
end
end

def banner
self.class.banner
end

end
end
end
47 changes: 47 additions & 0 deletions lib/generators/rspec/view_specs/view_specs_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
require 'generators/rspec'

module Rspec
module Generators
class ViewSpecsGenerator < Base
include Rails::Generators::ResourceHelpers
class_option :view_specs, :type => :boolean, :default => true
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"

class_option :orm, :desc => "ORM used to generate the controller"
class_option :template_engine, :desc => "Template engine to generate view files"
class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"

class_option :controller_specs, :type => :boolean, :default => true, :desc => "Generate controller specs"
class_option :view_specs, :type => :boolean, :default => true, :desc => "Generate view specs"
class_option :webrat_matchers, :type => :boolean, :default => false, :desc => "Use webrat matchers in view specs"
class_option :helper_specs, :type => :boolean, :default => true, :desc => "Generate helper specs"
class_option :routing_specs, :type => :boolean, :default => true, :desc => "Generate routing specs"

def generate_view_specs
return unless options[:view_specs]

copy_view :edit
copy_view :index unless options[:singleton]
copy_view :new
copy_view :show
end
protected
def webrat?
options[:webrat_matchers] || @webrat_matchers_requested
end

def copy_view(view)
template "#{view}_spec.rb",
File.join("spec/views", controller_file_path, "#{view}.html.#{options[:template_engine]}_spec.rb")
end
def value_for(attribute)
case attribute.type
when :string
"#{attribute.name.titleize}".inspect
else
attribute.default.inspect
end
end
end
end
end
5 changes: 4 additions & 1 deletion lib/rspec-rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ module Rails
class Railtie < ::Rails::Railtie
# Rails-3.0.1 requires config.app_generators instead of 3.0.0's config.generators
generators = config.respond_to?(:app_generators) ? config.app_generators : config.generators
generators.test_framework :rspec
generators.integration_tool :rspec
generators.test_framework :rspec
generators.routing_specs = :rspec unless generators.view_specs
generators.controller_specs = :rspec unless generators.view_specs
generators.view_specs = :rspec unless generators.view_specs

rake_tasks do
load "rspec/rails/tasks/rspec.rake"
Expand Down