Write rspec for rake task - ruby-on-rails

i have task /lib/crawler.rake like that:
namespace :crawler do
area_names = Dir[Rails.root.join("lib", "crawler", "*.rb")].map do |file_name|
File.basename(file_name, ".rb")
end
area_names.each do |area_name|
task area_name.to_sym => :environment do
logger = Logger.new("log/crawl_#{area_name}.log")
# do something
parallel_results = crawler.crawl
mutex = Mutex.new
Parallel.each(parallel_results, in_threads: [parallel_results.count, CRAWL_CONFIG["building_thread_max"]].min) do |pages|
begin
# do something
rescue => e
# do something
raise e
end
end
Availability.update_by_grounds_and_time
end
end
end
Logic here, if everything's ok after parallel, we'll call update_by_grounds_and_time method to update Availability; if get error, we'll stop action and raise error.
So i want to write rspec to test for these cases, i want to mock/stub output of task here (pass or raise error) and check did we call update_by_grounds_and_time method?
Can we don't need invoke really task? can we use Rspec Mock?
Can you help me!
Thank

What I usually do in these cases is I extract the meat into a separate class/service-object/whatever, which is much easier to test. The rake task then becomes just an invoker of that object and, as such, doesn't need to be tested.

If it is defined in Rakefile, try this:
require 'rake'
RSpec.describe "Rake Tasks" do
before do
file, path = Rake.application.find_rakefile_location
Rake.load_rakefile("#{path}/#{file}")
end
it "should invoke some tasks" do
expect(Availability).to receive(:update_by_grounds_and_time)
Rake.application["crawler:#{area_name}"].invoke
end
end
If it is defined in foo.rake, then try this one:
require 'rake'
RSpec.describe "Rake Tasks" do
before do
Rake.application.rake_require('/path/to/lib/tasks/foo')
end
it "should invoke some tasks" do
expect(Availability).to receive(:update_by_grounds_and_time)
Rake.application["crawler:#{area_name}"].invoke
end
end
UPDATE (error case)
For example
# foo.rake
Parallel.each(parallel_results, in_threads: [parallel_results.count, CRAWL_CONFIG["building_thread_max"]].min) do |pages|
begin
foo = Foo.new
foo.bar
# do something else
rescue => e
# do something
raise e
end
end
# foo_spec.rb
require 'rake'
RSpec.describe "Rake Tasks" do
before do
Rake.application.rake_require('/path/to/lib/tasks/foo')
end
it "should not call Availability#update_by_grounds_and_time if error raised" do
allow_any_instance_of(Foo).to receive(:bar).and_raise(StandardError)
expect(Availability).to_not receive(:update_by_grounds_and_time)
expect { Rake.application["crawler:#{area_name}"].invoke }.to raise_error(StandardError)
end
end

Related

Rails 4: PG::InFailedSqlTransaction when testing a rake task with RSpec

I am currently trying to test a rake task with RSpec.
My Rails version is 4.2.4 and rspec-rails version is 3.3.2.
I've have the following in rails_helper.rb:
ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
...
config.use_transactional_fixtures = false
config.infer_spec_type_from_file_location!
...
end
and then spec/support/tasks.rb:
require 'rake'
module TaskExampleGroup
extend ActiveSupport::Concern
included do
let(:task_name) { self.class.top_level_description.sub(/\Arake /, "") }
let(:tasks) { Rake::Task }
# Make the Rake task available as `task` in your examples:
subject(:task) { tasks[task_name] }
end
end
RSpec.configure do |config|
# Tag Rake specs with `:task` metadata or put them in the spec/tasks dir
config.define_derived_metadata(file_path: %r{/spec/tasks/}) do |metadata|
metadata[:type] = :task
end
config.include TaskExampleGroup, type: :task
config.before(:suite) do
Rails.application.load_tasks
end
end
my spec/support/database_cleaner.rb
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.append_after(:each) do
DatabaseCleaner.clean
end
end
and finally, the spec:
require 'rails_helper'
describe "rake some:my_task", type: :task do
# This test passes
it 'preloads the Rails environment' do
expect(task.prerequisites).to include 'environment'
end
# This test fails
it 'creates AnotherModel' do
my_hash = {foo => 'bar'}
allow(MyClient::Event).to receive(:list).and_return(my_hash)
expect { task.execute }.not_to raise_error
expect(AnotherModel.count).to eq(1)
end
end
The problem is that for some reason, executing this code results in the following error:
Failure/Error: AnotherModel.count
ActiveRecord::StatementInvalid:
PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block
The rake task looks like this:
namespace :some do
desc 'Parse stream'
task my_task: :environment do |_t|
cint_events['events'].each do |event|
begin
events = MyClient::Event.list(some_hash)
events.each do |event|
if some_condition
# The test should check whether this object gets created
AnotherModel.first_or_create_by(foo: some_hash['foo'])
end
end
rescue => e
# Log errors
end
end
end
end
I've tried running:
RAILS_ENV=test rake db:drop db:create db:migrate
and then running the spec again but I keep getting the aforementioned error. What might this be caused by?
Thanks in advance!
You configured your tests to run in a database transaction, which is a good thing.
But within your rake task you just eat up all errors that appear with:
rescue => e
# Log errors
end
However, certain errors may still cause you transaction to fail and rollback. So my guess is, that some severe error is happening the first time you do a call to the database (For example, the column foo is not known to the database). After that, it catches the error and you are adding a statement (AnotherModel.count) to the already aborted transaction, which fails.
So a good place to start is to check what the value of e.message is in your rescue block.
Also note:
It is never a good idea to rescue all errors blindly, and almost always leads to strange and unexpected behaviours.
This error seems to occur when in a test environment and your SQL query via ActiveRecord doesn't recognize a field in your query. In other words you have a scope or are trying to return some ActiveRecord relation with a bad database column name.
See this related post:
ActiveRecord::StatementInvalid: PG InFailedSqlTransaction

Rake does not swallow RSpec message output

My spec provides coverage like I had hoped, however, the following 2 messages are displaying in the rspec output:
rake resque:scheduler
rake environment resque:work
How do I swallow these during spec runs so they do not screw up my nyancat formatter?
Spec
describe 'database rake task' do
include_context 'rake'
let(:task_paths) { ['tasks/heroku'] }
before do
invoke_task.reenable
end
# rubocop:disable all
describe 'myapp:heroku' do
context ':setup' do
context ':secrets' do
let(:task_name) { 'myapp:heroku:setup:secrets' }
context 'with env' do
it 'works' do
expect(File).to receive(:exist?).with('.env').and_return(true)
expect_any_instance_of(Object).to receive(:system).with('heroku config:push --remote production').and_return(true)
expect { invoke_task.invoke }.to output(
"\nUpdating Secrets for production\n"
).to_stdout
end
end
context 'without env' do
it 'works' do
expect(File).to receive(:exist?).with('.env').and_return(false)
expect { invoke_task.invoke }.to raise_error("\nYou are missing the .env file\n").and(output(
"\nUpdating Secrets for production\n"
).to_stdout)
end
end
end
end
end
describe 'schedule_and_work' do
let(:task_name) { 'schedule_and_work' }
context 'with process fork' do
it 'works' do
expect(Process).to receive(:fork).and_return(true)
expect_any_instance_of(Object).to receive(:system).with('rake environment resque:work', {}).and_return(true)
expect(invoke_task.invoke).to be
end
end
context 'without process fork' do
it 'works' do
expect(Process).to receive(:fork).and_return(false)
expect(Process).to receive(:wait).and_return(true)
expect_any_instance_of(Object).to receive(:system).with('rake resque:scheduler', {}).and_return(true)
expect(invoke_task.invoke).to be
end
end
end
# rubocop:enable all
end
Rake Task
namespace :myapp do
namespace :heroku do
namespace :setup do
desc 'modify secrets'
task :secrets do
puts "\nUpdating Secrets for production\n"
raise "\nYou are missing the .env file\n" unless File.exist?('.env')
system('heroku config:push --remote production')
end
end
end
end
# Run resque scheduler on 2 free dynos
# https://grosser.it/2012/04/14/resque-scheduler-on-heroku-without-extra-workers/
task :schedule_and_work do
if Process.fork
sh 'rake environment resque:work'
else
sh 'rake resque:scheduler'
Process.wait
end
end
You can use the following test helper method
require 'stringio'
def silent_warnings
old_stderr = $stderr
$stderr = StringIO.new
yield
ensure
$stderr = old_stderr
end
-- Temporarily disabling warnings in Ruby | Virtuous Code
And wrap the invoking of a Rake task with silent_warnings method; like so
silent_warnings do
expect { invoke_task.invoke }.to output(
"\nUpdating Secrets for production\n"
).to_stdout
end
However, use it sparingly, since it swallow all warnings (printed to $stdout) produced within the block code, making it harder to debug in the future.
Also, you can wrap silent_warnings around all tests within an RSpec describe block using then around hook; e.g.
around(:example) do |example|
silent_warnings { example.run }
end
Again, use it sparingly

How do I test the rescue block of a method with rspec mocks 3.3

Help me make this test pass:
Here is an example of some rspec code,
class User
attr_accessor :count
def initialize
#count = 0
end
# sometimes raises
def danger
puts "IO can be dangerous..."
rescue IOError => e
#count += 1
end
#always raises
def danger!
raise IOError.new
rescue IOError => e
#count += 1
end
end
describe User do
describe "#danger!" do
it "its rescue block always increases the counter by one" do
allow(subject).to receive(:'danger!')
expect {
subject.danger!
}.to change(subject, :count).by(1)
end
end
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow(subject).to receive(:danger).and_raise(IOError)
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
end
I've also created a fiddle with these tests in it, so you can just make them pass. Please help me test the rescue block of a method!
Background:
My original question went something like this:
I have a method, like the following:
def publish!(resource)
published_resource = resource.publish!(current_project)
resource.update(published: true)
if resource.has_comments?
content = render_to_string partial: "#{ resource.class.name.tableize }/comment", locals: { comment: resource.comment_content_attributes }
resource.publish_comments!(current_project, published_resource.id, content)
end
true
rescue Bcx::ResponseError => e
resource.errors.add(:base, e.errors)
raise e
end
And I want to test that resource.errors.add(:base, e.errors) is, in fact, adding an error to the resource. More generally, I want to test the rescue block in a method.
So I'd like to write code like,
it "collects errors" do
expect{
subject.publish!(training_event.basecamp_calendar_event)
}.to change(training_event.errors.messages, :count).by(1)
end
Of course, this raises an error because I am re-raising in the rescue block.
I've seen a few answers that use the old something.stub(:method_name).and_raise(SomeException), but rspec complains that this syntax is deprecated. I would like to use Rspec Mocks 3.3 and the allow syntax, but I'm having a hard time.
allow(something).to receive(:method_name).and_raise(SomeException)
would be the new allow syntax. Check out the docs for reference.
I was misunderstanding what the allow syntax is actually for. So to make my example specs pass, I needed to do this:
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow($stdout).to receive(:puts).and_raise(IOError) # <----- here
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
This thing that I'm stubing is not the method, or the subject, but the object that might raise. In this case I stub $stdout so that puts will raise.
Here is another fiddle in which the specs are passing.

Rails/RSpec toggle cache for a single test

So in my app I can disable the cache for all tests, which would be ideal, but apparently there are a number of legacy tests that rely on the cache being functional. Is there a way to enable the Rails cache for a single RSpec test?
Something like:
before(:each) do
#cache_setting = Rails.cache.null_cache
Rails.cache.null_cache = true
end
after(:each) do
Rails.cache.null_cache = #cache_setting
end
it 'does not hit the cache' do
...
end
in spec_helper.rb
RSpec.configure do |config|
config.before(:example, disable_cache: true) do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::NullStore.new)
end
config.after(:example, disable_cache: true) do
allow(Rails).to receive(:cache).and_call_original
end
end
in xxx_spec.rb
RSpec.describe "a group without matching metadata" do
it "does not run the hook" do
puts Rails.cache.class
end
it "runs the hook for a single example with matching metadata", disable_cache: true do
puts Rails.cache.class
end
end
https://www.relishapp.com/rspec/rspec-core/docs/hooks/filters

How to test (Bean)Stalker using rspec?

I've this file called jobs.rb
require File.expand_path("../environment", __FILE__)
job "do.stuff" do |args|
$value = 10
end
Then I've this spec file called jobs_spec.rb
require File.expand_path("../../spec_helper", __FILE__)
describe "some beanstalk jobs" do
it "should work" do
# What to do here?
$value.should eq(10)
end
end
How do I test the $value variable?
You have to run beanstalkd on the machine you are running the test on.
require "Stalker"
describe Stalker, "job" do
before :all do
# Make sure beanstalkd is running
if `pgrep beanstalkd` == ""
raise "PRECONDITION NOT MET: beanstalkd not running"
end
module Stalker
def log(msg); end
def log_error(msg); end
end
end
before :each do
Stalker.clear!
$result = -1
$handled = false
end
it "enqueue and work a job" do
val = rand(999999)
Stalker.job('my.job') { |args| $result = args['val'] }
Stalker.enqueue('my.job', :val => val)
Stalker.prep
Stalker.work_one_job
val.should == $result
end
end
I'd check out how the library itself runs it's test:
https://github.com/adamwiggins/stalker/blob/master/test/stalker_test.rb
Keep in mind that this is using Test::Unit, however you can apply the same techniques in Rspec.

Resources