I an trying to organize code by making partial html.erb files that are shared frequently(e.g. _form.html.erb)
I want to check whether my partial code works well with different models/controllers, so I am manually doing CRUD from the views.
It would be nicer to test my code automatically using Rspec but I have no idea. Can anyone give me some guidance how to test controller code with Rspec?
To test controller and views together you write feature specs and request specs .
Request specs are lower level specs where you send HTTP requests to your application and write expectations (aka assertions in TDD lingo) about the response. They are a wrapper around ActionDispatch::IntegrationTest. Request specs should be considered the replacement for controller specs, the use of which are discouraged by by the RSpec and Rails teams.
# spec/requests/products_spec.rb
require 'rails_helper'
RSpec.describe "Products", type: :request do
describe "GET /products" do
let!(:products) { FactoryBot.create_list(:product, 4) }
it "contains the product names" do
get "/products"
expect(response).to include products.first.name
expect(response).to include products.last.name
end
end
end
Feature specs are higher level specs that focus on the user story. They often serve as acceptance tests. They use a browser simulator named Capybara which emulates a user clicking his way through the application. Capybara can also run headless browsers (headless chrome, firefox, phantom js, webkit etc) and "real" browsers through selenium. The minitest equivalent is ActionDispatch::SystemTestCase but RSpec features do not wrap it (it took minitest/testunit years to catch up here).
# Gemfile
gem 'capybara'
# spec/features/products_spec.rb
require 'rails_helper'
RSpec.feature "Products" do
let!(:products) { FactoryBot.create_list(:product, 4) }
scenario "when a user views a product" do
visit '/'
click_link 'Products'
click_link products.first.name
expect(page).to have_content products.first.name
expect(page).to have_content products.first.description
end
end
This specs tests the products#index and products#show action as well as the root page and the associated views.
Both types of specs have their strengths and weaknesses. Feature tests are good for testing large swaths of the application but are heavy. Request specs are faster and its easier to replicate a specific request that causes a bug/issue but you're basically just matching HTML with regular expressions which is highly limited.
To check whether partial code works well with different models/controllers. You can add render_views in controller specs.
How to test controller code with Rspec?
Read the official doc https://relishapp.com/rspec/rspec-rails/docs/controller-specs
And this page may help: https://thoughtbot.com/blog/how-we-test-rails-applications
Related
I'm learning Ruby.
I'm now trying to test one of my controller.
My test file is myapp/test/testing.rb while my controller is located at myapp/app/controllers/test_controller.rb.
The content of testing.rb is
data = testController.mymethod()
puts(data)
But when doing
ruby myapp/test/testing.rb
in the terminal, I get a warning :
Traceback (most recent call last):myapp/test/testing.rb:6:in `':
uninitialized constant testController (NameError)
Could someone explain me what i'm doing is wrong and how I should do this ?
Thanks !
The currently accepted way to test rails controllers is by sending http requests to your application and writing assertions about the response.
Rails has ActionDispatch::IntegrationTest which provides integration tests for Minitest which is the Ruby standard library testing framework. Using the older approach of mocking out the whole framework with ActionDispatch::ControllerTest is no longer recommended for new applications.
This is your basic "Hello World" example of controller testing:
require 'test_helper'
class BlogFlowTest < ActionDispatch::IntegrationTest
test "can see the welcome page" do
get "/"
assert_select "h1", "Welcome#index"
end
end
You can also use RSpec which is different test framework with a large following. In RSpec you write request specs which are just a thin wrapper on top of ActionDispatch::IntegrationTest.
RSpec is DSL written on Ruby special for this purpose. You have to be familiar with it to write tests of your code. As for RSpec team note:
`
The official recommendation of the Rails team and the RSpec core team is to write request specs instead. Request specs allow you to focus on a single controller action, but unlike controller tests involve the router, the middleware stack, and both rack requests and responses. This adds realism to the test that you are writing, and helps avoid many of the issues that are common in controller specs. In Rails 5, request specs are significantly faster than either request or controller specs were in rails 4.
So you have to write requestspecs for your controller. Something like this:
spec/controllers/users_controller_spec.rb
RSpec.describe "Test", type: :request do
describe "request list of all tests" do
user = User.create(email: 'test#user.com', name: 'Test User')
get users_path
expect(response).to be_successful
expect(response.body).to include("Test User")
end
end
Something like this. Hope it will help
I have the following code in my controller:
private
def remaining_words
#remaining_words = Vocab.all.where.not(id: session[:vocab_already_asked])
#questions_remaining = #remaining_words.length - 4
#quiz_words = #remaining_words.shuffle.take(4)
And here is my test:
feature 'Quiz functionality' do
scenario "gets 100% questions right in quiz" do
visit(root_path)
visit(start_quiz_path)
assigns(:questions_remaining).length.to_i.times do
orig_value = find('#orig', visible: false).value
choose(option: orig_value)
click_on('Submit')
expect(page).to have_content('You got it right!')
expect(page).not_to have_content('Sorry, wrong answer!')
end
expect(page).to have_content("Your score is 27/27")
save_and_open_page
end
end
I get the error message when I run the test:
NoMethodError: undefined method `assigns' for #<RSpec::ExampleGroups::QuizFunctionality:0x007f8f2de3f2b0>
# ./spec/features/quizzes_spec.rb:9:in `block (2 levels) in <top (required)>'
I've also tried using controller.instance_variable_get(:remaining_words) and get this error message
NameError:
undefined local variable or method `controller' for #<RSpec::ExampleGroups::QuizFunctionality:0x007fc4b99251a0>
Am I missing something in setting up the test? Should I be using describe instead of feature to enable the assign method?
assigns was solely available in controller tests - it was depreciated in Rails 5.
Testing what instance variables are set by your controller is a bad
idea. That's grossly overstepping the boundaries of what the test
should know about. You can test what cookies are set, what HTTP code
is returned, how the view looks, or what mutations happened to the DB,
but testing the innards of the controller is just not a good idea.
- David Heinemeier Hansson
In RSpec controller specs wrap the deprecated ActionController::TestCase.
A controller spec is identified by having the type: :controller metadata.
RSpec.describe ThingsController, type: :controller do
# ...
describe "GET #index" do
end
end
If you have set config.infer_spec_type_from_file_location! in config.infer_spec_type_from_file_location! RSpec will infer that any spec in spec/controllers has type: :controller.
You should avoid controller specs for new applications in favor of request and feature specs. One of the main problems with controller specs besides the violation of encapsulation is that the entire request phase is stubbed, the request does not actually go through rack or the routes which can mask routing errors and means that Rack middleware like Warden (used by Devise) or sessions must be stubbed.
If you have a legacy application you can reintroduce assigns with a gem. If you are just learning RSpec you should select more up to date tutorials.
Feature specs are high-level tests meant to exercise slices of
functionality through an application. They should drive the
application only via its external interface, usually web pages.
https://relishapp.com/rspec/rspec-rails/v/3-7/docs/feature-specs
Use feature specs for high level tests centered on the user story. Use RSpec.feature "New Cool Feature" to write a feature spec.
Request specs provide a thin wrapper around Rails' integration tests,
and aredesigned to drive behavior through the full stack, including
routing (provided by Rails) and without stubbing (that's up to you).
https://relishapp.com/rspec/rspec-rails/v/3-7/docs/request-specs/request-spec
Use RSpec.describe "Some resource", type: :request to write a feature spec.
Request specs are invaluable for testing API' or when you just need fast tests that ensure that the correct mutations happened to the DB or that the correct http responses are sent.
See:
https://blog.bigbinary.com/2016/04/19/changes-to-test-controllers-in-rails-5.html
https://github.com/rails/rails/issues/18950
You're writing feature specs/integration tests which don't have access to the controller/controller instance variables. They are meant to be more of a black box test executed from the users perspective. When setting up the data for the test you should know how many questions need to be asked and then either hardcode that in your test, or, better yet, detect based on the page contents whether there are more questions to answer (just like a user would have to).
I've been reading a ton of docs and SO questions/ answers on all the changes as Rspec has evolved, want to be sure of the answer...
My goal is to use native Rspec-rails (I have 3.2.2) to do integrated controller/view tests that look for 1) CSS classes and 2) ID selectors. In other words given this view snippet:
<!-- staticpages/dashboard -->
<div class="hidden">Something</div>
<div id="creation">This</div>
This should pass (however it should be semantically written):
describe StaticpagesController do
render_views
it "should find everything" do
get :dashboard
expect(response.body).to have_selector("div#creation")
expect(response.body).to have_css("hidden")
expect(response.body).to_not have_selector("div#nothinghere")
end
end
I would like to do this without additional gems like Capybara; is that possible?
Here's a high level of what I've learned so far:
in Rspec 1, the have_tag feature allowed you to do this (http://glenngillen.com/thoughts/using-rspec-have-tag)
in Rspec 2, the have_tag was replaced with webrat's have_selector (have_tag vs. have_selector)
in Rspec 3, webrat support has been removed (http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/)
In my own experimentation, the code above generated:
Expect<long response.body here>.to respond to `has_selector?`
So that has indeed been deprecated. Still, I'd love to know if there's some other way to do this that I don't know about.
IF it turns out I need Capybara to do these fancy matchers, is there a way to do this in my integrated controller/view specs? My understanding is that I have to add type: :feature to the describe StaticpagesController line to use Capybara's matchers. However, the minute I do that, render_views is no longer available (since it's limited to type: :controller). Note, render_views also dies if, per this post (https://www.relishapp.com/rspec/rspec-rails/v/2-99/docs/controller-specs/use-of-capybara-in-controller-specs), I manually include Capybara::DSL into my controller spec. Anyway, I would really like to not have to rewrite my current controller specs into a bunch of feature specs...
It would seem that you want feature specs (with Capybara) more than controller specs as you're not testing any of the things controller specs are typically used to test such as:
whether a template is rendered
whether a redirect occurs
what instance variables are assigned in the controller to be shared with the view
the cookies sent back with the response
Also, you probably want to consider writing feature specs for new apps over controller specs since controller tests will probably be dropped in Rails 5 in favor of the writing of integration/feature tests.
For a description of the different kinds of specs that you could write, and what they're typically used for,
see this SO answer.
I'm trying to set up some feature specs before I get into refactoring some of my company's old code. It's kind of an unconventional setup, but I was able to figure out enough about test doubles to bypass the authentication enough to get started. One problem I'm still having is that some of the instance variables set in these methods I'm bypassing are expected by the view, so I get undefined method for nil:NilClass errors. I would like to get the specs running before I make any changes to the program code. In this case, I could easily just move the particular instance variable to another method. But I'm sure more situations like this will come up. Here's the example I'm currently working on:
def security_level
#right_now = Time.now
#
# other code that wont work without
# connecting to a remote authentication
# server
#
end
Then in my spec:
feature 'Navigation' do
before(:each) do
allow_any_instance_of(ApplicationController).to receive(:security_level).and_return(nil)
end
scenario 'is possible' do
visit root_path
expect(page.has_content?('Quick Stats'))
end
end
Here's the error, coming from #right_now.year in the view
Failure/Error: visit root_path
NoMethodError:
undefined method `year' for nil:NilClass
# ./common/views/layouts/bootstrap/layout.haml:63
EDIT: Is there a way to set instance variables on the controller from within a feature spec?
There's no easy way to accomplish what you want.
The feature spec is handled mostly by Capybara, not RSpec. Capybara runs the majority of the browser / rails server behavior in an external process. This make it inaccessible from RSpec's point-of-view. Thus you cannot use stubs / doubles in this manner.
Feature specs are largely meant to be end-to-end acceptance tests. The idea is to exercise your system as those who would use your system do. Generally, in these types of specs you perform various "workflows". This means, having the spec, log a user in, navigate to particular pages, filling forms, clicking buttons and links. You then generally make your expectations on what you see in the view.
This means your spec would look more like:
feature 'Navigation' do
let(:regular_user) { User.create!(name: 'A Regular User') }
def sign_in(a_user)
visit sign_in_url
# fill out form
click_button 'Sign In'
end
before(:each) do
sign_in(regular_user)
end
scenario 'is possible' do
visit root_path
expect(page.has_content?('Quick Stats'))
end
end
https://github.com/per-garden/fakeldap may provide enough ldap functionality for your feature tests.
I'm trying to use RSPec to test my Ruby on Rails 3.2 app.
When I generated the controller, some specs were created for the views and the controller. I tried adding the following test to the controller spec:
it "should have h1 of Home" do
visit '/home/index'
page.should have_selector('h1', text: "Home")
end
But couldn't get it to pass.
When I ran rails generate integration_test home and put the same test in the homes_spec, the test works fine.
Why does it matter where the spec goes?
Thanks
According to the doc:
If you would like to use webrat or capybara with your request specs,
all you have to do is include one of them in your Gemfile and RSpec
will automatically load them in a request spec.
And request specs live in integration and requests folders.
Simply.