Passing variable between multiple contexts with Rspec - ruby-on-rails

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

Related

Rspec ignore allow block?

I want to test a rails job which call an endpoint of hubspot API (GET /crm/v3/owners/{ownerId}) and update a record with infos of the request result.
The problem is that I use this gem as an API wrapper and my before block seems like ignored because the result shows that the API call can't give me an owner object with this owner_id (the one given as parameter of attributes is obviously fake). A before block is supposed to override the "normal" response of the controller isn't it ?
I really don't know what I am doing wrong ..
For more context:
My job code
module Hubspots
module Contracts
class UpdateJob < BaseJob
queue_as :high_priority
def perform(attributes)
contract = Contract.find_by(hubspot_sales_deal_id: attributes[:hubspot_sales_deal_id])
return if contract.nil?
deal_owner = client.crm.owners.owners_api.get_by_id(owner_id: attributes[:hubspot_tailor_deal_owner],
id_property: 'id', archived: false)
attributes[:hubspot_tailor_deal_owner] = get_owner_name(deal_owner)
contract.update!(attributes)
end
private
def get_owner_name(hubspot_owner_object)
"#{hubspot_owner_object.last_name.upcase} #{hubspot_owner_object.first_name.capitalize}"
end
end
end
end
My test code
RSpec.describe Hubspots::Contracts::UpdateJob, type: :job do
let!(:job) { described_class.new }
let(:perform) { job.perform(attributes) }
let!(:contract) { create(:contract, hubspot_sales_deal_id: 123) }
let!(:attributes) do
{ hubspot_tailor_deal_id: 456, hubspot_tailor_deal_owner: 876, hubspot_sales_deal_id: 123 }
end
let!(:deal_owner_api) { Hubspot::Client.new(access_token: ENV['HUBSPOT_ACCESS_TOKEN']).crm.owners.owners_api }
let!(:deal_owner_properties) { { last_name: 'Doe', first_name: 'John' } }
before do
allow(deal_owner_api).to receive(:get_by_id).and_return(deal_owner_properties)
end
describe '#perform' do
it 'updates contract' do
expect { perform }.to change { contract.reload.hubspot_tailor_deal_owner }.from(nil)
.to('DOE John')
end
end
end
Test result
I try to rtfm on google but I didn't find the solution yet (I'm always bad for rtfm btw)
Ok my CTO finaly gave me the solution by using the Webmock gem
The code snippet :
before do
stub_request(:get, 'https://api.hubapi.com/crm/v3/owners/876?archived=false&idProperty=id')
.with(headers: { 'Authorization' => "Bearer #{ENV['HUBSPOT_ACCESS_TOKEN']}" }).to_return(status: 200, body: {
firstName: 'John',
lastName: 'Doe'
}.to_json, headers: {})
end
describe '#perform' do
it 'updates contract' do
expect { perform }.to change { contract.reload.hubspot_tailor_deal_owner }.from(nil)
.to('DOE John')
end
end

Rails RSpec (beginner): Why is this test sometimes passing and sometimes not?

I have a book database where books can have different book formats (hardcover, softcover etc).
I have factories with factory_bot.
The following spec just run through with an error - and then when I run it the second time, it worked. I have no idea where I need to start searching....
The error was:
1) BookFormat displays the proper book format for a book with that format
Failure/Error: expect(#book.book_format.name).to eq('Hardcover')
expected: "Hardcover"
got: "Not defined"
Here is the full spec:
require 'rails_helper'
RSpec.describe BookFormat, type: :model do
before(:all) do
#book = create(:hobbit)
#book_format_default = create(:not_defined)
end
it 'displays the proper book format for a book with that format' do
expect(#book.book_format.name).to eq('Hardcover')
end
it 'should reassign to the fallback book_format if their book_format is deleted' do
format = #book.book_format
format.destroy
expect(#book.reload.book_format.id).to eq(#book_format_default.id)
end
it 'should not let the fallback format be deleted' do
format = #book_format_default
format.destroy
expect(format).to be_truthy
end
end
Here is the corresponding factor for the book :hobbit:
factory :hobbit, class: Book do
title { 'The Hobbit' }
year { 1937 }
rating { 5 }
condition { 4 }
synopsis { "<p>#{Faker::Lorem.paragraphs(number: 30).join(' ')}</p>" }
association :book_format, factory: :hardcover
association :user, factory: :me
genres { [ create(:fiction) ] }
after(:build) do |hobbit|
hobbit.cover.attach(
# rubocop:disable Rails/FilePath
io: File.open(Rails.root.join('db', 'sample', 'images', 'cover-1.jpg')),
# rubocop:enable Rails/FilePath
filename: 'cover.jpg',
content_type: 'image/jpeg'
)
end
end
And here are the factories for book_formats:
FactoryBot.define do
factory :not_defined, class: BookFormat do
name { 'Not defined'}
fallback { true }
end
factory :hardcover, class: BookFormat do
name { 'Hardcover' }
end
factory :softcover, class: BookFormat do
name { 'Softcover' }
end
end

How to skip before_create callback in Rspec

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.

rspec + shoulda: setting up data

I have the following test. There are three it blocks. The first one doesn't use shoulda unlike the other two.
If I don't use the before block with post :create, product: attrs then the first test fails as expected. But If I put the before block there then the first test fails, but the other two pass. I have a uniqueness validation on product name, but that shouldn't be the problem as I'm using sequence with factory.
What should I do? How should I generally setup the data for testing when there are rspec and shoulda matchers present at the same time?
describe "when user logged in" do
before(:each) do
login_user #logged in user is available by calling #user
end
context "POST create" do
context "with valid attributes" do
let!(:profile) { create(:profile, user: #user) }
let!(:industry) { create(:industry) }
let!(:attrs) { attributes_for(:product, user_id: #user.id, industry_ids: [ industry.id ]).merge(
product_features_attributes: [attributes_for(:product_feature)],
product_competitions_attributes: [attributes_for(:product_competition)],
product_usecases_attributes: [attributes_for(:product_usecase)]
) }
it "saves the new product in the db" do
expect{ post :create, product: attrs }.to change{ Product.count }.by(1)
end
#If I don't use this the 2 tests below fail. If I use it, then the test above fails.
# before do
# post :create, product: attrs
# end
it { is_expected.to redirect_to product_path(Product.last) }
it { is_expected.to set_flash.to('Product got created!') }
end
end
end
factories
factory :product, class: Product do
#name { Faker::Commerce.product_name }
sequence(:name) { |n| "ABC_#{n}" }
company { Faker::Company.name }
website { 'https://example.com' }
oneliner { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
user
end
You can't have it both ways. If you execute the method you are testing in the before, then you can't execute it again to see if it changes the Product count. If you don't execute it in your before, then you must execute it in your example and therefore can't use the is_expected one liner format.
There are a variety of alternatives. Here is one that incorporates the execution of the method into all the examples.
describe "when user logged in" do
before(:each) do
login_user #logged in user is available by calling #user
end
describe "POST create" do
subject(:create) { post :create, product: attrs }
context "with valid attributes" do
let!(:profile) { create(:profile, user: #user) }
let!(:industry) { create(:industry) }
let!(:attrs) { attributes_for(:product, user_id: #user.id, industry_ids: [ industry.id ]).merge(
product_features_attributes: [attributes_for(:product_feature)],
product_competitions_attributes: [attributes_for(:product_competition)],
product_usecases_attributes: [attributes_for(:product_usecase)]
) }
it "saves the new product in the db" do
expect{ create }.to change{ Product.count }.by(1)
end
it("redirects") { expect(create).to redirect_to product_path(Product.last) }
it("flashes") { expect(create).to set_flash.to('Product got created!') }
end
end
end

How to test Rspec in controller

In controller,
def admin_search
#admins = User.find(:all,:joins=>[:roles],:conditions=>["name IN (?) and email like '#{params[:email]}%'",["content team","ops team"]]).paginate(:page => params[:page], :per_page => 10)
end
please suggest me some code in rspec
First of all, it's better to extract find(:all, ...) call to User model. Call it search, for instance.
class User < ActiveRecord::Base
scope :search_by_email, lambda { |email|
joins(:roles).where(["name IN (?) and email like '#{email}%'",["content team","ops team"]])
}
end
Use it in the controller then:
def admin_search
#admins = User.search_by_email(params[:email]).paginate(:page => params[:page], :per_page => 10)
end
Now, you can test the search_by_email method in isolation - check, that it returns result for "content team" and "ops team" only, correctly works with empty email string and so on.
I don't think you have to test paginate method, as it should be already tested in kaminari, will_paginate or whatever you use. But if you want to be sure, that it is being called, than you can use mock expectations (should_receive) in the controller specs.
EDIT: How the specs could look like
describe User do
describe ".search_by_email" do
let(:content_team) { Role.create! name: "content team" }
let(:ops_team) { Role.create! name: "ops team" }
let(:another_team) { Role.create! name: "another team" }
it "should search in content team" do
content_team_user = User.create! email: "joe.black#example.com", roles: [content_team]
User.search_by_email("black").should == [content_team_user]
end
it "should search in ops team" do
ops_team_user = User.create! email: "joe.black#example.com", roles: [ops_team]
User.search_by_email("black").should == [ops_team_user]
end
it "should not search in other teams" do
other_team_user = User.create! email: "joe.black#example.com", roles: [another_team]
User.search_by_email("black").should == []
end
it "should not search by empty string" do
content_team_user = User.create! email: "joe.black#example.com", roles: [content_team_user]
User.search_by_email("").should == []
User.search_by_email(nil).should == []
end
# more specs for search...
end
end
describe UsersController do
describe "admin search" do
let(:admin_user) { double(:admin_user).as_null_object }
let(:search_string) { 'joe' }
it "should search for admin users" do
User.should_receive(:search_by_email).with(search_string).and_return([admin_user])
get :admin_search, email: search_string
assigns(:admins).should == [admin_user]
end
end
end

Resources