I'm new to Rspec and Capybara. The error I'm getting is Navigation from homepage Visit Gallery
Failure/Error: visit root_path
NoMethodError:undefined method 'testimonials' for nil:NilClass
I tried let two different ways in order to define a variable in the spec. I've added both of them so I can get feedback on what I'm doing wrong.
class WelcomeController < ApplicationController
def index
#event = Event.last
#event.testimonials.first ? #latest_testimonial = #event.testimonials.first.id : #latest_testimonial = nil
end
end
feature 'Navigation from homepage' do
before :each do
visit root_path
find('#nav-menu').find('h1').click #opens up navigation bar
end
scenario 'Visit Gallery' do
find('#nav-gallery').find('.no_bar').click
let(:event) {Event.last} #1st attempt at solving Rspec error.
let(:event) {mock_model(Event, id: 3, name: "Jack & Jill", date: "2004-06-10", created_at: "2014-03-10 02:57:45", updated_at: "2014-03-10 02:57:45")} #2nd attempt at solving Rspec error.
controller.stub(:event)
expect(page).to have_css 'img.photos'
end
end
The other answer is correct - here is essentially the same content in simpler (or maybe just more drawn out by explaining things you already know) terms.
The first thing your test does is execute the before block, which visits root_path, presumably calling the index action on WelcomeController. The first thing that method does does is call Event.last, which returns nil, because your test database is empty so there is no last record to return. Then when you call testimonials on #event, you get the error you see because #event is nil.
To remedy this, you need to create an Event record in the database before you navigate to the root_path and call the index action. One way to do that would be to add this line before visit root_path:
Event.create(name: "Jack & Jill" [...])
That will create a record in the database, so Event.last will return something.
You would also get rid of both let statements and the controller.stub thing. Those are unnecessary now (and had other problems anyway). That should be enough to get that code to at least run.
In practice, you won't find just creating records like I've shown here to be a sustainable approach - that's where factories (with a tool like FactoryGirl) or mocks/stubs come in. Then you use let to define those items just once in your before block, and still limit the overhead consumed by creating them to those subsequent tests where they are actually used.
Regardless, the main point is that the setup of objects (and records if needed) needs to be done before you start triggering controller actions that assume those objects exist.
Event.last is returning nil.
The typical (and easiest) way to do this would be to just create an event in your test, before trying to visit the site, and then your controller will use it. Feature specs should really contain the minimal amount of mocking and stubbing - they represent a real user interacting with the system.
Your stubs are not getting used (and in fact are errors in themselves). This is verified by the fact that your error is coming before they even get set.
Related
Let's say I have a database to store the data of people (name, email). And there's an update method in the controller. The method is like:
def update
#people = People.find(params[:id])
if #people.update(people_params)
redirect_to people_path
else
render 'edit'
end
end
I wonder how can I test the situation that the update failed? Or do I really need to test it? I have searched it on StackOverflow, there is a link about it, but it not says if I should or should not to test it. Could anyone give me some help about it? If it can be test, how? Thank you so much!
You don’t need to test Ruby and Rails internals. Either you trust both of them do work as expected, or you’d better switch to some other language / framework.
Whether you still want to test it, mock everything unrelated. Here is an example of doing this with rspec and flexmock.
describe '#update' do
let(:person) { build(:people) }
before do
flexmock(People).should_receive(:find).once.returns person
flexmock(person).should_receive(:update).once.returns false
end
it 'redirects to `edit` page' do
...
end
end
Typically the update failure would be due to object to be saved not being valid, according to its validation criteria. You would normally test the validation criteria in the model specs, but when testing the controller, or in integration tests, you might want to ensure that the #edit render occurs when the user tries to save an invalid model.
If the model does not have any validation criteria, you can probably skip the else render :edit altogether. It's part of the Rails scaffold that may not apply in all cases.
You can test your update fail scenario by trying to save an invalid model. You would typically confirm that the user was correctly informed of the validity problem (flash message).
There's no right or wrong as to whether or not to test. Personally, I would test it, b/c I like TDD, and I prefer to over-test rather than under-test. Many would not.
I created an RSpec spec to test if a POST #create action works properly:
describe "POST #create" do
it "creates a career" do
expect {
post "/careers/", career: attributes_for(:career)
}.to change(Career, :count).by 1
end
end
The above code works correctly. The issue happens when I create another test to allow only users with roles of "admin". Do I need to create a new user, log them in, and then run the above test? Do I need to do this for all future tests which have a restriction based on the user's role?
Is there another way to do this type of testing? 1) Just test if the create method works, and 2) only allow Users with "admin" role access the GET #new and POST #create methods?
When your feature is fully developed you'll want to have the following tests:
one happy-path test in which an admin creates a career
one in which a non-admin tries to create a career but is prevented from doing so
possibly another in which a not-logged-in user tries to create a career but is prevented from doing so (whether you want this depends on whether you have to write different code to handle non-logged-in and logged-in non-admin users), and
possibly other tests of different scenarios in which an admin creates a career.
This idea of having one complete, happy-path test is one of the most fundamental patterns in testing, but I'm not aware that it has a name, other than being implied by the term "happy path".
It looks like you're doing TDD. Great! To get from where you are now to the above list of tests, the next test to write is the one where the non-logged-in user is prevented from creating a career. To make both tests pass at the same time you'll need to change the first test to log in an admin. And if you need more tests of successfully creating a career (bullet 4), yes, you'll need to log in an admin in those too.
Side notes:
Unless you already have it, I'd write your happy-path spec not as a controller spec but as a feature spec (an acceptance test), so that you specify the important parts of the UI and integration-test the entire stack. Your failed-authentication specs might work as controller specs, although you might decide you need to acceptance-test the UI when a user doesn't have permissions for at least one of those scenarios.
I really don't like that expect {}.to change syntax. It prevents you from making any other expectations on the result of posting. In your example I would want to expect that the HTTP response status is 200 (response.should be_success). As I said, though, my first spec would be a feature spec, not a controller spec.
So, this is an interesting question. Yes, you should definitely (IMO) test authentication separately from the target method/action. Each of these constitutes a unit of functionality and should be tested as such.
In my current project, I'm favoring POROs (I often keep them in a directory called 'managers' although I know many people prefer to call them 'services') for all sorts of things because it lets me isolate functionality and test it independently. So, I might end up with something like:
# controllers/foo_controller.rb
class FooController < ApplicationController
before_action :authenticate
def create
#results = FooManager.create(params)
redirect_to (#results[:success] ? my_happy_path : my_sad_path)
end
def authenticate
redirect_to unauthorized_path unless AuthenticationManager.authenticate(params, request)
end
end
# managers/foo_manager.rb
class FooManager
class << self
def create(params)
# do a bunch of great stuff and return a hash (perhaps a
# HashWithIndifferentAccess, if you like) which will
# allow for evaluation of #results[:success] back in the
# controller.
end
end
end
# managers/authentication_manager.rb
class AuthenticationManager
class << self
def authenticate(params, request)
# do a bunch of great stuff and return a boolean
end
end
end
With an approach like this, I can very easily test FooManager.create and AuthenticationManager.authenticate (as well as FooController.create and FooController.authenticate routing) all independently. Hooray!
Now, whether your authentication framework or controller method is behaving correctly at a unit level, as Dave points out very well, is a separate issue from whether your entire system is behaving as expected. I'm with him on having high-level integration tests so you're clear about what 'done' looks like and you know when to holler 'ship it!'
(This question is similar to Ruby on Rails Method Mocks in the Controller, but that was using the old stub syntax, and besides, that didn't receive a working answer.)
short form
I want to test my controller code separate from my model code. Shouldn't the rspec code:
expect(real_time_device).to receive(:sync_readings)
verify that RealTimeDevice#sync_readings gets called, but inhibit the actual call?
details
My controller has a #refresh method that calls RealTimeDevice#sync_readings:
# app/controllers/real_time_devices_controller.rb
class RealTimeDevicesController < ApplicationController
before_action :set_real_time_device, only: [:show, :refresh]
<snip>
def refresh
#real_time_device.sync_readings
redirect_to :back
end
<snip>
end
In my controller tests, I want to verify that (a) that #real_time_device is being set up and (b) the #sync_reading model method is getting called (but I don't want to invoke the model method itself since that's covered by the model unit tests).
Here's my controller_spec code that doesn't work:
# file: spec/controllers/real_time_devices_controller_spec.rb
require 'rails_helper'
<snip>
describe "PUT refresh" do
it "assigns the requested real_time_device as #real_time_device" do
real_time_device = RealTimeDevice.create! valid_attributes
expect(real_time_device).to receive(:sync_readings)
put :refresh, {:id => real_time_device.to_param}, valid_session
expect(assigns(:real_time_device)).to eq(real_time_device)
end
end
<snip>
When I run the test, the actual RealTimeDevice#sync_readings method is getting called, i.e., it's trying to call code in my model. I thought the line:
expect(real_time_device).to receive(:sync_readings)
was necessary and sufficient to stub the method and verify that it got called. My suspicion is that it needs to be a double. But I can't see how to write the test using a double either.
What am I missing?
You're setting an expectation on a specific instance of RealTimeDevice. The controller fetches the record from the database, but in your controller, it's using another instance of RealTimeDevice, not the actual object you set the expectation on.
There are two solutions to this problem.
The Quick and Dirty
You can set an expectation on any instance of RealTimeDevice:
expect_any_instance_of(RealTimeDevice).to receive(:sync_readings)
Note that this is not the best way to write your spec. After all, this doesn't guarantee that your controller fetches the right record from the database.
The Mocking Approach
The second solution involves a bit more work, but will cause your controller to be tested in isolation (which it is not really if it's fetching actual database records):
describe 'PUT refresh' do
let(:real_time_device) { instance_double(RealTimeDevice) }
it 'assigns the requested real_time_device as #real_time_device' do
expect(RealTimeDevice).to receive(:find).with('1').and_return(real_time_device)
expect(real_time_device).to receive(:sync_readings)
put :refresh, {:id => '1'}, valid_session
expect(assigns(:real_time_device)).to eq(real_time_device)
end
end
Quite some things have changed. Here's what happens:
let(:real_time_device) { instance_double(RealTimeDevice) }
Always prefer using let in your specs rather than creating local variables or instance variables. let allows you to lazy evaluate the object, it's not created before your spec requires it.
expect(RealTimeDevice).to receive(:find).with('1').and_return(real_time_device)
The database lookup has been stubbed. We're telling rSpec to make sure that the controller fetches the correct record from the database. The important part is that the very instance of the test double created in the spec is being returned here.
expect(real_time_device).to receive(:sync_readings)
Since the controller is now using the test double rather than an actual record, you can set expectations on the test double itself.
I've used rSpec 3's instance_double, which verifies the sync_readings method is actually implemented by the underlying type. This prevents specs from passing when a method would be missing. Read more about verifying doubles in the rSpec documentation.
Note that it's not required at all to use a test double over an actual ActiveRecord object, but it does make the spec much faster. The controller is now also tested in complete isolation.
I want to use this piece of code to retrieve a user's list of credit cards on file with Stripe to show on his profile (/users/:id)
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
Thing is, I'm not exactly sure where (in terms of Rails best practices) it fits. My first tought is to put it in the show method of the User controller since it's not really business logic and doesn't fit in the model. I've also looked at helper methods but they seem (from my understanding) to be used strictly when toying around with HTML.
Can any of you Rails experts chime in?
Thanks!
Francis
Good question. Whenever you see an instance variable in rails (starting with a #), it usually is a view/controller bit of code.
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
However looking at the tail end of that
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
This might fit better of in a model, where you can reuse that same line, but have the safety of added error handling and predictable behavior. For example
# user.rb
def stripe_customer_cards
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
rescue Stripe::InvalidRequestError
false # You could use this to render some information in your views, without breaking your app.
end
Also note the use of self. This usually implies use of a Rails model, because calling self in the controller actually refers to the controller, rendering it almost worthless, unless you really know what you are doing.
EDIT
To render an error message, simply write a call to redirect or render, with the alert option.
if #stripe_cards = current_user.stripe_customer_cards
# Your being paid, sweet!
else
# Render alert info :(
render 'my_view', alert: 'This is an alert'
redirect_to other_path, alert: 'Another alert'
end
I also like to make it a point to mention that you should not handle errors just because you can. Don't handle errors you don't expect. If you handle errors you don't expect it will
Confuse users
Make bugs in code harder to fix
Exaggerate the time before an error is recognized
I'd recommend adding a virtual attribute in your User model:
# app/models/user.rb
def cards
Stripe::Customer.retrieve(stripe_customer_id).cards.all # note the spelling of `retrieve`
end
Then, you'd be able to access all a users cards in the following manner:
user = User.first
#=> #<User id:1>
user.cards
#=> [Array of all cards]
This is the most confusing thing I have dealt with in a long time. I have a controller action that sends an e-mail when a comment is made:
class CommentsController < ActionController::Base
def create
#comment = Comment.new(params[:comment])
#comment.author = current_user
#comment.save
CommentMailer.recent_comment_made(#comment).deliver
respond_with(#comment)
end
end
I would like to test it. My tests go to the proper page (I've verified this), where there is a form with action /comments. It then submits the form and returns the view with a successful flash (using FlashResponder) and everything seems great... but the e-mail is never sent. In fact, the entire create action is never called. The weird thing is the same process works in development but sends the e-mail!
Now I know it's getting to the correct controller, because I can add this:
before_filter { raise }
And the tests fail. I can add:
before_filter { p params }
And I see the parameters, which have controller as 'comments' and action as 'create'. But if I add:
def create
raise
...
No exception is raised. In fact I can just comment out the entire create method and the test still passes, with the comment being created and everything. I am not using InheritedResources or anything of that kind. And like I said... it works on development!
I've used save_and_open_page after every step and it all looks good. The form action is correct. The flash message is correct. The assertion that the comment is created is correct... even when the create method is commented out completely.
Originally I thought that it was the wrong controller or that Cucumber was using some older version of my controller for some unknown reason, but when I add the before_filters to raise/print params... that all happens and works as expected.
Does anyone know what could be going on here, or any way I can at least SEE what is going on here? I am all out of ideas. My feature looks like this:
Given I visit the page
And I enter a comment
When I submit the comment
Then the e-mail is delivered
And the comment is saved to the database
These are made more generic than they actually are to conceal the actual intent of the project. The step definition pseudo code is:
visit ...
fill_in ...
find('submit button').click
assert ActionMailer::Base.deliveries.include? ...
assert comments.present?
Pretty simple stuff. Visit a page, submit a form, assert that the stuff in the create action worked.
WOW...
What happened was there was a copy of the controller in our api/v1 directory (but not namespaced to api/v1) on accident. The tests were loading that version, while development was loading the 'actual' version.