Random results when trying to set an attachment with ActiveStorage - ruby-on-rails

I have the following in my test:
...
let(:image) { Rack::Test::UploadedFile.new('spec/support/assets/test.png', 'image/png') }
subject { described_class.new(gold_bar_order).set_bar_custom_image(image) }
it do
subject
expect(gold_bar_order.reload.bar_custom_image.present?).to be_truthy
end
And here is the class from the method I'm testing:
class GoldBarOrderCustomizationUpdater
def initialize(gold_bar_order)
#gold_bar_order = gold_bar_order
end
def set_bar_custom_image(bar_custom_image)
update_values do
#gold_bar_order.bar_custom_image.attach(bar_custom_image)
#gold_bar_order.save!
end
end
...
private
def update_values
ActiveRecord::Base.transaction do
yield
calculator = MasterOrderCalculator.new(#gold_bar_order.master_order)
calculator.recalculate_sub_orders!
calculator.save_values!
end
end
end
What is happening is that I'm receiving random results for the described test: if I run it a given number of times, it fails or passes without particular order.
I couldn't understand why. Any guesses?

Related

How to use the allow method in RSpec to mock a function inside a controller that is inside a module (Module > Controller > Function)

I am trying to write the allow method in RSpec. My rails controller is
module Users
class ProfilesController < ApplicationController
# Update user profile
def update
payload = { name: params[:user][:name],email: params[:user][:email]}
response = send_request_to_update_in_company(payload)
if response['code'] == 200
if User.first.update(user_params)
render json: { message: "User successfully updated"}, status: :ok
else
head :unprocessable_entity
end
else
render json: { error: 'Error updating user in Company' },status: :unprocessable_entity
end
end
private
def send_request_to_update_in_comapny(payload)
response = Api::V1::CompanyRequestService.new(
payload: payload.merge(company_api_access_details),
url: 'customers/update_name_email',
request_method: Net::HTTP::Post
).call
JSON.parse(response.body)
end
end
end
When I write the bellow code in my test file
allow(Users::ProfilesController).to receive(:send_request_to_update_in_company).and_return({ 'code' => 500 })
I am getting the following error in terminal
Users::ProfilesController does not implement: send_request_to_update_in_comapny
enter code here
With allow_any_instance_of I am able to get the code working. But how can I implement it using allow?
Yes, allow_any_instance_of works because, as the name suggests, it allows any instance of Users::ProfilesController to respond to the instance method send_request_to_update_in_company with your mock return value.
However, your line
allow(Users::ProfilesController).to receive(:send_request_to_update_in_company)
is telling RSpec to mock a class method called send_request_to_update_in_company, which doesn't exist. And so, you're seeing the error message saying so.
You don't say where your test is situated, but generally wherever it is, it's not a good idea to either test or stub out a private method.
I'd be inclined to instead create a mock Api::V1::CompanyRequestService object to return a fake response, which your controller code can then parse as expected and produce the expected JSON. For example
mock_request = instance_double(Api::V1::CompanyRequestService)
allow(mock_request).to receive(:call).and_return('{"code": 500}')
allow(Api::V1::CompanyRequestService).to receive(:new).and_return(mock_request)
Another approach might be to leave your service alone, and instead use tools like VCR or WebMock to provide mocked JSON values at the network layer - your code can think it's calling out to the internet, but really it gets back responses that you define in your tests.
How about this way:
spec/requests/users/profiles_controller_spec.rb
require 'rails_helper'
RSpec.describe "Users::ProfilesControllers", type: :request do
describe "Test call to special function: " do
let(:controller) { Users::ProfilesController.new }
it "Should response to code 500" do
response = controller.send_request_to_update_in_company("test")
expect(response).to eq({"code"=>"500", "test1"=>"abc", "test2"=>"def"})
end
it "Should return to true" do
response = controller.true_flag?
expect(response).to eq(true)
end
end
end
app/controllers/users/profiles_controller.rb
module Users
class ProfilesController < ApplicationController
# Update user profile
def update
payload = { name: params[:user][:name],email: params[:user][:email]}
response = send_request_to_update_in_company(payload)
Rails.logger.debug "Ok71 = response['code'] = #{response['code']}"
# if response['code'] == 200
# if User.first.update(user_params)
# render json: { message: "User successfully updated"}, status: :ok
# else
# head :unprocessable_entity
# end
# else
# render json: { error: 'Error updating user in Company' },status: :unprocessable_entity
# end
end
# Not private, and not mistake to 'send_request_to_update_in_comapny'
def send_request_to_update_in_company(payload)
response = Api::V1::CompanyRequestService.new(
payload: "for_simple_payload_merge_values",
url: 'for_simple_customers/update_name_email',
request_method: "for_simple_request_method"
).call
Rails.logger.debug "Ok66 = Start to log response"
Rails.logger.debug response
JSON.parse(response.body)
end
# Simple function to test
def true_flag?
true
end
end
end
app/services/api/v1/company_request_service.rb
class Api::V1::CompanyRequestService < ActionController::API
def initialize(payload="test1", url="test2", request_method="test3")
#payload = payload
#url = url
#request_method = request_method
end
def call
#object = Example.new
#object.body = {code: "500", test1: "abc", test2: "def"}.to_json
return #object
end
end
class Example
attr_accessor :body
def initialize(body={code: "000", test1: "init_value_abc", test2: "init_value_def"}.to_json)
#body = body
end
end
I use simple code to simulate your project. Modify it to suitable your working! Tell me about your its thinking. Thank you!

Rspec: how to test Service Object method "call" which is called in Controller action create?

Can somebody help me with rspec testing method call in Service Object?
class UserEntitiesController < ApplicationController
def create
#result = UserEntities::Create.new(params).call
return render '/422.json.jbuilder', status: :unprocessable_entity unless #result
end
here is the service objects:
module UserEntities
class Create
attr_accessor :params
def initialize(params)
#params = params
end
def call
#user_entity = UserEntity.new(user_entity_params)
set_time
if #user_entity.save
#user_entity
else
error_result
end
end
private
def error_result
false
end
def user_entity_params
#params.require(:user_entity).permit(:information,
:destroy_option,
:reviews)
end
def set_time
if #params[:available_days].present?
#user_entity.termination = Time.now + #params[:available_days].days
end
end
end
end
I tried to find information how to do this, but there are not so many.
Also i read some
You can certainly write a unit test to test the Service Object standalone
In this case, create a file spec/services/user_entities/create_spec.rb
describe UserEntities::Create do
let(:params) { #values go here }
context ".call" do
it "create users" do
UserEntities::Create.new(params).call
# more test code
end
# more tests
end
end
Later in the controller tests, if you are planning to write such, you do not need to test UserEntities::Create instead you can just mock the service object to return the desired result
describe UserEntitiesController do
before do
# to mock service object in controller test
allow(UserEntities::Create).to receive(:new)
.and_return(double(:UserEntities, call: "Some Value"))
end
# controller tests go here
end
As a supplement to #bibin answer.
If you want to mock some instance's method renturn:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_return("some value")
if you want to raise a eror:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_raise("boom")

Getting undefined method error in RSpec

I'm using RSpec and FactoryGirl for testing my models and I'm stuck at "highest_priority" method which can't be seen by RSpec for some reason.
Here's the method itself:
models/task.rb
class Task < ActiveRecord::Base
#some stuff
def self.highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
end
end
And when I run task_spec.rb
require 'spec_helper'
describe Task do
it "returns highest priority" do
last_task = FactoryGirl.build(:task, priority: "5")
last_task.highest_priority
expect(last_task(:priority)).to eq("6")
end
end
I get the following error:
When I'm calling this method in my controller like this
def create
#task = current_user.tasks.build(task_params)
#task.highest_priority
#task.complete = false
respond_to do |format|
if #task.save
format.js
else
format.js
end
end
end
And the method looks like
def highest_priority
self.maximum(:priority).to_i + 1
end
I'm getting
First of all, you better use ActiveRecord's maximum instead of ordering and then picking one, you'll avoid the instance initialization and get a number directly from the query
Task.maximum(:priority)
this could be put in a class method like this
def self.maximum_priority
Task.maximum(:priority) || 0 # fall back to zero if no maximum exists
end
Then for the second half which is updating the method, i would create an instance method for that, and using the class method
def set_maximum_priority
self.priority = self.class.maximum_priority + 1
self
end
Note that I returned self at the end for chainability
Then your action would become something like this
def create
#task = current_user.tasks.build(task_params).set_maximum_priority
#task.complete = false
...
end
You need to create the method as an instance method of Task model. Like below :
class Task < ActiveRecord::Base
#some stuff
def highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
end
end

Testing mixin in ruby

I'm trying to write a rspec test for a mixin class. I have the following.
module one
module two
def method
method_details = super
if method_details.a && method_details.b
something
elsif method_details.b
another thing
else
last thing
end
end
end
end
Now I have mocked the "method" object that will be passed to the class.
But I'm struggling to access the super method.
I did,
let(:dummy_class) { Class.new { include one::two } }
How to pass the mocked method object to this dummy class?
How do I go about testing this? New to ruby, can someone show me a direction with this.
Thanks in advance.
UPDATE:
I tried,
let(:dummy_class) {
Class.new { |d|
include one::two
d.method = method_details
}
}
let (:method_details){
'different attributes'
}
still doesn't work. I get undefined local variable or method method_details for #<Class:0x007fc9a49cee18>
I personally test mixing with the class. Because the mixing (module) itself has no meaning unless its attached to a class/object.
Ex:
module SayName
def say_name
p 'say name'
end
end
class User
include SayName
end
So I believe you should test your module with attached to the relevant class / object.
How ever this is a different perspective on testing mixings
HTH
I think that in your specs, you'll need to explicitly provide a super class definition for when super is called in #method as "you can't mock super and you shouldn't".
I've attempted to spec out all three of your scenarios with the following minor changes:
Changed your example code slightly to become valid Ruby
Changed #method to #the_method so it doesn't conflict with Object#method
Used OpenStruct to represent the object that super returns, because all I know is that it's an object that has methods #a and #b. You can change that out as appropriate for your real specs
Copy and paste the class and specs below into a file and give them a try:
module One
module Two
def the_method
method_details = super
if method_details.a && method_details.b
'something'
elsif method_details.b
'another thing'
else
'last thing'
end
end
end
end
RSpec.describe One::Two do
require 'ostruct'
let(:one_twoable) { Class.new(super_class) { include One::Two }.new }
describe '#the_method' do
let(:the_method) { one_twoable.the_method }
context 'when method_details#a && method_details#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: true, b: true)
end
end
end
it 'is "something"' do
expect(the_method).to eq('something')
end
end
context 'when just method#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: true)
end
end
end
it 'is "another thing"' do
expect(the_method).to eq('another thing')
end
end
context 'when neither of the above' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: false)
end
end
end
it 'is "last thing"' do
expect(the_method).to eq('last thing')
end
end
end
end

How to test such class behavior in Rspec

I have a class which is responsible for dealing with some response from payments gateway.
Let's say:
class PaymentReceiver
def initialize(gateway_response)
#gateway_response = gateway_response
end
def handle_response
if #gateway_response['NC_STATUS'] != '0'
if order
order.fail_payment
else
raise 'LackOfProperOrder'
# Log lack of proper order
end
end
end
private
def order
#order ||= Order.where(id: #gateway_response['orderID']).unpaid.first
end
end
In payload from payment I've NC_STATUS
which is responsible for information if payment succeed and orderID which refers to Order ActiveRecord class byid`.
I would like to test behavior(in rspec):
If PaymentReceiver receives response where NC_STATUS != 0 sends fail_payment to specific Order object referred by orderID.
How you would approach to testing this ? I assume that also design could be bad ...
You have to make refactorization to remove SRP and DIR principles violations.
Something below I'd say:
class PaymentReceiver
def initialize(response)
#response = response
end
def handle_response
if #response.success?
#response.order.pay
else
#response.order.fail_payment
end
end
end
# it wraps output paramteres only !
class PaymentResponse
def initialize(response)
#response = response
end
def order
# maybe we can check if order exists
#order ||= Order.find(#response['orderID'].to_i)
end
def success?
#response['NCSTATUS'] == '0'
end
end
p = PaymentReceiver.new(PaymentResponse({'NCSTATUS' => '0' }))
p.handle_response
Then testing everything is easy.

Resources