The block #update doesn't run. Why? How to change it to run all of them. #anything works fine.
describe UsersController, type: :controller do
login_admin
describe '#update' do
def user_update_params(roles:)
{
role_ids: roles.map(&:id),
name: 'new'
}
end
shared_examples_for 'update user' do
it 'change the user' do
expect do
put :update, id: user.id, user: user_params
end.to change { user.reload.name }
end
end
end
describe '#anything' do
it 'is ok' do
#runs ok
end
end
end
It's a shared example, not a real test. It is supposed to be included in other test groups. Like this:
describe '#whatever' do
it_behaves_like 'update user'
it 'runs shared example' do
end
end
Related
I'm studying rails and rspec.
And I made rspec unit test (request test) on rails application.
But after searching on google, I'm wonder if my job is on right way.
Can my code be a "Unit test by function(not a method, web site's feature ex)create, show, delete..) of rails application" ?
this is my code with request test.
require 'rails_helper'
RSpec.describe 'Users', type: :request do
let!(:users) { create_list(:user, 10) }
let(:user_id) { users.first.id }
let(:user) { create(:user) }
def send_request_to_store_user(name, mailaddress)
post '/users', params: {
user: {
name: users.first.name,
mailaddress: users.first.mailaddress
}
}
end
def http_status_success_and_body_element_check(body_element)
expect(response).to have_http_status(:success)
expect(response.body).to include(body_element)
end
describe 'GET' do
context 'Get /users test' do
it 'test user list page' do
get '/users'
http_status_success_and_body_element_check('User List')
end
end
context 'Get /users/create test' do
it 'test user create page' do
get '/users/create'
http_status_success_and_body_element_check('create user')
end
end
context 'Get /users/:id/edit' do
it 'test user edit page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('edit user')
end
end
context 'Get /users/:id' do
it 'test user show page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('show user')
end
end
end
describe 'POST' do
context 'test store new user' do
it 'test create new user' do
send_request_to_store_user(user.name, user.mailaddress)
expect do
create(:user)
end.to change { User.count }.from(User.count).to(User.count + 1)
end
it 'test redirect after create' do
send_request_to_store_user(user.name, user.mailaddress)
expect(response).to have_http_status(302)
end
end
end
describe 'DELETE' do
it 'test delete user' do
expect do
delete "/users/#{user_id}"
end.to change { User.count }.from(User.count).to(User.count - 1)
expect(response).to have_http_status(302)
end
end
describe 'PUT' do
context 'user update' do
it 'test user information update' do
old_name = users.first.name
new_name = 'new_name'
expect do
put "/users/#{user_id}", params: {
user: {
name: new_name
}
}
end.to change { users.first.reload.name }.from(old_name).to(new_name)
expect(response).to have_http_status(:redirect)
end
end
end
end
this is my code with test on model
require 'rails_helper'
RSpec.describe User, type: :model do
it 'user must have name and mailaddress' do
user = create(:user)
expect(user).to be_valid
expect(user.name).not_to be_nil
expect(user.mailaddress).not_to be_nil
end
it 'mailaddress must include #' do
# user = FactoryBot.create(:user)
# If rails_helper.rb has config.include FactoryBot::Syntax::Methods,
# Can use shortcut. Don't have to FactoryBot.create
user = create(:user)
# Test pass if email match with regexp
expect(user.mailaddress).to match(/\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/)
end
end
I don't think these tests are valuable (meaningful).
Here's my reasoning:
What are these tests telling you? That the Rails router is working? That the controller is responding with the right action? Neither of these are your responsibility to test. Rails has that covered.
If you want to know "does the index page render?" and "can I CRUD a user?" then write system tests with Capybara that simulate the whole flow. That way you are testing the real-world interaction with your whole system.
I've got custom member_action in my Active Admin panel which is responsible for resending devise reset password instructions.
admin/users.rb
ActiveAdmin.register User do
member_action :reset_password do
user = User.find(params[:id])
user.send_reset_password_instructions
redirect_to(admin_user_path(user),
notice: "Password reset email sent to #{user.email}")
end
end
How to write RSpec tests for such an action? The only thing I found is this one and I think it's not quite related to my problem.
I was trying to sth like below:
require 'rails_helper'
describe Admin::UsersController, type: :controller do
include Devise::TestHelpers
let!(:admin) { create(:admin_user) }
before(:each) do
sign_in admin
end
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before(:each) do
User.should_receive(:find).at_least(:once).and_return(user)
get :show
end
it 'sends email' do
get :reset_password
expect(user).should_receive(:send_reset_password_instructions)
end
end
end
But I'm getting an error:
ActionController::UrlGenerationError:
No route matches {:action=>"reset_password", :controller=>"admin/users"}
Personally I prefer to use a feature test, since when using active admin, UI stuff handle by the framework:
RSpec.feature 'Reset Password', type: :feature do
let(:user) { create :user }
before do
login_as(user, scope: :user)
end
scenario 'can delete future episode' do
visit some_path
click_link 'Reset Password'
expect(page.current_path).to eq(admin_user_path(user))
expect(page).to have_content("Password reset email sent to #{user.email}")
end
end
Ok, it turns out small adjustments (pass the user.id in params) make the trick.
describe Admin::UsersController, type: :controller do
include Devise::Test::ControllerHelpers
before { sign_in admin }
let!(:admin) { create(:admin_user) }
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before do
allow(User).to receive(:find).at_least(:once) { user }
get :show, params: { id: user.id }
end
it 'sends email' do
get :reset_password, params: { id: user.id }
expect(flash[:notice]).to match("Password reset email sent to #{user.email}")
end
end
end
I am trying to test some PUT/PATCH endpoint from my API, but my 'record_to_update' is not chaging as expect.
I organize the spec as follow using Rails 5.2 and RSpec 3.8:
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
context 'when request with no valid headers' do
...
...
...
end
context 'when resquest with valid headers' do
before do
request.accept = 'application/vnd.api+json'
request.content_type = 'application/vnd.api+json'
2.times do
create(:user)
end
end
describe 'PATCH/PUT /users/:id' do
let(:record_to_update) { create(:user) }
let(:user_params) do
{ id: first_user.id, name: 'goku', email: 'goku#bol.com' }
end
before do
put :update, params: { id: record_to_update, user: user_params }
record_to_update.reload
end
it 'should update user' do
expect(record_to_update.name).to eq('goku')
expect(response.status).to eq 200
end
end
end
end
As I said the problem here that I am facing is that 'record_to_update' is not chaging. I already test the same PUT/PATCH endpoint in the same API with Postman and successfully updated from there. What am I doing wrong here?
Thanks in advance.
you should move the before block to it 'should update user'
before do
put :update, params: { id: record_to_update.id, user: user_params }
end
So it becoming
it 'update user' do
put :update, params: { id: record_to_update, user: user_params }
expect ...
record.reload!
....
end
I have a problem with rspec stubbing. I'm following this doc https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/any-instance
describe Api::V1::ActionsController, type: :controller do
let(:admin) { create :admin }
subject { response }
describe 'GET #index' do
before :each do
get :index
end
context 'admin' do
before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
it 'expects 200' do
expect(response.status).to eq 200
end
end
end
This test fails. And the interesting thing is that if I put these stubs to spec_helper.rb like
config.before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
it works fine. Any ideas?
I guess the problem is that this piece of code:
before :each do
get :index
end
runs before the stubs:
before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
before blocks are run from outside-in, and the block with the stubs is nested deeper. Therefore, by the time you stub the methods, get :index has already been executed.
Try this instead:
describe 'GET #index' do
subject do # define what `subject` will do, but don't actually run it just yet
get :index
end
context 'admin' do
before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
it 'returns 200' do
expect(subject).to be_success
# ^^^ now it's only here that the controller action is executed
end
end
end
I'm trying to add a function to allow for quick testing of redirects for unauthenticated users. Here's what I have so far:
def unauthenticated_redirects_to redirect_path #yeild
context "when not signed in" do
it "redirects to #{redirect_path}" do
yield
expect(response).to redirect_to redirect_path
end
end
end
describe SomeController do
describe 'GET #show' do
unauthenticated_redirects_to('/some_path') { get :show }
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #whatever' do
unauthenticated_redirects_to('/some_other_path') { get :whatever }
end
end
This doesn't work, however, since the scope and context of the primary describe block is not available to the block passed to unauthenticated_redirects_to. This reasonably leads to the error: undefined method `get' for RSpec::Core::ExampleGroup::Nested_1::Nested_2:Class.
Is there a way around this or is there a cleaner way to accomplish something similar which I should consider?
Here's an approach using shared examples which triggers the example based on shared metadata (:auth => true in this case) and which parses the example group description to pick up some key parameters.
require 'spec_helper'
class SomeController < ApplicationController
end
describe SomeController, type: :controller do
shared_examples_for :auth => true do
it "redirects when not signed in" do
metadata = example.metadata
description = metadata[:example_group][:description_args][0]
redirect_path = metadata[:failure_redirect]
http_verb = description.split[0].downcase.to_s
controller_method = description.match(/#(.*)$/)[1]
send(http_verb, controller_method)
expect(response).to redirect_to redirect_path
end
end
describe 'GET #show', :auth => true, :failure_redirect => '/some_path' do
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #whatever', :auth => true, :failure_redirect => '/some_other_path' do
end
end
For completeness, here's another shared examples approach, this time using a block parameter with a before call which avoids the original scope problem:
require 'spec_helper'
class SomeController < ApplicationController
end
describe SomeController, type: :controller do
shared_examples_for 'auth ops' do
it "redirects when not signed in" do
expect(response).to redirect_to redirect_path
end
end
describe 'GET #show' do
it_behaves_like 'auth ops' do
let(:redirect_path) {'/some_path'}
before {get :show}
end
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #new' do
it_behaves_like 'auth ops' do
let(:redirect_path) {'/some_other_path'}
before {get :whatever}
end
end
end
Have a look at rspec shared example.
Using shared_examples_for seemed like overkill given that I was only concerned with a single example. Furthermore, it_behaves_like("unauthenticated redirects to", '/some_other_path', Proc.new{ get :whatever}) seems unnecessarily verbose. The trick is to use #send() to maintain the proper scope.
def unauthenticated_redirects_to path, method_action
context "when not signed in" do
it "redirects to #{path} for #{method_action}" do
send(method_action.first[0], method_action.first[1])
expect(response).to redirect_to path
end
end
end
describe 'GET #new' do
unauthenticated_redirects_to '/path', :get => :new
end