How to fix a task test in RSpec? - ruby-on-rails

I have a table called Jurisdiction and created that task to update the kind column:
namespace :populate_jurisdiction do
desc "Populate column kind of jurisdiction model"
task kind: :environment do
Jurisdiction.all.each { |jurisdiction|
case (jurisdiction.kind.nil? || jurisdiction.kind.blank?)
when jurisdiction.name.parameterize == "federal"
jurisdiction.kind = 1
when jurisdiction.name.parameterize == "estadual"
jurisdiction.kind = 2
when jurisdiction.name.parameterize == "municipal"
jurisdiction.kind = 3
when jurisdiction.name.parameterize == "privado"
jurisdiction.kind = 4
end
jurisdiction.save!
}
end
end
Then I created that test
require "spec_helper"
Rails.application.load_tasks
describe "populate_jurisdiction:kind" do
context "when update kind column of jurisdiction" do
let(:federal) { create(:jurisdiction, name: "Federal", kind: nil) }
let(:state) { create(:jurisdiction, name: "Estadual", kind: nil) }
let(:municipal) { create(:jurisdiction, name: "Municipal", kind: '') }
let(:particular) { create(:jurisdiction, name: "Privado", kind: '') }
it "when kind column is updated" do
Rake::Task["populate_jurisdiction:kind"].invoke
expect(federal.kind).to eq(1)
expect(state.kind).to eq(2)
expect(municipal.kind).to eq(3)
expect(particular.kind).to eq(4)
end
end
end
When I run the task in rails console it works, but when I run the test, I got this error
Failures:
1) populate_jurisdiction:kind when update kind column of jurisdiction when kind column is updated
Failure/Error: expect(federal.kind).to eq(1)
expected: 1
got: nil
(compared using ==)
What I'm doing wrong? How can I fix this test?

You're invoking the rake task before any of the Jurisdiction's are created. When you say this:
let(:federal) { create(:jurisdiction, name: "Federal", kind: nil) }
that will create federal when you first access it in a test and then remember that for the duration of the test. So there is no federal until after your rake task runs. If you used let! instead of let and reloaded the jurisdictions after the task runs, you'd get better results.
BTW, your rake task doesn't work they way you think it does. There are two forms of case:
case expr
when value1
...
when value2
...
end
and
case
when expr1
...
when expr2
...
end
You're using the first form when you mean to be using the second and your code is working by accident. I suspect that all your kinds are nil when you run this, otherwise you'd end up doing:
case false
...
end
and you'd go into the first branch where the jurisdiction.name.parameterize test failed.
Your task should look more like:
Jurisdiction.all.reject { |j| j.kind.blank? }.each do |jurisdiction|
case jurisdiction.name.parameterize
when 'federal'
jurisdiction.kind = 1
when 'estadual'
jurisdiction.kind = 2
when 'municipal'
jurisdiction.kind = 3
when 'privado'
jurisdiction.kind = 4
end
jurisdiction.save!
end
or:
Jurisdiction.all.reject { |j| j.kind.blank? }.each do |jurisdiction|
jurisdiction.kind = case jurisdiction.name.parameterize
when 'federal' then 1
when 'estadual' then 2
when 'municipal' then 3
when 'privado' then 4
end
jurisdiction.save!
end
If kind is an integer then kind.blank? will only true when kind.nil? so that can be pushed into the database:
Jurisdiction.where(kind: nil).each do |jurisdiction|
jurisdiction.kind = case jurisdiction.name.parameterize
when 'federal' then 1
when 'estadual' then 2
when 'municipal' then 3
when 'privado' then 4
end
jurisdiction.save!
end
and it looks like #parameterize in this case is only going to be converting the name to lower case so push all the logic into the database:
# This is an SQL CASE expression, not a Ruby one
Jurisdiction.where(kind: nil).update_all(%q(
kind = case lower(name)
when 'federal' then 1
when 'estadual' then 2
when 'municipal' then 3
when 'privado' then 4
end
))

When you invoke the rake task, there are no jurisdictions, that's why you're getting nil. For example, federal jurisdiction is only created after the rake task when you call federal.kind.
require "spec_helper"
Rails.application.load_tasks
describe "populate_jurisdiction:kind" do
context "when update kind column of jurisdiction" do
let(:federal) { create(:jurisdiction, name: "Federal", kind: nil) }
let(:state) { create(:jurisdiction, name: "Estadual", kind: nil) }
let(:municipal) { create(:jurisdiction, name: "Municipal", kind: '') }
let(:particular) { create(:jurisdiction, name: "Privado", kind: '') }
it "when kind column is updated" do
# NOTE: jurisdictions are not created until `let` blocks are called.
federal
state
municipal
particular
Rake::Task["populate_jurisdiction:kind"].invoke
# NOTE: `let` return values are memoized, second call will just
# retrieve the value. You have to reload the models as well
# to get the updated values from the database.
expect(federal.reload.kind).to eq(1)
expect(state.reload.kind).to eq(2)
expect(municipal.reload.kind).to eq(3)
expect(particular.reload.kind).to eq(4)
end
end
end
https://relishapp.com/rspec/rspec-core/v/3-11/docs/helper-methods/let-and-let

Related

Write RSpec for include method

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

Testing Cronjobs in Rails

I have a cronjob, which moves users from one table to another according some deadline reached.
This cronjob works in rails console, but the test is red. If I test the function from this cronjob, the test is green. When I go inside the cronjob with binding.pry, it holds all necessary variables and does its work correctly.
What can be wrong?
Test:
describe 'try various methods' do
before(:each) do
Obparticipant::Participant.all.delete_all
#content = FactoryBot.create(:content, :with_department_ob, target_group: 'child', subject: 'Infos für Teilnehmer aus {ort}', message: '«{geschlecht} | Lieber | Liebe» {vorname}, du bist am {geburtsdatum} geboren.', notification_email: '{nachname}, {straße}, {plz}, {wohnland}, {bundesland}, {landesgruppe}')
germany = ::Physical::Base::Country.GERMAN
address = FactoryBot.create(:address, addressline_1: 'Sesamstraße', addressline_2: 'Kaufmannstraße', state: 'Bayern', city: 'München', zip: '80331', country_id: germany.id)
person = FactoryBot.create(:person, firstname: 'Pablo', lastname: 'Domingo', dateofbirth: Date.new(2001,2,3), gender: 'm', address_id: address.id)
#participant = FactoryBot.create(:participant, person_id: person.id)
#participant.open_todos.by_task(:account_data).each{ |t| t.complete! }
end
it 'should move recipients with a start_date of today back to content_recipients' do
person_two = FactoryBot.create(:person)
participant_two = FactoryBot.create(:participant, person_id: person_two.id, program_season_id: #participant.program_season_id)
participant_two.open_todos.by_task(:account_data).each{ |t| t.complete! }
filter = '{"program_season":"' + #participant.program_season_id.to_s + '"}'
#content.update_attributes(for_dynamic_groups: true, filter: filter, is_draft: false, delay_days: 5)
FactoryBot.create(:delayed_content_recipient, content_id: #content.id, recipient_id: participant_two.id, start_date: Date.today)
expect(#content.content_recipients.size).to eq(0)
Cronjobs.check_recipients # or #content.insert_open_recipients
expect(#content.delayed_content_recipients.size).to eq(1)
expect(#content.content_recipients.map(&:recipient_id).last).to eq(participant_two.id) # this expectation fails, when a cronjob is tested, and passes, when a function is tested
end`
Cronjob:
def self.check_recipients
contents = ::Content.published.current.by_for_dynamic_groups(true)
contents.each do |content|
content.insert_open_recipients
end
end
Function
def insert_open_recipients
search = ::SimpleParticipantSearch.new(JSON.parse(self.filter))
new_recipients = search.result.without_content(self.id)
new_recipients.each do |nr|
if self.delay_days.present?
unless self.delayed_content_recipients.map(&:recipient_id).include?(nr.id)
self.delayed_content_recipients.create(content_id: self.id, recipient_id: nr.id, start_date: Date.today + self.delay_days.days)
end
else
self.participant_recipients << nr unless errors_with_participant?(nr)
end
end
if self.delayed_content_recipients.any?
self.delayed_content_recipients.each do |recipient|
if new_recipients.map(&:id).include?(recipient.recipient_id)
if recipient.start_date == Date.today
self.delayed_content_recipients.delete(recipient)
self.participant_recipients << Obparticipant::Participant.find_by(id: recipient.recipient_id) unless errors_with_participant?(Obparticipant::Participant.find_by(id: recipient.recipient_id))
end
else
self.delayed_content_recipients.delete(recipient)
end
end
end
end
The solution I found is to test separately whether a Cronjob is run, and whether the function it calls works.
I wrote a stub for this Cronjob in the cronjobs controller rspec
it 'should call the correct method on the Cronjobs.check_recipients object' do
Cronjobs.stub(:check_recipients)
post :create, job: 'CheckRecipients'
expect(Cronjobs).to have_received(:check_recipients)
expect(response).to have_http_status(200)
end
and tested the function in the test i provided above.
it 'should move recipients with a start_date of today back to content_recipients' do
person_two = FactoryBot.create(:person)
participant_two = FactoryBot.create(:participant, person_id: person_two.id, program_season_id: #participant.program_season_id)
participant_two.open_todos.by_task(:account_data).each{ |t| t.complete! }
filter = '{"program_season":"' + #participant.program_season_id.to_s + '"}'
#content.update_attributes(for_dynamic_groups: true, filter: filter, is_draft: false, delay_days: 5)
FactoryBot.create(:delayed_content_recipient, content_id: #content.id, recipient_id: participant_two.id, start_date: Date.today)
expect(#content.content_recipients.size).to eq(0)
#content.insert_open_recipients
expect(#content.delayed_content_recipients.size).to eq(1)
expect(#content.content_recipients.map(&:recipient_id).last).to eq(participant_two.id)
end

Conditional filters for contexts in rspec?

Is there a way to set conditional filters for contexts in rspec?
I have a context with variables that are only valid if a particular variable != 1, so I wanted to use that as the filter for that context's test. I can't have the filter on the test itself because I'm using said variables to set the context for that test (see below for ideal state):
context 'if interval is not 1 every non interval day', :if => Reminders::INTERVAL != 1 do
num = rand(0..100)
non_limbo = num + num/(Reminders::INTERVAL - 1) + 1
let!(:evaluation) { create(:evaluation, created_at: non_limbo.days.ago) }
it 'doesn't send an email' do
expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(0)
end
end
You can surround the whole context in a condition:
if Reminders::INTERVAL != 1
context 'if interval...' do
# ...
end
end

Elasticsearch: Undefined method 'perform' for #<Indexer...> [test env]

I'm writing tests for my app (in Rspec) and lost a whole day trying to figure out where did I make a mistake with Capybara. I noticed that when a action is called (change_status), the following errors occur in the test.log:
undefined method `perform' for #<Indexer:0x000000095d9680>
and the changes are not saving. I have no idea where to start looking. I'm guessing it's a problem with Elasticsearch. Here's the whole action from the controller:
def change_status
product = Product.find_by(id: params[:id])
product.update_attribute(:status, params[:product][:status])
if params[:product][:status] == "accepted"
kind = Kind.where(name: "title").first
product.author.change_points({points: 2, kind: kind.id})
elsif params[:product][:status] == "rejected"
kind = Kind.where(name: "title").first
product.author.change_points({points: 1, kind: kind.id})
product.author.change_points({points: -3, kind: kind.id})
end
user_id = product.author_id
products = Product.where(author_id: user_id)
if products.where(status: "accepted").count == 3
user = User.find_by(id: user_id)
user.update_attribute(:approved, true)
kind = Kind.where(name: "title").first
user.change_points({points: 1, kind: kind.id})
redirect_to products_path
else
redirect_to action: "pending_products", id: user_id
end
end
Any suggestion? Besides this, the log is useless...
EDIT:
class Indexer
include Sidekiq::Worker
sidekiq_options queue: 'elasticsearch', retry: false, backtrace: true
Logger = Sidekiq.logger.level == Logger::DEBUG ? Sidekiq.logger : nil
Client = Elasticsearch::Client.new host: (ENV['ELASTICSEARCH_URL'] || 'http://localhost:9200'), logger: Logger
def perform(operation, klass, record_id, options={})
logger.debug [operation, "#{klass}##{record_id} #{options.inspect}"]
case operation.to_s
when /index|update/
record = klass.constantize.find(record_id)
record.__elasticsearch__.client = Client
record.__elasticsearch__.__send__ "#{operation}_document"
when /delete/
Client.delete index: klass.constantize.index_name, type: klass.constantize.document_type, id: record_id
else raise ArgumentError, "Unknown operation '#{operation}'"
end
end
end

Issue with Faker gem

I installed Fabrication and Faker in my Rails 4 project
I created a fabrarication object:
Fabricator(:course) do
title { Faker::Lorem.words(5) }
description { Faker::Lorem.paragraph(2) }
end
And I'm calling the Faker object within my courses_controller_spec.rb test:
require 'spec_helper'
describe CoursesController do
describe "GET #show" do
it "set #course" do
course = Fabricate(:course)
get :show, id: course.id
expect(assigns(:course)).to eq(course)
end
it "renders the show template"
end
end
But for some reason, the test failes at line 6:
course = Fabricate(:course)
the error message is:
Failure/Error: course = Fabricate(:course)
TypeError:
can't cast Array to string
Don't know exactly why this is failing. Has anyone experienced the same error message with Faker?
The return from words(5) is an array, not a string. You should do this:
title { Faker::Lorem.words(5).join(" ") }
Not sure if this was available back then, but another solution would be to give it a sentence instead of a word:
From the repo
def sentence(word_count = 4, supplemental = false, random_words_to_add = 6)
words(word_count + rand(random_words_to_add.to_i).to_i, supplemental).join(' ').capitalize + '.'
end
So you could do:
Faker::Lorem.sentence(5, false, 0)

Resources