Write RSpec for include method - ruby-on-rails

Need to write RSpec for specific function:
def display_type(record,house_record)
#user_value = either user will provide input through enviroment, else it will be ['dctEng']
user_value =
if ENV['USER_VALUE'].nil?
ServiceConfig[:USER_VALUE]
else
ENV['USER_VALUE'].split(',')
end
user_value.map!(&:downcase)
myArr = [
'ani',
'awe',
'emi'
]
if user_value.include?(record[:mnemonic].downcase)
return (myArr.include?(house_record[:name] || house_record[:mnemonic]) ? true : false)
else
return [true, false].sample
end
end
I tried this one:
I have added the variable record and house_record but don't know how to properly write testcase for this method
describe '#display_type' do
let(:record) do
{
name: 'test_name',
mnemonic: 'test_schema_name1'
}
end
let(:house_record) do
{
name: 'test_name',
mnemonic: 'test_mnemonic',
row: true,
col: true
}
end
let(:user_value) do [
'test_name1',
'test_name2'
] end
let(:myArr) do [
'test1',
'test2',
'test3',
'test4'
] end
#updated_code:
it 'should include record[:mnemonic] in myArr array' do
result = display_type(record,house_record)
expect([true]).to include(result)
end
it 'should not include record[:mnemonic] in myArr array' do
result = display_type(record,house_record2)
expect([true,false]).to include(result)
end
end
But don't know how to complete it, getting continuous error:
It is not coming under IF block return statement, I tried to check it using binding.pry, it is giving true for
if user_value.include?(record[:mnemonic].downcase)
return (myArr.include?(house_record[:name] || house_record[:mnemonic]) ? true : false)
But while running rspec it is going to else block and do sampling based on true and false
Kindly, give a look, I updated one case, how to improve it, because it is not going in if condition

Related

Rails Rspec - How to test if Service has been called in another Service

While writing tests, I stopped at trying to test Service in another Service. In such a situation, I should probably just check if Service has been called because it has already been tested elsewhere. I did a little research on the Internet and found something like have_received but I have no idea how to use it in my example.
check_service.rb
Class CheckService
def initialize(params)
#params = params
end
def self.call(params)
new(params).call
end
def call
CheckUser.call(params[:user_id])
end
end
check_service_spec.rb
...
describe 'call' do
let(:result) { CheckService.call(params) }
let(:params) { { user_id: "100" } }
let(:check_user) { instance_double(CheckUser) }
before do
allow(check_user).to receive(:call).and_return(true)
end
it do
result
expect(check_user).to have_received(:call)
end
end
...
I was trying something like this (it's simple example), but I get error:
(InstanceDouble(CheckUser) (anonymous)).call(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Is there any option to test situation I presented?
Short anwser
describe 'call' do
let(:result) { CheckService.call(params) }
let(:params) { { user_id: "100" } }
## let(:check_user) { instance_double(CheckUser) } delete this
before do
allow(CheckUser).to receive(:call).and_return(true)
end
it do
result
expect(CheckUser).to have_received(:call)
end
end
Alternative
I think a better way to test this is to use DI (Dependency Injection), so you pass CheckUser as a dependency to CheckService. I prefer to write the whole test inside the it block too!
class CheckService
def initialize(params, check_handler:)
#params = params
#check_handler = check_handler
end
def self.call(params, check_handler: CheckUser)
new(params, check_handler: check_handler).call
end
def call
#check_handler.call(#params[:user_id])
end
end
describe 'call' do
it 'check user with params' do
check_user = class_double(CheckUser)
allow(check_user).to receive(:call).and_return(true)
params = { user_id: "100" }
CheckService.call(params, check_handler: check_user)
expect(check_user).to have_received(:call)
end
end
A blog post to read more about -> https://blog.testdouble.com/posts/2018-05-17-do-we-need-dependency-injection-in-ruby/

how to mock method retry and variable assignment inside method in rspec

require 'requiredclass'
class Test
def get_client()
return some_client
end
def intermediate_method()
res = nil
self.class
.get_client
.retry(tries:5, on: [RequiredClass::ClientTimeout]) do |myclient|
call_count += 1
res = myclient.dosomething()
end
return res
end
def method_to_test()
x = intermediate_method()
y = false
return x && y
end
end
How can I write rspec for method_to_test here. How can I mock get_client.retry as well as calls to get_client while also mocking res variable assignment so that gets assigned the value i would like it to assign.
As written, this code is difficult to test. That's a smell and a sign that the code should be restructured. Really any time you feel tempted to mock a method in the current class, that's a sign that the thing you want to mock does not belong in that class. It should be injected (passed in) instead. Like this:
require 'requiredclass'
class Test
attr_reader :client
def initialize(client)
#client = client
end
def method_to_test
x = intermediate_method
y = false
x && y
end
def intermediate_method
res = nil
client.retry(tries: 5, on: [RequiredClass:ClientTimeout]) do |my_client|
call_count += 1
res = my_client.do_something
end
res
end
end
Given this refactored code, the tests might look like this:
RSpec.describe Test do
subject(:test) { Test.new(client) }
let(:client) { instance_double(Client, retry: true, do_something: true) }
describe '#method_to_test'
subject(:method_to_test) { test.method_to_test }
it 'returns false' do
expect(method_to_test).to be_false
end
end
end
In this code I've passed a double with a stubbed retry method into the Test class on instantiation. You could optionally use a mock, instead. That would look like this:
RSpec.describe Test do
subject(:test) { Test.new(client) }
let(:client) { instance_double(Client) }
before do
allow(client).to receive(:retry)
allow(client).to receive(:do_something)
end
describe '#method_to_test'
subject(:method_to_test) { test.method_to_test }
it 'returns false' do
expect(method_to_test).to be_false
end
end
end
There's a good write up of mocks and doubles in the RSpec documentation.

Ruby 2.4/Rails 5: making a recursive array of hashes, deleting if key is blank

I've got a class that looks like this that turns a collection into a nested array of hashes:
# variable_stack.rb
class VariableStack
def initialize(document)
#document = document
end
def to_a
#document.template.stacks.map { |stack| stack_hash(stack) }
end
private
def stack_hash(stack)
{}.tap do |hash|
hash['stack_name'] = stack.name.downcase.parameterize.underscore
hash['direction'] = stack.direction
hash['boxes'] = stack.boxes.indexed.map do |box|
box_hash(box)
end.reverse_if(stack.direction == 'up') # array extensions
end.delete_if_key_blank(:boxes) # hash extensions
end
def box_hash(box)
{}.tap do |hash|
hash['box'] = box.name.downcase.parameterize.underscore
hash['content'] = box.template_variables.indexed.map do |var|
content_array(var)
end.join_if_any?
end.delete_if_key_blank(:content)
end
def content_array(var)
v = #document.template_variables.where(master_id: var.id).first
return unless v
if v.text.present?
v.text
elsif v.photo_id.present?
v.image.uploaded_image.url
else
''
end
end
end
# array_extensions.rb
class Array
def join_if_any?
join("\n") if size.positive?
end
def reverse_if(boolean)
reverse! if boolean
end
end
# hash_extensions.rb
class Hash
def delete_if_key_blank(key)
delete_if { |_, _| key.to_s.blank? }
end
end
This method is supposed to return a hash that looks like this:
"stacks": [
{
"stack_name": "stack1",
"direction": "down",
"boxes": [
{
"box": "user_information",
"content": "This is my name.\n\nThis is my phone."
}
},
{
"stack_name": "stack2",
"direction": "up",
"boxes": [
{
"box": "fine_print",
"content": "This is a test.\n\nYeah yeah."
}
]
}
Instead, often the boxes key is null:
"stacks": [
{
"stack_name": "stack1",
"direction": "down",
"boxes": null
},
{
"stack_name": "stack2",
"direction": "up",
"boxes": [
{
"box": "fine_print",
"content": "This is a test.\n\nYeah yeah."
}
]
}
I suspect it's because I can't "single-line" adding to arrays in Rails 5 (i.e., they're frozen). The #document.template.stacks is an ActiveRecord collection.
Why can't I map records in those collections into hashes and add them to arrays like hash['boxes']?
The failing test
APIDocumentV3 Instance methods #stacks has the correct content joined and indexed
Failure/Error:
expect(subject.stacks.first['boxes'].first['content'])
.to include(document.template_variables.first.text)
expected "\n" to include "#1"
Diff:
## -1,2 +1 ##
-#1
The presence of \n means the join method works, but it shouldn't join if the array is empty. What am I missing?
reverse_if returns nil if the condition is false. Consider this:
[] if false #=> nil
You could change it like this:
def reverse_if(condition)
condition ? reverse : self
end
delete_if_key_blank doesn't look good for me. It never deletes anything.
Disclaimer. I don't think it's a good idea to extend standard library.
So thanks to Danil Speransky I solved this issue, although what he wrote doesn't quite cover it.
There were a couple of things going on here and I solved the nil arrays with this code:
hash['boxes'] = stack.boxes.indexed.map do |box|
box_hash(box) unless box_hash(box)['content'].blank?
end.reverse_if(stack.direction == 'up').delete_if_blank?
end
That said, I'm almost certain my .delete_if_blank? extension to the Array class isn't helping at all. It looks like this, FYI:
class Array
def delete_if_blank?
delete_if(&:blank?)
end
end
I solved it by thowing the unless box_hash(box)['content'].blank? condition on the method call. It ain't pretty but it works.

Call a generic function with or without parameters

I had a code looking like this:
def my_function(obj)
if obj.type == 'a'
return [:something]
elsif obj.type == 'b'
return []
elsif obj.type == 'c'
return [obj]
elsif obj.type == 'd'
return [obj]*2
end
end
I want to separate all these if...elsif blocks into functions like this:
def my_function_with_a
return [:something]
end
def my_function_with_b
return []
end
def my_function_with_c(a_parameter)
return [a_parameter]
end
def my_function_with_d(a_parameter)
return [a_parameter] * 2
end
I call these functions with
def my_function(obj)
send(:"my_function_with_#{obj.type}", obj)
end
The problem is that some functions need parameters, others do not. I can easily define def my_function_with_a(nothing=nil), but I'm sure there is a better solution to do this.
#Dogbert had a great idea with arity. I have a solution like this:
def my_function(obj)
my_method = self.method("my_function_with_#{obj.type}")
return (method.arity.zero? ? method.call : method.call(obj))
end
Check how to call methods in Ruby, for that I will recommend you this two resources: wikibooks and enter link description here.
Take a special note on optional arguments where you can define a method like this:
def method(*args)
end
and then you call call it like this:
method
method(arg1)
method(arg1, arg2)
def foo(*args)
[ 'foo' ].push(*args)
end
>> foo
=> [ 'foo' ]
>> foo('bar')
=> [ 'foo', 'bar' ]
>> foo('bar', 'baz')
=> [ 'foo', 'bar', 'baz' ]
def my_function(obj)
method = method("my_function_with_#{obj.type}")
method.call(*[obj].first(method.arity))
end
Change your function to something like:
def my_function_with_foo(bar=nil)
if bar
return ['foo', bar]
else
return ['foo']
end
end
Now the following will both work:
send(:"my_function_with_#{foo_bar}")
=> ['foo']
send(:"my_function_with_#{foo_bar}", "bar")
=> ['foo', 'bar']
You can also write it like this if you don't want to use if/else and you're sure you'll never need nil in the array:
def my_function_with_foo(bar=nil)
return ['foo', bar].compact
end
You can use a default value
def fun(a_param = nil)
if a_param
return ['raboof',a_param]
else
return ['raboof']
end
end
or...
def fun(a_param : nil)
if a_param
return ['raboof',a_param]
else
return ['raboof']
end
end
The latter is useful if you have multiple parameters because now when you call it you can just pass in the ones that matter right now.
fun(a_param:"Hooray")

rails rspec, any way to handle multiple 'should' for the same example via a block?

Suppose the model method foo() returns an array [true, false, 'unable to create widget']
Is there a way to write an rspec example that passes that array as a block that verifies [0] = true, [1] = false, and [2] matches a regex like /
Currently, I do it like:
result = p.foo
result[2].should match(/unable/i)
result[0].should == true
result[1].should == false
I can't quite get my head around how that might be doable with a block?
It would be slightly over engineered but try to run this spec with --format documentation. You will see a very nice specdocs for this method ;)
describe '#some_method' do
describe 'result' do
let(:result) { subject.some_method }
subject { result }
it { should be_an_instance_of(Array) }
describe 'first returned value' do
subject { result.first }
it { should be_false }
end
describe 'second returned value' do
subject { result.second }
it { should be_true }
end
describe 'third returned value' do
subject { result.third }
it { should == 'some value' }
end
end
end
Do you mean that your result is an array and you have to iterate over to test it's various cases?
Then, you can do that by following, right:
result = p.foo
result.each_with_index do |value, index|
case index
when 0 then value.should == true
when 1 then value.should == false
when 2 then value.shoud match(/unable/i)
end
end

Resources