I have a problem with calling 'POST' method only once in my test suits.
let(:foo) {post :foo_controller arguments}
it 'FIRST: should validate post response first field' do
expect(foo[:first_field]).to match('something')
end
it 'SECOND: should validate post response second field' do
expect(foo[:second_field]).to match('something else')
end
Now foo's "POST" action is called twice.
I would like to make it that FIRST requests 'POST' and gets a request value, but SECOND only gets a value, which is persisted, without calling this 'POST'.
Is there an elegant way to solve this problem?
You could use a before(:all) block, not sure what post actually returns though.
before(:all) do
#my_response = post :foo_controller arguments
end
Hope that helps!
I us a small helper to fix this issue for me.
This is the helper module:
module ControllerSpecHelpers
# example:
#
# describe 'my request' do
# examine_response {get '/foo/bar'}
# it {should be_ok}
# end
#
#
def examine_response &block
before(:all) do
self.instance_exec &block
end
subject {last_response}
end
end
I configure Rspec to use it in my spec helper:
RSpec.configure do |conf|
# snip ...
conf.extend ControllerSpecHelpers
end
Then when I need to execute the call only once, and test multiple properties, I use it like this:
describe "when signing up" do
examine_response do
post "/signup", {email: 'signup#test.com', password: 'password'}
end
it {should be_ok}
it "body says welcome" do
expect(subject.body).to include 'welcome'
end
end
Here's more detail on how extending Rspec works:
http://timnew.github.io/blog/2012/08/05/extend-rspec-dsl/
Related
I'm writing an RSpec request spec, which looks roughly like (somewhat shortened for brevity):
describe 'Items', type: :request do
describe 'GET /items' do
before do
allow_any_instance_of(ItemsController).to receive(:current_user).and_return(user)
get '/items'
#parsed_body = JSON.parse(response.body)
end
it 'includes all of the items' do
expect(#parsed_body).to include(item_1)
expect(#parsed_body).to include(item_2)
end
end
end
The controller looks like:
class ItemsController < ApplicationController
before_action :doorkeeper_authorize!
def index
render(json: current_user.items)
end
end
As you can see, I'm trying to stub doorkeeper's current_user method.
The tests currently pass and the controller works as expected. My question is about the line:
allow_any_instance_of(ItemsController).to receive(:current_user).and_return(user)
I wrote this line based on the answers in How to stub ApplicationController method in request spec, and it works. However, the RSpec docs call it a "code smell" and rubocop-rspec complains, "RSpec/AnyInstance: Avoid stubbing using allow_any_instance_of".
One alternative would be to get a reference to the controller and use instance_double(), but I'm not sure how to get a reference to the controller from a request spec.
How should I write this test avoid code smells / legacy testing approaches?
You're supposed to be on vacation.
I think the right way is to avoid stubbing as much as you can in a request spec, doorkeeper needs a token to authorize so I'd do something like:
describe 'Items', type: :request do
describe 'GET /items' do
let(:application) { FactoryBot.create :oauth_application }
let(:user) { FactoryBot.create :user }
let(:token) { FactoryBot.create :access_token, application: application, resource_owner_id: user.id }
before do
get '/items', access_token: token.token
#parsed_body = JSON.parse(response.body)
end
it 'includes all of the items' do
expect(#parsed_body).to include(item_1)
expect(#parsed_body).to include(item_2)
end
end
end
Here are some examples of what those factories might look like.
Lastly, nice SO points!
have you thought not to mock current_user at all?
if you write a test helper to sign in a user before your request spec, current_user will be populate automatically as if it was a real user. The code would look like this:
before do
sign_in user
get '/items'
#parsed_body = JSON.parse(response.body)
end
if you are using devise gem for authentication it has a nice written wiki page about that here.
This approach is also recommended here by #dhh
i am using rails and want to write a test for password reset in Rspec. i am quite new to testing.
this is what i have done so far:
require 'rails_helper'
describe UsersController, type: :controller do
describe 'post #reset_password' do
let(:user) { create(:user) }
context "reset password" do
def do_request
patch :update_password
end
before { do_request }
it { expect(ActionMailer::Base.deliveries.count(1) }
end
end
end
every time i run this it gives ma an syntax error in
"it { expect(ActionMailer::Base.deliveries.count(1) } ".
i want to check whether the email successfully sent of not and if the user have key in the email.
Thanks!
1) you miss ) at last here so got syntax error
it { expect(ActionMailer::Base.deliveries.count(1) }
to
it { expect(ActionMailer::Base.deliveries.count(1)) }
2)
If you want to check total deliveries. you can try
it 'should send an email' do
ActionMailer::Base.deliveries.count.should == 1
end
also check sender
it 'renders the sender email' do
ActionMailer::Base.deliveries.first.from.should == ['notifications#domain.com']
end
Also check subject line
it 'should set the subject to the correct subject' do
ActionMailer::Base.deliveries.first.subject.should == 'Here Is Your Story!'
end
The problems you're having will most likely be fixed by writing better tests.
Here's generally how you would write tests for something like this.
Lets suppose in your routes file you have a post route that looks something like this
# config/routes.rb
post "/user/:id/reset_password", to: "users#reset_password"
And your User controller looks something like this
# app/controllers/users_controller.rb
class UsersController
...
def reset_password
user = User.find(params[:id])
user.reset_password!
SomeMailClass.email_reset_instructions(user)
end
end
and your User.rb model looks something like this
# app/models/user.rb
class User < ActiveRecord::Base
def reset_password!
update!(password: nil) # or whatever way you want/need to reset the password
end
end
and you have some type of mailing class to send your email
# app/models/some_mail_class.rb
class SomeMailClass
def self.email_reset_instructions(user)
# do something to send email...
end
end
The way you would go about testing this in the controller would be
# spec/controllers/users_controller_spec.rb
require 'rails_helper'
describe UsersController, type: :controller do
it "#reset_password" do
user_id = double(:user_id)
user = double(:user)
expect(User).to receive(:find).with(user_id).and_return(user)
expect(user).to receive(:reset_password!).and_return(true)
expect(SomeMailClass).to receive(:email_reset_instructions).with(user)
post :reset_password, id: user_id
end
end
But you shouldn't stop there. Because the implementation of the newly made method reset_password! and the SomeMailClass has yet to be tested. So you would write model/unit tests like this for them
# spec/models/user_spec.rb
require "rails_helper"
describe User do
it ".reset_password!" do
user = User.create(password: "foo")
expect(user.password).to eq "foo"
user.reset_password!
expect(user.password).to eq nil
end
end
Then you might install vcr and factory_girl gems and use them like so to test your mailer
# spec/models/some_mail_class_spec.rb
require "rails_helper"
describe SomeMailClass do
VCR.use_cassette "email_reset_instructions" do |cassette|
it ".email_reset_instructions" do
user = FactoryGirl.create(:user)
SomeMailClass.email_reset_instructions(user)
# you can write some expectations on the cassette obj to test.
# or you can write whatever expectations you need/desire
end
end
end
And in the end if there was something happening on the front end that a user would click that made this post request you would write a feature test for it as well.
Hope this helps!
I've recently learned how to stub in rspec and found that some benefits of it are we can decouple the code (eg. controller and model), more efficient test execution (eg. stubbing database call).
However I figured that if we stub, the code can be tightly tied to a particular implementation which therefore sacrifice the way we refactor the code later.
Example:
UsersController
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
User.create(name: params[:name])
end
end
Controller spec
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
By doing that didn't I just limit the implementation to only using User.create? So later if I change the code my test will fail even though the purpose of both code is the same which is to save the new user to database
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new
#user.name = params[:name]
#user.save!
end
end
Whereas if I test the controller without stubbing, I can create a real record and later check against the record in the database. As long as the controller is able to save the user Like so
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
post :create, :name => "abc"
user = User.first
expect(user.name).to eql("abc")
end
end
end
Really sorry if the codes don't look right or have errors, I didn't check the code but you get my point.
So my question is, can we mock/stub without having to be tied to a particular implementation? If so, would you please throw me an example in rspec
You should use mocking and stubbing to simulate services external to the code, which it uses, but you are not interested in them running in your test.
For example, say your code is using the twitter gem:
status = client.status(my_client)
In your test, you don't really want your code to go to twitter API and get your bogus client's status! Instead you stub that method:
expect(client).to receive(:status).with(my_client).and_return("this is my status!")
Now you can safely check your code, with deterministic, short running results!
This is one use case where stubs and mocks are useful, there are more. Of course, like any other tool, they may be abused, and cause pain later on.
Internally create calls save and new
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, options, &block)
object.save
object
end
end
So possibly your second test would cover both cases.
It is not straight forward to write tests which are implementation independent. That's why integration tests have a lot of value and are better suited than unit tests for testing the behavior of the application.
In the code you're presented, you're not exactly mocking or stubbing. Let's take a look at the first spec:
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
Here, you're testing that User received the 'create' message. You're right that there's something wrong with this test because it's going to break if you change the implementation of the controllers 'create' action, which defeats the purpose of testing. Tests should be flexible to change and not a hinderance.
What you want to do is not test implementation, but side effects. What is the controller 'create' action supposed to do? It's supposed to create a user. Here's how I would test it
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect { post :create, name: 'abc' }.to change(User, :count).by(1)
end
end
end
As for mocking and stubbing, I try to stay away from too much stubbing. I think it's super useful when you're trying to test conditionals. Here's an example:
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
flash[:success] = 'User created'
redirect_to root_path
else
flash[:error] = 'Something went wrong'
render 'new'
end
end
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it "renders new if didn't save" do
User.any_instance.stub(:save).and_return(false)
post :create, name: 'abc'
expect(response).to render_template('new')
end
end
end
Here I'm stubbing out 'save' and returning 'false' so I can test what's supposed to happen if the user fails to save.
Also, the other answers were correct in saying that you want to stub out external services so you don't call on their API every time you're running your test suite.
I'm trying to get RSpec working for a simple scaffolded app, starting with the rspec scaffold tests.
Per the devise wiki, I have added the various devise config entries, a factory for a user and an admin, and the first things I do in my spec controller is login_admin.
Weirdest thing, though... all my specs fail UNLESS I add the following statement right after the it ... do line:
dummy=subject.current_user.inspect
(With the line, as shown below, the specs pass. Without that line, all tests fail with the assigns being nil instead of the expected value. I only happened to discover that when I was putting some puts statements to see if the current_user was being set correctly.)
So what it acts like is that dummy statement somehow 'forces' the current_user to be loaded or refreshed or recognized.
Can anyone explain what's going on, and what I should be doing differently so I don't need the dummy statement?
#specs/controllers/brokers_controller_spec.rb
describe BrokersController do
login_admin
def valid_attributes
{:name => "Bill", :email => "rspec_broker#example.com", :company => "Example Inc", :community_id => 1}
end
def valid_session
{}
end
describe "GET index" do
it "assigns all brokers as #brokers" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :index, {}, valid_session
assigns(:brokers).should eq([broker])
end
end
describe "GET show" do
it "assigns the requested broker as #broker" do
dummy=subject.current_user.inspect # ALL SPECS FAIL WITHOUT THIS LINE!
broker = Broker.create! valid_attributes
get :show, {:id => broker.to_param}, valid_session
assigns(:broker).should eq(broker)
end
end
and per the devise wiki here is how I login a :user or :admin
#spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in Factory.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = Factory.create(:user)
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in user
end
end
end
What a struggle! Thank you Robin, I've been googling on this for hours and finally saw your post; now my controller tests are working :)
To add to your answer, I figured out how to get the devise session into the valid_session hash, which allows the controller tests to run properly as generated by rails.
def valid_session
{"warden.user.user.key" => session["warden.user.user.key"]}
end
In your tests, there is the following code:
def valid_session
{}
end
...
get :index, {}, valid_session
Because of this 'session' variable, the "log_in" that you did is essentially not being used during the 'get'.
The way that I solved it was to remove all of the "valid_session" arguments to the get, post, put, delete calls in that controller's spec. The example above becomes:
get :index, {}
I suspect that there's a way to add the devise's session to the "valid_session" hash, but I don't know what it is.
Thanks for this solution.
If you are using a different Devise model, the session id also changes.
For a model Administrator use the following:
def valid_session
{'warden.user.administrator.key' => session['warden.user.administrator.key']}
end
I would like write RSpec for my controller using RR.
I wrote following code:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe RegistrationController do
it "should work" do
#deploy and approve are member functions
stub.instance_of(Registration).approve { true }
stub.instance_of(Registration).deploy { true }
post :register
end
end
However RR stubs only deploy method when still calls original approve method.
What syntax should I use to stub both method calls for all instances of Registration class?
UPDATE:
I achivied desired result with [Mocha]
Registration.any_instance.stubs(:deploy).returns(true)
Registration.any_instance.stubs(:approve).returns(true)
It would appear the behavior you describe is actually a bug:
http://github.com/btakita/rr/issues#issue/17
as far as I know, the RSpec mocks don't allow you to do that. Are you sure, that you need to stub all instances? I usually follow this pattern:
describe RegistrationController do
before(:each) do
#registration = mock_model(Registration, :approve => true, :deploy => true)
Registration.stub!(:find => #registration)
# now each call to Registration.find will return my mocked object
end
it "should work" do
post :register
reponse.should be_success
end
it "should call approve" do
#registration.should_receive(:approve).once.and_return(true)
post :register
end
# etc
end
By stubbing the find method of the Registration class you control, what object gets returned in the spec.