Rails RSpec controller test changes second attribute - ruby-on-rails

I have a controller called "CrossLayerParamtersController". If one specified attribute (donor_layer) is updated. I want the attribute (donor_material) to be set to "0".
In the controller update method I'm checking if the donor_layer params are present and if they are the donor_material is set to 0:
Controller file:
cross_layer_parameter_controller.rb
def update
#stack = Stack.find(params[:stack_id])
#cross_layer_parameter = CrossLayerParameter.find(params[:id])
if params[:cross_layer_parameter][:donor_layer]
#cross_layer_parameter.donor_material = 0
end
respond_to do |format|
if #cross_layer_parameter.update(cross_layer_parameter_params)
new_rows = render_to_string('stacks/_cross_layer_parameters.html.erb', layout: false, locals: { stack: #stack} )
id = #cross_layer_parameter.id
format.html { redirect_to(#stack) }
format.json { render json: { new_rows: new_rows, id: id, status: 200 } }
else
format.html { redirect_to edit_stack_path(#cross_layer_parameter) }
format.json { respond_with_bip(#cross_layer_parameter) }
end
end
end
This is working as expected and I want to write an RSpec test in my controller tests that checks that. Until now I have:
RSpec test file:
cross_layer_parameter_controller_spec.rb
describe 'PUT #update' do
context "with params donor_layer or acceptor_layer" do
before do
post :update, params: { stack_id: #stack.id, donor_layer: 5, id: #cross_layer_parameter.id, cross_layer_parameter: FactoryGirl.attributes_for(:cross_layer_parameter) }
#cross_layer_parameter.reload
end
it "should changed the donor material '0'" do
expect(#cross_layer_parameter.donor_material).to eq 0
end
end
end
I suspect FactoryGirl is messing it up but I'm not sure. Is there another way to test wittout using FactoryGirl? I've tried the put underneath but this didn't work.
post :update, params: { stack_id: #stack.id, donor_layer: 5, id: #cross_layer_parameter.id }
FactoryGirl file:
cross_layer_parameter.rb
require 'faker'
FactoryGirl.define do
factory :cross_layer_parameter do
donor_layer { Faker::Number.between(0, 10) }
donor_material { Faker::Number.between(0, 10) }
acceptor_layer { Faker::Number.between(0, 10) }
acceptor_material { Faker::Number.between(0, 10) }
interaction { Faker::Number.between(1, 9) }
value { Faker::Number.decimal(2) }
end
end

You should not add params as params argument there. First part is type of action and action (put and update) and rest are params. Here is code sample for put update:
let(:params) { { attachment: '' } }
let(:action) { put :update, parent_id: parent.id, id: object.id, object: params }

Related

Rails + Rspec: Test that service is called

I am trying to write a test to ensure that my service, WeeklyReportCardService, is instantiated and that it's method :send_weekly_report_card_for_repositioning is called.
Here's the controller:
def update
Audited.audit_class.as_user($user) do
if #check_in.update(check_in_params)
client = Client.find_by(id: check_in_params[:client_id])
if #check_in.repositioning.present? && #check_in.weigh_in.present? && #check_in.client&.location&.name == "World Wide"
# I see this in the console so the if statement returns true
p "hitting send!!"
WeeklyReportCardService.new.send_weekly_report_card_for_repositioning(#check_in.repositioning)
end
render json: #check_in, status: :ok, serializer: API::CheckInsIndexSerializer
else
render json: #check_in.errors.full_messages, sattus: :unprocessable_entity
end
end
end
Here's my test:
RSpec.describe API::CheckInsController, type: :request do
fit "should send if the client's location is World Wide" do
program = create(:program, :with_client)
worldwide = create(:location, name: "World Wide")
program.client.update(location_id: worldwide.id)
check_in = create(:check_in, client_id: program.client.id, program_id: program.id)
create(:repositioning, check_in_id: check_in.id)
create(:weigh_in, check_in_id: check_in.id)
url = root_url[0..-2] + api_check_in_path(check_in.id) + "?sendReportCardEmail=true"
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
expect_any_instance_of(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
end
end
and the error I see is:
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
Exactly one instance should have received the following message(s) but didn't: send_weekly_report_card_for_repositioning
What else do I need to do to ensure that function is called?
You're doing it in the wrong order. You need to set the expectation first before the method is expected to be called:
expect_any_instance_of(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
If you need to set the expecations afterwards you need to replace the method or object with a spy which is useful if you prefer the arrange-act-assert (or given-when-then)
pattern for structuring tests.
You should also note that the use of any instance is strongly discouraged and you can avoid it by providing a simple class method:
class WeeklyReportCardService
def self.send_weekly_report_card_for_repositioning(...)
new.send_weekly_report_card_for_repositioning(...)
end
end
RSpec.describe API::CheckInsController, type: :request do
it "should send if the client's location is World Wide" do
expect(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
end
end
Or alternatively by stubbing the WeeklyReportCardService#new method to return a mock or spy.

RSpec returns empty string in a fairly simple get request

I want to test show action in my Shipment controller. To do so I've prepared fairly simple specs:
RSpec.describe ShipmentsController, type: :controller do
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
ShimpentsController.rb
class ShipmentsController < ApplicationController
before_action :set_product
attr_reader :shipment
def show
#shipment = Shipment.find(params[:id])
#items = shipment&.grouped_shipment_items
end
private
def set_product
#product = Product.find(params[:product_id])
end
end
When I use postman everything went well - it returns expected json but in the RSpec test I'm getting:
response.body
=> ""
I think you need to add render_views in your controller spec file.
RSpec.describe ShipmentsController, type: :controller do
render_views
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
Reference: https://rubyinrails.com/2019/04/11/rails-test-jbuilder-json-response-with-rspec/
I think you are not making a request for JSON response with rspec. You can check by putting a breakpoint in your controller action, then checking
request.format.json?
In order to ask for JSON response from an rspec test, you should add as: :json to the end of the request. The request should look like this:
get :show, params: params, as: :json

How to do testing in rails using rspec with factory girl?

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.

Where to put mocks

I'm trying to spec this action.
def get
#asset = current_user.assets.find(params[:id])
send_file #asset.uploaded_file.path, type: #asset.uploaded_file_content_type
rescue ActionController::MissingFile
redirect_to assets_url, error: 'missing file'
end
To test the send file method we mock it out.
controller.should_receive(:send_file)
However, I have no idea where to put this mock:
Here's how my spec looks:
subject { response }
let!(:user) { FactoryGirl.create(:user) }
let!(:user_2) { FactoryGirl.create(:user) }
let!(:asset) { FactoryGirl.create(:asset, user_id: user.id) }
let!(:file) { fixture_file_upload('files/eve.jpg', 'image/jpeg') }
let!(:folder) { FactoryGirl.create(:folder, user_id: user.id, parent_id: nil) }
before do
sign_in user
end
describe '#get' do
context 'when exists' do
before do
get :get, id: asset.id
end
# controller.should_receive(:send_file).with(*args) <-- I need to test that
it { should have_http_status 302 }
end
context 'when doesn\'t exist' do
before do
get :get, id: 765
end
it { should redirect_to_location '/assets'}
it { should set_flash_type_to :error }
it { should set_flash_message_to 'missing file' }
end
end
How do I test line 6. I want to keep the one line syntax if possible.
Put it in the before block
before do
controller.should_receive(:send_file)
get :get, id: asset.id
end

How to stub after_create callback save! in model?

I receive following error:
Output:
1) LabelsController#create label is new creates a new label
Failure/Error: post :create, attributes[:label], format: :json
NoMethodError:
undefined method `save!' for nil:NilClass
# ./app/models/labeling.rb:17:in `update_target'
In Labeling model:
after_create :update_target
def update_target
self.target.save!
end
Test:
require 'spec_helper'
describe LabelsController do
before(:each) do
controller.stub(:current_user).and_return(mock_model(User))
stub_request(:any, "www.example.com").to_return(status: 200)
end
describe "#create" do
context "label is new" do
it "creates a new label" do
attributes = {
label: {
name: "test",
labeling: {
target_type: "Link", target_id: 1
}
}
}
response.status.should == 200
post :create, attributes[:label], format: :json
end
end
end
end
Labeling controller:
def create
label = Label.find_by_name(params[:name])
labeling = label.labelings.build do |lb|
lb.user_id = current_user.id
lb.target_type = params[:labeling][:target_type]
lb.target_id = params[:labeling][:target_id]
end
if labeling.save
render json: {
name: label.name,
id: label.id,
labeling: {
id: labeling.id
}
}
end
end
By the looks of it you don't have a Target with ID 1 on the database, so where you refer to self.target the returned value is nil.
What I'd do in your case is first create a target and then pass its id to the attributes hash:
target = Traget.create!
attributes = {
label: {
name: "test",
labeling: {
target_type: "Link", target_id: target.id
}
}
}
This way you don't need to stub anything.
If you really must stub the method you can use RSpecs any_instance method:
Labeling.any_instance.stub(:update_target).and_return(true)

Resources