Hi i am using a Rails application with ruby-2.2.5 and rails 5 i am using rspec to test my controller. there is a callback in User model before_create :create_token i want to skip this callback in rspec.
spec/controllers/check_token_controller_spec.rb
# frozen_string_literal: true
require 'rails_helper'
describe CheckTokenController do
before do
#user = User.create!(fullname: 'saddam husain',
email: 'saddam#gmail.com',
password: 'pass',
password_confirmation: 'pass',token: 'xyz')
end
describe 'POST create' do
subject { post :create, params: params }
context 'when credentials are valid' do
let(:params) do
{ data: { attributes: { username: 'saddam#gmail.com', token: 'xyz' } } }
end
it { is_expected.to have_http_status(200) }
end
context 'when credentials are invalid' do
let(:params) do
{ data: { attributes: { username: 'saddam#gmail.com', token: '' } } }
end
it { is_expected.to have_http_status(401) }
end
end
end
i want to skip create_token callback. please help me how to skip.
A quick solution is using the RSpec#allow_any_instance_of method to stub the create_token for all classes.
before do
allow_any_instance_of(User).to receive(:create_token)
#user = User.create!(fullname: 'saddam husain', email: 'saddam#gmail.com', ...)
end
This isn't recommended (reference; please read the full section), and you probably need to skip a third-party code, or service integration inside create_token method. So you should mock this service instead of the create_token method. Here is a sample on how to do this: https://stackoverflow.com/a/19281316/1042324.
Let us know the content of your create_token method so we can help you better.
Related
In my Rails/Grape app I created a webhook controller which receive JSON from CMS webhook. I'm just wondering how to test it in RSpec if I don't have any params (I guess I don't need it because I only receive JSON from webhook).
My webhook controller (it works well):
module Cms
class Webhook < Base
desc 'Take the CMS webhook'
http_basic do |user, password|
user == ENV['USER'] && password == ENV['PASSWORD']
end
post :receive do
params
end
end
end
I was trying to like:
describe Cms::Webhooks, type: :request do
subject(:call) { post endpoint, params: params, as: :json }
let(:endpoint) { '/api/cms/webhooks/receive' }
let(:params) do
{
some: 'some pass'
}
end
it 'returns a successful response' do
call
expect(response).to be_successful
end
end
I'm getting an error:
Failure/Error: expect(response).to be_successful
expected `#<ActionDispatch::TestResponse:0x00007f9058e43e60 #mon_data=#<Monitor:0x00007f9058e43de8>, #mon_data_..., #method=nil, #request_method=nil, #remote_ip=nil, #original_fullpath=nil, #fullpath=nil, #ip=nil>>.successful?` to return true, got false
Can you try this code?
describe Cms::Webhooks, type: :request do
subject(:call) { post endpoint, params: params, as: :son, headers: headers }
let(:endpoint) { '/api/cms/webhooks/receive' }
let(:params) do
{
some: 'some pass'
}
end
let(:headers) do
{
'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('your_username', 'your_password')
}
end
it 'returns a successful response' do
call
expect(response).to be_successful
end
end
Ref https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic.html
My project is a Rails 5.2 app, running Ruby 2.6, and uses the shopify_gem and factory_bot_rails.
I have a controller that inherits from ShopifyController. My unit tests for controllers are stuck at a 302. I'm unable to figure out how to get past authentication...
I've tried these tutorials and other links, but no luck:
http://www.codeshopify.com/blog_posts/testing-shopify-authenticated-controllers-with-rspec-rails
https://community.shopify.com/c/Shopify-APIs-SDKs/Testing-a-Rails-app-created-through-shopify-app-gem/td-p/337337
https://github.com/Shopify/shopify_app/issues/445
https://github.com/Shopify/shopify_app/issues/731
My controller test is below
require 'rails_helper'
describe OnboardingController, type: :controller do
before do
shop = FactoryBot.create(:shop)
request.env['rack.url_scheme'] = 'https'
#request.session[:shopify] = shop.id
#request.session[:shopify_domain] = shop.shopify_domain
end
it 'onboards correctly', :focus do
get :onboard_completed
expect(response).to have_http_status(:success)
end
end
I was also playing with this code, but it failed (errors in comments):
module ShopifyHelper
def login(shop)
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:shopify,
provider: 'shopify',
uid: shop.shopify_domain,
credentials: { token: shop.shopify_token })
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:shopify]
get "/auth/shopify" # this leads to a UrlGenerationError
follow_redirect! # this is an undefined method. Seems to be a MiniTest thing
end
end
require 'rails_helper'
RSpec.describe "Home", type: :request do
def login(shop)
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:shopify,
provider: 'shopify',
uid: shop.shopify_domain,
credentials: { token: shop.shopify_token })
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:shopify]
get "/auth/shopify"
follow_redirect!
#request.session[:shopify] = shop.id
#request.session[:shopify_domain] = shop.shopify_domain
end
describe "GET /" do
it "works!" do
shop = Shop.first || create(:shop)
login(shop)
get root_path
shop.with_shopify!
expect(assigns(:products)).to eq ShopifyAPI::Product.find(:all, params: { limit: 10 })
expect(response).to render_template(:index)
expect(response).to have_http_status(200)
end
end
end
Something like this works for me, your getting the errors in your function probably because you do not have get and follow_redirect! functions defined in your ShopifyHelper module context.
Reference: http://www.codeshopify.com/blog_posts/testing-shopify-authenticated-controllers-with-rspec-rails
This ended up being the working solution
require 'rails_helper'
describe WizardController, type: :controller do
before do
shop = FactoryBot.create(:shop)
request.env['rack.url_scheme'] = 'https'
allow(shop).to receive(:wizard_completed?).and_return(false)
allow(Shop).to receive(:current_shop).and_return(shop)
# #note: my original code had "session[:shopify]" of "session[:shop]", which was the error
session[:shop_id] = shop.id
session[:shopify_domain] = shop.shopify_domain
end
it 'enter test here', :focus do
get :wizard
expect(response).to have_http_status(:success)
end
end
This worked for me:
# File: spec/support/request_helper.rb
def shopify_login(shop)
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:shopify, provider: 'shopify', uid: shop.myshopify_domain,
credentials: { token: shop.api_token })
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:shopify]
get "/auth/shopify/callback?shop=#{shop.myshopify_domain}"
follow_redirect!
#request.session[:shopify] = shop.shopify_id
#request.session[:shop_id] = shop.id
#request.session[:shopify_domain] = shop.myshopify_domain
end
Btw, testing controllers are deprecated in favour of requests.
RSpec.describe 'ShopsController', type: :request do
let(:shop) { FactoryBot.build :shop }
let(:plan) { FactoryBot.build :enterprise_plan }
let(:subscription) { FactoryBot.create :subscription, shop: shop, plan: plan }
describe 'GET#product_search' do
it 'returns a successful 200 response for listing action do' do
VCR.use_cassette('shop-search-product', record: :new_episodes) do
new_subscrip = subscription
shopify_login(new_subscrip.shop)
get product_search_path, { params: { query: 'bike' } }
json = JSON.parse(response.body)
expect(response).to be_successful
expect(json.length).to eq(7)
end
end
end
Remember to setup "admin { true }" in your shop's FactoryBot if you are using the 'shopify_app' gem.
I wrote this code for testing controller update function.
Wrote a method for eliminating duplicate code.
Is this an explicit way to do it?
users_controller_spec.rb
context 'Update failed' do
def render_edit
user.reload
expect(response.status).to eq(200)
end
it 'Name is nil' do
put :update, params: { id: user.id, user: { name: '' } }
render_edit
end
it 'Email is exist' do
create(:user, email: 'user#gmail.com')
put :update, params: { id: user.id, user: { email: 'user#gmail.com' } }
render_edit
end
it 'Email is nil' do
put :update, params: { id: user.id, user: { email: '' } }
render_edit
end
it 'Password must be at least 8 characters' do
put :update, params: { id: user.id, user: { password: '1234567', password_confirmation: '1234567' } }
render_edit
end
it 'Passwords do not match' do
put :update, params: { id: user.id, user: { password: '1234567890', password_confirmation: '123456789' } }
render_edit
end
end
I was thinking to use after(:each). But it looks a little wired in logic.
Or use loop to replace params.
Any suggestion?
You can use shared examples as suggested in the comments, but there's an easier way.
context 'Update failed' do
before do
put :update, params: params
user.reload # I'm not sure why you need this
end
subject { response }
context 'Name is nil' do
let(:params} { {id: user.id, user: { name: '' }} }
it { is_expected.to be_success }
end
context 'Email exists' do
let(:params) { { id: user.id, user: { email: 'user#gmail.com' } }
let(:user) { create(:user, email: 'user#gmail.com') }
it { is_expected.to be_success }
end
# and so on
end
The main rune I use is - make it obvious what change in each context. So instead of redefining put ..., extract it as a let and define it per context.
be_success is part of rspec magic, wherever you use be_something matcher it'll try to use something? method and check if it's true, i.e.
expect(foo).to be_empty? == expect(foo.empty?).to eq(true)
If you don't want it make it like this
subject { response.status }
# and later
is_expected.to eq 200
is_expected.to is just a shorthand for expect(subject).to
Hi i am working on rails application with ruby 2.5.0 and rails 5. I have written an api to check user exist with provided username and token.
check_token_controller.rb
class CheckTokenController < ApplicationController
def create
begin
user = User.where(email: check_params[:username], token: check_params[:token]).first
if user.blank?
render json: {},
status: 401
else
render json: {},
status: 200
end
rescue => e
render json: {},
status: 500
end
end
private
def check_params
permitted = %i[username token]
params.require(:data)
.require(:attributes)
.permit(permitted)
.transform_keys(&:underscore)
end
end
now i want to test this api in my spec.rb file.
*spec/controllers/check_token_controller_spec.rb
require 'rails_helper'
describe CheckTokenController do
let(:user) { instance_double('user') }
let(:save_result) { true }
let(:params) do
{ data: { attributes: { fullname: 'michael febrianto',email: 'saddam#gmail.com', token: 'rWCyRUgfLODuc8B4DvA_8w',password: 'password' } } }
end
before do
allow(User).to receive(:new).and_return(user)
allow(user).to receive(:save).and_return(save_result)
end
let(:params) do
{ data: { attributes: { username: 'saddam#gmail.com', token: 'rWCyRUgfLODuc8B4DvA_8w' } } }
end
describe 'POST create' do
subject { post :create, params: params }
context 'when success' do
it { is_expected.to have_http_status(200) }
end
context 'when failed' do
it { is_expected.to have_http_status(401) }
end
end
end
i am first time working with rspec now whenever i run this test it doesnot create any test data i checked with debugger. please help me how can i create a test data and then i can test my api. Thanks in advance.
Please go through the books:
Everyday Rails Testing with RSpec
The RSpec Book: Behaviour-Driven Development
They might help you.
I'm writing some tests using FactoryGirl and Rspec.
spec/factories/students.rb:
FactoryGirl.define do
factory :student do
end
factory :student_with_profile_and_identity, class: 'Student' do
after(:create) do |student|
create(:profile, profileable: student)
create(:student_identity, student: student)
end
end
end
spec/factories/profiles.rb:
FactoryGirl.define do
factory :profile do
birthday { Faker::Date.birthday(15, 150) }
sequence(:email) { |i| "profile_#{i}#email.com" }
first_name { Faker::Name.first_name }
last_name { Faker::Name.first_name }
password { Faker::Internet.password(6, 72, true, true) }
end
end
spec/factories/student_identities.rb:
FactoryGirl.define do
factory :student_identity do
provider { ['facebook.com', 'google.com', 'twitter.com'].sample }
uid { Faker::Number.number(10) }
end
end
spec/requests/authorizations_spec.rb:
require 'rails_helper'
RSpec.describe 'Authorizations', type: :request do
describe 'POST /v1/authorizations/sign_in' do
let!(:student) { create(:student_with_profile_and_identity) }
context 'when the request is valid' do
subject do
post '/v1/authorizations/sign_in',
params: credentials
end
context "user signs up via social network" do
let(:credentials) do
{
authorization: {
student: {
profile_attributes: {
email: student.profile.email
},
student_identities_attributes: {
provider: student.student_identities[0].provider,
uid: student.student_identities[0].uid
}
}
}
}
end
it 'returns an authentication token' do
subject
p "1 student.profile.inspect #{student.profile.inspect}"
expect(json['token']).to(be_present)
end
end
context 'when the user has already an account' do
let(:credentials) do
{
authorization: {
email: student.profile.email,
password: student.profile.password
}
}
end
it 'returns an authentication token' do
p "2 student.profile.inspect #{student.profile.inspect}"
subject
expect(json['token']).to(be_present)
end
end
end
end
end
Almost all tests are passing... the problem is that:
It's creating a new student in every context. I'd expect the let!(:student) { ... } to be something like "singleton", in other words, once it's created/defined here let!(:student) { create(:student_with_profile_and_identity) } it won't be called anymore.
Ex: the logs are like this:
"1 student.profile.inspect #<Profile id: 1, email: \"profile_1#email.com\", profileable_type: \"Student\", profileable_id: 1>"
"2 student.profile.inspect #<Profile id: 2, email: \"profile_2#email.com\", profileable_type: \"Student\", profileable_id: 2>"
While I'd expect the instances to be the same.
Am I missing something?
In RSpec, let and let! are the same thing, except that let is lazy and let! is eager:
Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.
If you want something to persist through all examples, you can use a before hook...before(:context) sounds like it might be what you're wanting. You might be able to setup a helper method that memoizes in a before block, to avoid having to use an instance variable everywhere (per this comment):
def student
#student ||= create(:student_with_profile_and_identity)
end
before(:context) do
student # force student creation
end