RSpec has an anonymous controller which comes in handy to test the "base" controller of other controllers, please see this example:
app/controllers/admin/base_controller.rb
class Admin::BaseController < ApplicationController
before_action :authenticate_user!
before_action :admin_required
layout 'admin'
private
def admin_required
render text: 'Unauthorized', status: :unauthorized unless current_user.admin?
end
end
spec/controllers/admin/base_controller_spec.rb
require 'rails_helper'
RSpec.describe Admin::BaseController, :type => :controller do
controller do
def index
head :ok
end
end
describe '#index' do
def do_request
get :index
end
context "as non-admin" do
before { sign_in create(:user) }
it 'raises error' do
do_request
expect(response).to have_http_status(:unauthorized)
expect(response).not_to be_success
end
end
context "as admin" do
before { sign_in create(:user, :with_admin) }
it 'does not raise error' do
do_request
expect(response).to be_success
end
end
end
end
I use a similar structure for my mailers.
My current implementation would need me to add a test to BaseMailer and add corresponding view for that test method.
Is there any way to achieve sort of anonymous mailer testing? something like:
app/mailers/base_mailer.rb
class BaseMailer < ActionMailer::Base
layout 'mailer'
default from: 'Support <support#example.com>',
reply_to: 'Support <support#example.com>',
end
spec/mailers/base_mailer_spec.rb
require 'rails_helper'
RSpec.describe Admin::BaseController, :type => :mailer do
mailer do # <= Anonymous mailer!
def test
mail
end
end
describe '#welcome' do
let(:email) { email_to }
def email_to
mailer.test # <= Anonymous mailer!
end
it { expect(email).to deliver_from 'Support <support#example.com>' }
it { expect(email).to reply_to 'Support <support#example.com>' }
end
end
Then I can get rid of having a test and app/views/base_mailer/test.html.erb file that I'll never used it but just use for testing.
Thanks!
P.S. This mailer testing syntax is from: https://github.com/bmabey/email-spec
Can be achieved, please see this comment:
RSpec.describe BaseMailer do
mailer = Class.new(BaseMailer) do
def a_sample_email
# We need a body to not render views
mail(body: '')
end
end
it 'has the default "from" address' do
email = mailer.a_sample_email
expect(email.from).to eq 'Support <support#example.com>'
end
end
Source: https://github.com/rspec/rspec-rails/issues/1182
Related
I'm new to rails api rspec and somehow I cannot make the test work. Can someone provide some inputs in the tests? The models and controllers are more like a pseudocode. I appreciate it. Thank you.
# routes.rb
Rails.application.routes.draw do
resources :users, :only [:create]
end
# app/model/user.rb
class User < ApplicationRecord
validates_uniqueness_of :name
end
# app/controllers/users_controller.rb
def create
#user = User.new(user_params)
if #user.save
head 200
else
render json: { error: 'Failed', status: 400}, status: 400
end
end
def user_params
params.require(:user).permit(:name)
end
# RSpec Test
require 'rails_helper'
RSpec.describe UsersController do
describe '#create' do
context 'the parameter "user[name]"" is blank' do
it 'creates new user' do
#Test Here
end
it 'renders empty response' do
#Test Here
end
it 'renders response with status 200' do
#Test Here
end
end
end
end
spec/models/user_spec.rb make sure you install gem shoulda-matchers and factory_bot_rails
require 'rails_helper'
RSpec.describe User, type: :model do
let(:user) { create(:user) }
describe "validation" do
it { should validate_uniqueness_of(:name) }
end
end
spec/controllers/users_controller_spec.rb
require 'rails_helper'
RSpec.describe UserssController, type: :controller do
describe "POST /users" do
it "when create user successfully return status 200" do
post :create, params: { name: "name" }
expect(response.status).to eq 200
end
it "when create user errors return status 400" do
post :create, params: { name: "name duplicate" }
expect(response.status).to eq 400
end
end
end
I hope it helps for you
I am trying to write spec code for my controller it gets failed. And i am not sure where it gets failed.
Controller Code
def index
#users = User.all
end
def update
authorize! :update, #user
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_index_path }
else
format.html { render :index }
end
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.permit(:active)
end
Spec Code for the above controller
RSpec.describe UserController, type: :controller do
describe 'GET #index' do
let(:user) {User.create!(name: "hari")}
context 'with user details'do
it 'loads correct user details' do
get :index
expect(response).to permit(:user)
end
end
context 'without user details' do
it 'doesnot loads correct user details' do
get :index
expect(response).not_to permit(:user)
end
end
end
describe 'Patch #update' do
context 'when valid params' do
let(:attr) do
{active: 'true'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'redirects to user_index_path ' do
expect(response).redirect_to(user_index_path)
end
it 'sets active state' do
expect(#user.active?('true')).to be true
end
end
context 'when invalid param' do
let(:attr) do
{active: 'nil'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'render index' do
expect(respone.status).to eq(200)
end
it 'doesnot change active state' do
expect(#user.active?(nil)).to be true
end
end
end
end
I am just a beginner and tried the spec code for my controller by checking https://relishapp.com/rspec/rspec-rails/docs/gettingstarted. Can you help me where my spec goes wrong or could anyone give me a few test examples for these methods or could redirect me to an rspec guide? the index method is getting failed
and my
terminal log is
1) UserController GET #index with user details loads correct user details
Failure/Error: expect(response).to permit(:user)
NoMethodError:
undefined method `permit' for #<RSpec::ExampleGroups::UserController::GETIndex::WithUserDetails:0x00005614152406b0>
Did you mean? print
# ./spec/controllers/user_controller_spec.rb:10:in `block (4 levels) in <top (required)>'
I am trying to stub out an :authenticate_user method call in my request spec so I can test the user's association creation. I am using these blog posts as a guide on stubbing:
1) https://8thlight.com/blog/mike-knepper/2014/07/01/stubbing-authentication-and-authorization-in-controller-specs.html
2) http://johnnyji.me/rspec/2015/06/18/stubbing-controller-instance-methods-in-rspec.html
I'm not having any success with stubbing and I can't figure out what am I missing.
When I tried
it 'creates a new contract' do
allow(controller).to receive(:authenticate_user).and_return(user)
post api_v1_user_contracts_path(user), { params: contract_params}
expect(response).to have_http_status(200)
end
I got:
When I tried:
it 'creates a new contract' do
allow_any_instance_of(controller).to receive(:authenticate_user).and_return(user)
post api_v1_user_contracts_path(user), { params: contract_params}
expect(response).to have_http_status(200)
end
I got
My code:
spec/requests/contracts_api_spec.rb
require 'rails_helper'
require 'pry'
context "POST #create" do
let (:user) { User.create(full_name: "Jason Bourne", email: "jbourne#test.com", password: "123456") }
let (:contract_params) do
{
"contract[vendor]" => "Lebara",
"contract[starts_on]" => "2018-12-12",
"contract[ends_on]" => "2018-12-16",
"contract[price]" => "15"
}
end
it 'creates a new contract' do
allow(controller).to receive(:authenticate_user).and_return(user)
post api_v1_user_contracts_path(user), { params: contract_params}
expect(response).to have_http_status(200)
end
app/controllers/api/v1/contracts_controller.rb
class Api::V1::ContractsController < ApplicationController
before_action :authenticate_user
def show
if #current_user.contracts.find_by(id: params[:id])
render json: #current_user.contracts.find_by(id: params[:id])
else
render json: { error: "Contract not found"}, status: 400
end
end
def create
contract = #current_user.contracts.build(contract_params)
if contract.save
render json: contract
else
render json: { error: contract.errors }, status: 400
end
end
app/controllers/concerns/token_authenticatable.rb
class NotAuthorizedException < StandardError; end
module TokenAuthenticatable
extend ActiveSupport::Concern
included do
attr_reader :current_user
before_action :authenticate_user
rescue_from NotAuthorizedException, with: -> { render json: { error: 'Not Authorized' }, status: :unauthorized }
end
private
def authenticate_user
#current_user = DecodeAuthenticationCommand.call(request.headers).result
raise NotAuthorizedException unless #current_user
end
end
Additional questions:
1) Should I be using a real User object, or should that be a double? I'm assuming it should be a real user in order to test if the association creation is working.
2) Should I be using allow(Api::V1::ContractsController).to receive(:authenticate_user).and_return(user)? I've tried it before and didn't work but I didn't know it was because something else also was breaking it.
Thanks for any feedback you can give!
The point is that authenticate_user assigns user to the variable (and you use it later). Please try:
allow(DecodeAuthenticationCommand).to receive_message_chain(:call, :result).and_return(user)
With the test double, you will have to define all methods for the user, such as contracts. Also, you are checking if the contract was created - in my opinion, it is perfectly fine to use a real object for the user.
I have a before_action filter and want to test that the index action is only executed if the user is logged in. Simply put, i don't know how to do this. I'm using my own simple authentication and i know i could use CanCan or similar but for my own learning i'm doing it the hard way!
ApplicationController.rb
helper_method :logged_in
helper_method :current_user
def current_user
#current_user ||= User.find_by_id(session[:current_user]) if session[:current_user]
end
def logged_in
unless current_user
redirect_to root_path
end
end
ActivitiesController.rb
before_action :logged_in
def index
#activities = Activity.all.where(user_id: #current_user)
end
Activities_Controller_spec.rb
require 'rails_helper'
RSpec.describe ActivitiesController, :type => :controller do
describe "GET index" do
before(:each) do
#activity = FactoryGirl.create(:activity)
session[:current_user] = #activity.user_id
#current_user = User.find_by_id(session[:current_user]) if session[:current_user]
end
it "shows all activities for signed in user" do
get :index, {user_id: #activity.user_id}
expect(response).to redirect_to user_activities_path
end
end
end
activities.rb(Factory)
FactoryGirl.define do
factory :activity do
association :user
title { Faker::App.name }
activity_begin { Faker::Date.forward(10) }
activity_end { Faker::Date.forward(24) }
end
end
I'm getting the following error:
Failure/Error: expect(response).to redirect_to user_activities_path
Expected response to be a redirect to <http://test.host/users/1/activities> but was a redirect to <http://test.host/>.
Expected "http://test.host/users/1/activities" to be === "http://test.host/".
After long discussion I think tests should be smth like this (it is not tested :) )
require 'rails_helper'
RSpec.describe ActivitiesController, :type => :controller do
describe "GET index" do
before(:each) do
#activity = FactoryGirl.create(:activity)
end
context 'when user is logged' do
before(:each) do
session[:current_user] = #activity.user_id
end
it "shows all activities for signed in user" do
get :index, {user_id: #activity.user_id}
expect(response).to be_success
end
end
context 'when user is anonymous' do
it "redirects user to root path" do
get :index, {user_id: #activity.user_id}
expect(response).to redirect_to root_path
end
end
end
end
I have written this controller code in Ruby on Rails
class PostsController < ApplicationController
before_filter :authenticate_user!
def index
#posts = Post.all(:order => "created_at DESC")
respond_to do |format|
format.html
end
end
def create
#post = Post.create(:message => params[:message])
respond_to do |format|
if #post.save
format.html { redirect_to posts_path }
format.js
else
flash[:notice] = "Message failed to save."
format.html { redirect_to posts_path }
end
end
end
end
and corresponding to this I have written the following test case :-
require 'spec_helper'
describe PostsController do
describe "GET 'index'" do
it "returns http success" do
get 'index'
response.should be_success
end
end
describe "#create" do
it "creates a successful mesaage post" do
#post = Post.create(message: "Message")
#post.should be_an_instance_of Post
end
end
end
I am getting failures on both. Please take a look on the code and help me figure out.
I suspect you are not logged in since you are using Devise?
Maybe you need to include the devise testhelpers:
describe PostsController do
include Devise::TestHelpers
before(:each) do
#user = User.create(...)
sign_in #user
end
#assertions go here
end
As Tigraine states, it appears as though you probably are not logged in (with Devise) when the tests get executed. However, showing the failures would help in narrowing down the problem further.
On top of that, the second test isn't really an integration test and I would probably prefer something like the following to test the same condition. There are two types of test you could do:
# inside 'describe "#create"'
let(:valid_params) { {'post' => {'title' => 'Test Post'} }
it 'creates a new Post' do
expect {
post :create, valid_params
}.to change(Post, :count).by(1)
end
# and / or
it 'assigns a new Post' do
post :create, valid_params
assigns(:post).should be_a(Post)
assigns(:post).should be_persisted
end
Don't forget to add this line into your spec_helper.rb
require "devise/test_helpers"
include Devise::TestHelpers
Nevertheless, here is link for Devise wiki - How to test Controllers where you can find more info about this approach. I recommend writing the before method without (:each), what I remember it sometimes causes problems.
before do
#user = FactoryGirl.create(:user)
sign_in #user
end
Can always use:
puts response.inspect
To see how your response looks like.