Skip to content

Generate request specs when scaffold generator is used #2262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ Metrics/BlockLength:
# Warns when the class is excessively long.
Metrics/ClassLength:
Max: 100
Exclude:
# Splitting the Generator classes would make matters more confusing
# than them being too big
- lib/generators/rspec/**/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the relevant magic comment to the generator that fails rather than doing this, or add the specific files, its possible to extract things without class splitting.


Metrics/PerceivedComplexity:
Enabled: false
Expand Down
14 changes: 11 additions & 3 deletions example_app_generator/generate_stuff.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ def using_source_path(path)

generate('model thing name:string')
generate('helper things')
generate('scaffold widget name:string category:string instock:boolean foo_id:integer bar_id:integer --force')
generate('scaffold product name:string category:string instock:boolean foo_id:integer bar_id:integer --force')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point the project stops being fun 😠

Apparently there other tests assumed that the Widget constant is created and I cannot figure how to make the testing of IntegrationGenerator and RequestGenerator work, sometimes the <%= index_helper %>_path works, sometimes or the _url helper.

I still think that generated request specs is important and very useful but for working on this as a lone wolf I am out. So please help, take over oder schedule chatting/pair programming (personally; I am more available in February).


Log of the smoke tests:

Failures:

  1) Using rspec-mocks with models supports stubbing attribute methods on models
     Failure/Error: a_widget = Widget.new
     
     NameError:
       uninitialized constant Widget
     # ./spec/features/model_mocks_integration_spec.rb:10:in `block (2 levels) in <top (required)>'

  2) Using rspec-mocks with models supports stubbing class methods on models
     Failure/Error: allow(Widget).to receive(:all).and_return(:any_stub)
     
     NameError:
       uninitialized constant Widget
     # ./spec/features/model_mocks_integration_spec.rb:5:in `block (2 levels) in <top (required)>'

  3) Widgets GET /widgets works! (now write some real specs)
     Failure/Error: get widgets_index_path
     
     NameError:
       undefined local variable or method `widgets_index_path' for #<RSpec::ExampleGroups::Widgets::GETWidgets:0x00007ff1f27043d8>
     # ./spec/requests/widgets_spec.rb:6:in `block (3 levels) in <top (required)>'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you had a chance to take another look? I can lend a hand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll write you an email to chat up or something

generate('scaffold gadget') # scaffold with no attributes
generate('scaffold ticket original_price:float discounted_price:float')
generate('scaffold admin/account name:string') # scaffold with nested resource
generate('scaffold card --api')
generate('scaffold upload --no-request_specs --controller_specs')
generate('rspec:feature gadget')
generate('controller things custom_action')

Expand Down Expand Up @@ -137,8 +139,14 @@ def using_source_path(path)
'config.warnings = false'
gsub_file '.rspec', '--warnings', ''

# Remove skips so we can test controller specs work
gsub_file 'spec/controllers/gadgets_controller_spec.rb',
# Remove skips so we can test that request and controller specs work
gsub_file 'spec/requests/gadgets_request_spec.rb',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why we have to change that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file that was attempted to edit does not exist anymore. That's what I meant with my question in the PR body about Integration / smoke test coverage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to ensure that the smoke coverage covers both scenarios.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Also added generation with using the --api flag

'skip("Add a hash of attributes valid for your model")',
'{}'
gsub_file 'spec/requests/cards_request_spec.rb',
'skip("Add a hash of attributes valid for your model")',
'{}'
gsub_file 'spec/controllers/uploads_controller_spec.rb',
'skip("Add a hash of attributes valid for your model")',
'{}'

Expand Down
2 changes: 1 addition & 1 deletion example_app_generator/run_specs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
) || abort
run('bin/rake --backtrace spec') || abort
run('bin/rake --backtrace spec:requests') || abort
run('bin/rake --backtrace spec:controllers') || abort
run('bin/rake --backtrace spec:models') || abort
run('bin/rake --backtrace spec:views') || abort
run('bin/rake --backtrace spec:controllers') || abort
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why we have to change that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspected that the smoke app does not generate controller specs anymore because the default of the ScaffoldGenerator changed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than removing these, you need to add a command to generate some controller tests for this to run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

run('bin/rake --backtrace spec:helpers') || abort
run('bin/rake --backtrace spec:mailers') || abort
run("bin/rake --backtrace stats") || abort
27 changes: 24 additions & 3 deletions lib/generators/rspec/scaffold/scaffold_generator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'generators/rspec'
require 'rails/generators/resource_helpers'

Expand All @@ -14,7 +16,8 @@ class ScaffoldGenerator < Base
class_option :singleton, type: :boolean, desc: "Supply to create a singleton controller"
class_option :api, type: :boolean, desc: "Skip specs unnecessary for API-only apps"

class_option :controller_specs, type: :boolean, default: true, desc: "Generate controller specs"
class_option :request_specs, type: :boolean, default: true, desc: 'Generate request specs'
class_option :controller_specs, type: :boolean, default: false, desc: 'Generate controller specs'
class_option :view_specs, type: :boolean, default: true, desc: "Generate 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"
Expand All @@ -24,6 +27,21 @@ def initialize(*args, &blk)
super(*args, &blk)
end

def generate_request_spec
return unless options[:request_specs]

template_file = File.join(
'spec/requests',
controller_class_path,
"#{controller_file_name}_request_spec.rb"
)
if options[:api]
template 'api_request_spec.rb', template_file
else
template 'request_spec.rb', template_file
end
end

def generate_controller_spec
return unless options[:controller_specs]

Expand Down Expand Up @@ -60,8 +78,6 @@ def generate_routing_spec
template 'routing_spec.rb', template_file
end

hook_for :integration_tool, as: :integration
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Check also my commit message on this change)

I had to dig extremely dig into the git history of the project, tried to figure the purpose and the consequences of this setting (the generator from railties does not have this). But I don't understand it. But is the thing that causes the IntegrationGenerator to run and also generate a request_spec file (figured that out through debugging and lookig at the backtrace).

Changing this seems to cause something, at least that's what the test suite is telling me

See also 98b886b


protected

attr_reader :generator_args
Expand Down Expand Up @@ -100,6 +116,11 @@ def ns_suffix
@ns_suffix ||= ns_parts[-1]
end

def singular_url_helper(record_name)
# Similar to Rails::Generators::NamedBase.show_helper - but singular_route_name method is not available
"#{singular_table_name}_url(#{record_name})"
end

def value_for(attribute)
raw_value_for(attribute).inspect
end
Expand Down
133 changes: 133 additions & 0 deletions lib/generators/rspec/scaffold/templates/api_request_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# frozen_string_literal:true

require 'rails_helper'

# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator. If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails. There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.

<% module_namespacing do -%>
RSpec.describe <%= controller_class_name %>Controller, <%= type_metatag(:request) %> do
# This should return the minimal set of attributes required to create a valid
# <%= class_name %>. As you add validations to <%= class_name %>, be sure to
# adjust the attributes here as well.
let(:valid_attributes) {
skip("Add a hash of attributes valid for your model")
}

let(:invalid_attributes) {
skip("Add a hash of attributes invalid for your model")
}

# This should return the minimal set of values that should be in the headers
# in order to pass any filters (e.g. authentication) defined in
# <%= controller_class_name %>Controller. Be sure to keep this updated too.
let(:valid_headers) {
{}
}

<% unless options[:singleton] -%>
describe "GET /index" do
it "renders a successful response" do
<%= class_name %>.create! valid_attributes
get <%= "#{index_helper}_url" %>, headers: valid_headers, as: :json
expect(response).to be_successful
end
end
<% end -%>

describe "GET /show" do
it "renders a successful response" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
get <%= singular_url_helper(file_name) %>, as: :json
expect(response).to be_successful
end
end

describe "POST /create" do
context "with valid parameters" do
it "creates a new <%= class_name %>" do
expect {
post <%= "#{index_helper}_url" %>,
params: { <%= ns_file_name %>: valid_attributes }, headers: valid_headers, as: :json
}.to change(<%= class_name %>, :count).by(1)
end

it "renders a JSON response with the new <%= ns_file_name %>" do
post <%= "#{index_helper}_url" %>,
params: { <%= ns_file_name %>: valid_attributes }, headers: valid_headers, as: :json
expect(response).to have_http_status(:created)
expect(response.content_type).to eq("application/json")
expect(response.location).to eq(<%= singular_url_helper("#{class_name}.last") %>)
end
end

context "with invalid parameters" do
it "does not create a new <%= class_name %>" do
expect {
post <%= "#{index_helper}_url" %>,
params: { <%= ns_file_name %>: invalid_attributes }, as: :json
}.to change(<%= class_name %>, :count).by(0)
end

it "renders a JSON response with errors for the new <%= ns_file_name %>" do
post <%= "#{index_helper}_url" %>,
params: { <%= ns_file_name %>: invalid_attributes }, headers: valid_headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.content_type).to eq("application/json")
end
end
end

describe "PATCH /update" do
context "with valid parameters" do
let(:new_attributes) {
skip("Add a hash of attributes valid for your model")
}

it "updates the requested <%= ns_file_name %>" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
patch <%= singular_url_helper(file_name) %>,
params: { <%= singular_table_name %>: invalid_attributes }, headers: valid_headers, as: :json
<%= file_name %>.reload
skip("Add assertions for updated state")
end

it "renders a JSON response with the <%= ns_file_name %>" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
patch <%= singular_url_helper(file_name) %>,
params: { <%= singular_table_name %>: invalid_attributes }, headers: valid_headers, as: :json
expect(response).to have_http_status(:ok)
expect(response.content_type).to eq("application/json")
end
end

context "with invalid parameters" do
it "renders a JSON response with errors for the <%= ns_file_name %>" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
patch <%= singular_url_helper(file_name) %>,
params: { <%= singular_table_name %>: invalid_attributes }, headers: valid_headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.content_type).to eq("application/json")
end
end
end

describe "DELETE /destroy" do
it "destroys the requested <%= ns_file_name %>" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
expect {
delete <%= singular_url_helper(file_name) %>, headers: valid_headers, as: :json
}.to change(<%= class_name %>, :count).by(-1)
end
end
end
<% end -%>
136 changes: 136 additions & 0 deletions lib/generators/rspec/scaffold/templates/request_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# frozen_string_literal:true

require 'rails_helper'

# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator. If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails. There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.

<% module_namespacing do -%>
RSpec.describe <%= controller_class_name %>Controller, <%= type_metatag(:request) %> do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JonRowe In an older PR comment You disliked using the word controller in the description title, is that still the case?

Because I would tend to disagree. The specs are describing that specified controller. What else should it be named?

It could also help editor features like "jump to test file", although I didn't see it working in RubyMine (yet)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request specs don't test controllers, they test specific requests. They route to a certain path and something happens, it could be the controller you intend, it might not. It could be terminated in a rack middleware, it could be routed to a different controller.

# This should return the minimal set of attributes required to create a valid
# <%= class_name %>. As you add validations to <%= class_name %>, be sure to
# adjust the attributes here as well.
let(:valid_attributes) {
skip("Add a hash of attributes valid for your model")
}

let(:invalid_attributes) {
skip("Add a hash of attributes invalid for your model")
}

<% unless options[:singleton] -%>
describe "GET /index" do
it "renders a successful response" do
<%= class_name %>.create! valid_attributes
get <%= "#{index_helper}_url" %>
expect(response).to be_successful
end
end
<% end -%>

describe "GET /show" do
it "renders a successful response" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
get <%= singular_url_helper(file_name) %>
expect(response).to be_successful
end
end

describe "GET /new" do
it "renders a successful response" do
get <%= "#{new_helper}" %>
expect(response).to be_successful
end
end

describe "GET /edit" do
it "render a successful response" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
get <%= "edit_#{singular_url_helper(file_name)}" %>
expect(response).to be_successful
end
end

describe "POST /create" do
context "with valid parameters" do
it "creates a new <%= class_name %>" do
expect {
post <%= "#{index_helper}_url" %>, params: { <%= ns_file_name %>: valid_attributes }
}.to change(<%= class_name %>, :count).by(1)
end

it "redirects to the created <%= ns_file_name %>" do
post <%= "#{index_helper}_url" %>, params: { <%= ns_file_name %>: valid_attributes }
expect(response).to redirect_to(<%= singular_url_helper("#{class_name}.last") %>)
end
end

context "with invalid parameters" do
it "does not create a new <%= class_name %>" do
expect {
post <%= "#{index_helper}_url" %>, params: { <%= ns_file_name %>: invalid_attributes }
}.to change(<%= class_name %>, :count).by(0)
end

it "renders a successful response (i.e. to display the 'new' template)" do
post <%= "#{index_helper}_url" %>, params: { <%= ns_file_name %>: invalid_attributes }
expect(response).to be_successful
end
end
end

describe "PATCH /update" do
context "with valid parameters" do
let(:new_attributes) {
skip("Add a hash of attributes valid for your model")
}

it "updates the requested <%= ns_file_name %>" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
patch <%= "#{singular_table_name}_url(#{file_name})" %>, params: { <%= singular_table_name %>: new_attributes }
<%= file_name %>.reload
skip("Add assertions for updated state")
end

it "redirects to the <%= ns_file_name %>" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
patch <%= singular_url_helper(file_name) %>, params: { <%= singular_table_name %>: new_attributes }
<%= file_name %>.reload
expect(response).to redirect_to(<%= singular_table_name %>_url(<%= file_name %>))
end
end

context "with invalid parameters" do
it "renders a successful response (i.e. to display the 'edit' template)" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
patch <%= singular_url_helper(file_name) %>, params: { <%= singular_table_name %>: invalid_attributes }
expect(response).to be_successful
end
end
end

describe "DELETE /destroy" do
it "destroys the requested <%= ns_file_name %>" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
expect {
delete <%= singular_url_helper(file_name) %>
}.to change(<%= class_name %>, :count).by(-1)
end

it "redirects to the <%= table_name %> list" do
<%= file_name %> = <%= class_name %>.create! valid_attributes
delete <%= singular_url_helper(file_name) %>
expect(response).to redirect_to(<%= "#{index_helper}_url" %>)
end
end
end
<% end -%>
Loading