How would you test this class? Would you integrate it with Resque and check that the job gets put in the queue or would you mock it, and if you would mock it how would you avoid just duplicating the code like shown in example spec.
class BookingReminderEnqueuer
def initialize(user, booking, worker = BookingReminder)
#user, #booking, #worker = user, booking, worker
end
def enqueue
Resque.enqueue_at(
remind_user_at,
#worker,
booking_id: #booking.id,
user_id: #user.id
) if #booking.remind_user?
end
private
def remind_user_at
#booking.start_time - 6.hours
end
end
require 'spec_helper'
describe BookingReminderEnqueuer
describe "#enqueue" do
context "when the user should have a reminder" do
it "enqueues the reminder" do
booking.stub(:remind_user?) { true }
Resque.should_receive(:enqueue_at).with(
booking.start_time - 6.hours,
BookingReminder,
booking_id: booking.id,
user_id: user.id
).once
booking_reminder_enqueuer.enqueue
end
end
context "when the user shouldn't have a reminder" do
it "does not enqueue the reminder" do
booking.stub(:remind_user?) { false }
Resque.should_not_receive(:enqueue_at)
end
end
end
end
I would use a gem like fakeredis to create a test redis db. This will create an isolated in-memory Redis instance for testing that can be cleared around test runs. Then you can verify that the job is enqueued properly with the right parameters vs. mocking the enqueue method (since getting the enqueue parameters to work properly is the heart of this method.
# Gemfile
group :test do
gem "rspec"
gem "fakeredis", :require => "fakeredis/rspec"
end
# Spec
describe BookingReminderEnqueuer
describe "#enqueue" do
context "when the user should have a reminder" do
it "enqueues the reminder" do
booking.stub(:remind_user?) { true }
booking_reminder_enqueuer.enqueue
resque_job = Resque.peek(:queue_name)
resque_job.should be_present
# add assertions about job content/scheduling here
end
end
...
end
end
Related
I have a worker method which looks like this
class ScoreWorker
include Sidekiq::Worker
sidekiq_options queue: 'score_worker'
def perform(scores, notification, devices)
threads = []
scores.each do |gg|
threads << Thread.new { PushNotificationService.new(notification, devices).deliver }
end
threads.each { |thr| thr.join }
end
I am writing an rspec test for my worker
describe ScoreWorker do
self.use_transactional_fixtures
before(:all) do
Rails.cache.clear
end
describe '.perform' do
self.use_transactional_fixtures
context 'create' do
it 'creates notification' do
job = create(:job)
notification = create(:notification)
devices = create(:device)
scores = ScoringService.new(job, 25).score
worker = ScoreWorker.new
expect do
worker.perform(scores, notification, devices)
end.to change(Notification, :count).by(1)
end
end
end
end
But whenever i run my tests, it keeps failing as rspec doesnt seem to support threading. So my job, notification and devices object are all empty.
How can i run my tests when using ruby threads in rspec
I'm quite new to Rspec testing in Rails and I'm trying to test the following:
class Event < ApplicationRecord
def host_details
query ||= User.where(id: self.user_id)
end
def hosting_company
company = host_details.first.company
end
end
I have the following spec but I'm not sure exactly what I'm supposed to be expecting back for the results of the host_details method. I'm using FactoryGirl.
describe "gets information about the host of an event" do
before(:each) do
#event = build(:event)
end
context "host_details" do
it "querys the event data and finds the associated user" do
end
end
context "hosting_company" do
it "gets the company that is hosting the event" do
expect(#event.hosting_company).to eq "Attnd"
end
end
end
Here is my setup:
airbrake.rb
require 'airbrake'
Airbrake.configure do |c|
c.ignore_environments = [:test, :development]
c.project_id = ENV['PROJECT_ID']
c.project_key = ENV['PROJECT_KEY']
end
use Airbrake::Rack::Middleware
spec_helper.rb
RSpec.configure do |config|
config.before(:suite) do
FactoryGirl.reload
FactoryGirl.define do
to_create { |instance| instance.save }
end
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
Airbrake.configure(:test) do |c|
c.project_id = ENV['PROJECT_ID']
c.project_key = ENV['PROJECT_KEY']
end
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
config.include FactoryGirl::Syntax::Methods
end
worker_test_spec.rb
require 'spec_helper'
RSpec.describe NotificationWorker do
it "perform should call Airbrake#notify" do
anotification_worker = LNotificationWorker.new
airbrake_notification_worker.perform("some error message"))
expect(Airbrake).to receive(:notify).with("some error message")
end
end
I call Airbrake#notify in other (non-Sidekiq) tests, and they find the appropriate ENV variables just fine.
Yet if I run the above Sidekiq test with the above setup, I get the following error:
Airbrake::Error:
the 'default' notifier isn't configured
But if I change the Airbrake config in spec_helper.rb to:
Airbrake.configure do |c|
c.project_id = ENV['PROJECT_ID']
c.project_key = ENV['PROJECT_KEY']
end
the ENV keys are able to be found in the tests. Why is this?
When you say Airbrake.configure(:test), it does not mean "configure Airbrake for the test RAILS_ENV". Rather :test creates a non-default named notifier. Then you can send specific notifications to that notifier by saying Airbrake.notify("oops", {time: Time.now}, :test). But that is not about development/test/production, it is about categorizing your notifications.
So the problem is that you have configured a notifier named test, but you have not yet configured one named default, and default is what Airbrake wants to use when you don't tell it otherwise. That's why your spec passes when you say simply Airbrake.configure { ... }.
I have the following test setup for a sidekiq test using the fake testing in https://github.com/mperham/sidekiq/wiki/Testing.
spec_helper.rb
require 'sidekiq/testing'
Sidekiq::Testing.fake!
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
config.before(:suite) do
FactoryGirl.reload
FactoryGirl.define do
to_create { |instance| instance.save }
end
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
Airbrake.configure do |c|
c.project_id = ENV['AIRBRAKE_PROJECT_ID']
c.project_key = ENV['AIRBRAKE_PROJECT_KEY']
end
end
config.before(:each, job: true) do
Sidekiq::Worker.clear_all #make sure jobs don't linger between tests
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
config.include FactoryGirl::Syntax::Methods
end
notification_spec.rb
require 'spec_helper'
RSpec.describe NotificationWorker do
it "perform should call Airbrake#notify", job: true do
notification_worker = NotificationWorker.new
message = "This is your error message"
expect { notification_worker.perform(message) }.to change(NotificationWorker.jobs, :size).by(1)
end
end
notification_worker.rb
class NotificationWorker
include Sidekiq::Worker
sidekiq_options queue: :high
def perform(message)
Airbrake.notify(message)
end
end
Yet, why do I receive the following error message:
Failure/Error: expect { notification_worker.perform(message) }.to change(NotificationWorker.jobs, :size).by(1)
expected #size to have changed by 1, but was changed by 0
It seems as if the jobs array should be incremented by 1. What is going on? Is it a threading issue caused by the interaction between RSpec and Database Cleaner?
Because it's calling the perform method directly and not Sidekiq's API.
Try NotificationWorker.perform_async(message)
I am running rspec tests on a catalog object from within a Ruby app, using Rspec::Core::Runner::run:
File.open('/tmp/catalog', 'w') do |out|
YAML.dump(catalog, out)
end
...
unless RSpec::Core::Runner::run(spec_dirs, $stderr, out) == 0
raise Puppet::Error, "Unit tests failed:\n#{out.string}"
end
(The full code can be found at https://github.com/camptocamp/puppet-spec/blob/master/lib/puppet/indirector/catalog/rest_spec.rb)
In order to pass the object I want to test, I dump it as YAML to a file (currently /tmp/catalog) and load it as subject in my tests:
describe 'notrun' do
subject { YAML.load_file('/tmp/catalog') }
it { should contain_package('ppet') }
end
Is there a way I could pass the catalog object as subject to my tests without dumping it to a file?
I am not very clear as to what exactly you are trying to achieve but from my understanding I feel that using a before(:each) hook might be of use to you. You can define variables in this block that are available to all the stories in that scope.
Here is an example:
require "rspec/expectations"
class Thing
def widgets
#widgets ||= []
end
end
describe Thing do
before(:each) do
#thing = Thing.new
end
describe "initialized in before(:each)" do
it "has 0 widgets" do
# #thing is available here
#thing.should have(0).widgets
end
it "can get accept new widgets" do
#thing.widgets << Object.new
end
it "does not share state across examples" do
#thing.should have(0).widgets
end
end
end
You can find more details at:
https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#define-before(:each)-block