How to stub after_create callback save! in model? - ruby-on-rails

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)

Related

Rspec: Mocked payload is returning nil instead of the actual object created for testing purposes

I've created the following service
class ApiCustomers < ApiBase
def initialize(url: nill)
url = url.presence || ENV.fetch("CUSTOMERS_API_APP_NAME")
super(url)
end
def find_customer_apps(customer_id, params=nil)
begin
customer_url = "#{#url}/customers/#{customer_id}/applications"
# call api-customers
if params.nil?
response = connection.get(customer_url)
else
response = connection.get(customer_url, params)
end
rescue StandardError => e
Rails.logger.error "Error fetching API-CUSTOMERS Applications: #{e}"
raise e
end
return response.body["data"]
end
end
Now, I want to test that with the following code:
require "rails_helper"
RSpec.describe ApiCustomers do
let(:customer_id) { SecureRandom.uuid }
let(:customers_client) { ApiCustomers.new(url: "api_customers.local") }
context "#find_customer_apps" do
let(:app) { double("application", id: 123, name: "App", customer_id: "123", supported: false, additional_notes: "Test") }
let(:success_response) { OpenStruct.new({ status: 200, body: { "data": app } }) }
let(:bad_request) { OpenStruct.new({ status: 400, body: {"error"=>"invalid call"} }) }
describe "with valid params" do
before do
allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(success_response)
end
it "returns customer applications" do
applications_payload = customers_client.find_customer_apps(account_id, nil)
expect(applications_payload).not_to be_nil
end
end
describe "with invalid params" do
before do
allow_any_instance_of(Faraday::Connection).to receive(:get).and_raise(CustomErrors::BadRequest)
end
it "raises an error" do
expect { customers_client.find_customer_apps(nil, nil) }.to raise_error(CustomErrors::BadRequest)
end
end
end
end
The test with invalid params is working, but for some reason the one with valid params is failing.
If I print the success_response, this is being created properly. I do not know what I am getting nil in my applications_paylod. What I am missing? I thought this line should cover what I am doing: allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(success_response)
Thanks in advance.
I found the issue:
In my stub:
let(:success_response) { OpenStruct.new({ status: 200, body: { "data": app } }) }
I am creating an Struct, so, I decided to change the code of my method in order to return: response.body[:data]
instead of response.body["data"]

RSpec for stubbed method inside call

I'm trying test class which is responsible for creating Jira tickets. I want to stub create_issue method which is inside of method call
module JiraTickets
class Creator
def initialize(webhook)
#webhook = webhook
end
def call
create_issue(support_ticket_class, webhook)
end
private
def client
#client ||= JIRA::Client.new
end
def support_ticket_class
#support_ticket_class ||= "SupportBoard::Issues::#{webhook.action_type_class}".constantize
end
def create_issue(support_ticket_class, webhook)
issue = client.Issue.build
issue.save(support_ticket_class.new(webhook).call)
end
def fields
{
'fields' => {
'summary' => 'example.rb',
'project' => { 'id' => '11' },
'issuetype' => { 'id' => '3' }
}
}
end
end
end
The create_issue method should return true. So I've made a specs:
RSpec.describe JiraTickets::Creator do
describe '#call' do
subject { described_class.new(webhook).call }
let(:webhook) { GithubApi::Webhook.new(webhook_hash, 'repository') }
let(:webhook_hash) { { repository: { name: 'Test-repo' }, action: 'publicized' } }
let(:creator_instance) { instance_double(JiraTickets::Creator) }
before do
allow(described_class).to receive(:new).with(webhook).and_return(creator_instance)
allow(creator_instance).to receive(:call).and_return(true)
end
context 'when webhook class is supported' do
it 'expect to create Jira ticket' do
expect(subject).to receive(:call)
end
end
end
end
But I'm getting an error:
Failure/Error: expect(subject).to receive(:call)
true does not implement: call
You just need to check that the method was called on the stub creator_instance
RSpec.describe JiraTickets::Creator do
describe '#call' do
subject { described_class.new(webhook) }
let(:webhook) { GithubApi::Webhook.new(webhook_hash, 'repository') }
let(:webhook_hash) { { repository: { name: 'Test-repo' }, action: 'publicized' } }
before do
allow_any_instance_of(described_class).to receive(:create_issue).with(any_args).and_return(true)
end
context 'when webhook class is supported' do
it 'expects to create Jira ticket' do
expect(subject.call).to eq(true)
end
end
end
end

Rails RSpec controller test changes second attribute

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 }

How do you test authentication in API controllers

I'm writing a Rails API and am stuck trying to write controllers that will test the authentication. For instance, I have in my PostController before_action :authenticate which is
def authenticate
authenticate_or_request_with_http_token do |token, options|
User.find_by(:auth_token => token)
end
end
And this is my PostsController test:
require 'rails_helper'
RSpec.describe Api::PostsController, type: :controller do
let(:valid_attributes) {
{
"title" => "Post title",
"content" => "Post content",
"status" => "published"
}
}
let(:invalid_attributes) {
{
"title" => "",
"content" => "",
"status" => ""
}
}
let(:valid_session) { {} }
before do
params = { session: { email: 'wagner.matos#mac.com', password: 'foobar' } }
SessionsController.create params
#post = Post.new({
title: "Some title",
content: 'Some content',
status: "published"
})
end
it "creates a new post" do
post :create, post: #post
expect(Post.count).to eq(1)
end
end
Yet the above is failing with the following error:
1) Api::PostsController creates a new post
Failure/Error: SessionsController.create params
NoMethodError:
undefined method `create' for SessionsController:Class
Any suggestions?
You can not invoke create action of SessionsController with class. Better you create user object and assign to request.env the same token. like below sample code -
require 'rails_helper'
RSpec.describe Api::PostsController, type: :controller do
before do
user = User.create( :auth_token => 'token' )
request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Token.encode_credentials("token")
end
end

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

Resources