Rspec doesn't clear factory objects after some specs - ruby

Im using clear ruby (ruby 2.3.5) without Rails and ActiveRecord and trying to write specs for some service. And if I run only part of file (including problem spec), it passed. If I run the whole file - the problem spec is failed.
By the way, I already use rspec/retry, and it settings for 20 times retry (And I see in logs, that the problem spec is failed in all 20 times).
ballot_spec.rb
# frozen_string_literal: true
require 'spec_helper'
require 'byebug'
RSpec.describe Ballot do
describe '#run' do
let(:kingdoms) { build_list(:kingdom, 6) }
let(:service) { described_class.new(kingdoms) }
before { allow(service).to receive(:hold_ballot) }
it 'holds ballot if cannot_finish_ballot? returns true' do
allow(service).to receive(:cannot_finish_ballot?).and_return(true)
service.run
expect(service).to have_received(:hold_ballot).at_least(:once)
end
it "doesn't hold ballot if cannot_finish_ballot? returns false" do
allow(service).to receive(:cannot_finish_ballot?).and_return(false)
service.run
expect(service).not_to have_received(:hold_ballot)
end
it 'returns Struct object' do
expect(service.run.class.superclass).to be Struct
end
end
describe '#hold_ballot' do
let(:all_kingdoms) { build_list(:kingdom, rand(2..10)) }
let(:pretendents) { all_kingdoms.first(rand(2..all_kingdoms.count)) }
let(:message) { build(:message, from: all_kingdoms.sample, to: all_kingdoms.sample) }
let(:expected_message_count) { pretendents.count * all_kingdoms.count }
let(:new_service) { described_class.new(pretendents) }
before do
allow(Message).to receive(:new).and_return(message)
allow(message).to receive(:send)
new_service.send('hold_ballot')
end
\/\/ THE PROBLEM IS IN THIS SPEC \/\/
it 'prepares messages to all existed kingdoms from every pretendent' do
expect(Message).to have_received(:new).exactly(expected_message_count).times
end
it 'only 6 of messages will be selected to be sent' do
expect(message).to have_received(:send).exactly(6).times
end
it 'resets Kingdoms' do
allow(Kingdom).to receive(:reset)
new_service.run
expect(Kingdom).to have_received(:reset).at_least(:once)
end
end
end
when I run rspec spec/services/ballot_spec.rb:26 (for test whole method '#hold_ballot') every spec is passed.
when I run rspec spec/services/ballot_spec.rb, I got failure:
1) Ballot#hold_ballot prepares messages to all existed kingdoms from every pretendent
Failure/Error: expect(Message).to have_received(:new).exactly(expected_message_count).times
(Message (class)).new(*(any args))
expected: 4 times with any arguments
received: 260 times with any arguments
when I use byebug, I see, that class Kingdom has dozens of objects, but variables "all_kingdoms" and "pretendents" contains not more than 10 objects.
So the once rootcause I can see here - rspec doesn't clear kingdoms, created during testing method '#run', but how to push it destroy them? I don't use ActiveRecord, don't use Rails, so cannot tun "reload!" or "destroy" explicitly. Tried to run GC.start, but it doesn't help. What can I do? Many thanks! )
spec_helper.rb
# frozen_string_literal: true
require './models/kingdom.rb'
require './models/message.rb'
require './services/message_compose.rb'
require 'factory_bot'
require 'ffaker'
require 'capybara'
require 'rspec/retry'
require './spec/factories/kingdom.rb'
require './spec/factories/message.rb'
RSpec.configure do |config|
config.verbose_retry = true
config.display_try_failure_messages = true
config.around :each do |ex|
ex.run_with_retry retry: 20 unless ex.run_with_retry
end
config.include FactoryBot::Syntax::Methods
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
end

Related

Uninitialized constant NameError in Rspec

When I run rails c, I can call the following class and the method works:
test = SlackService::BoardGameNotifier
test.create_alert("test")
>>method works
I'm trying to set this up in rspec like this:
require 'spec_helper'
require 'slack-notifier'
RSpec.describe SlackService::BoardGameNotifier do
describe '#notify' do
#notifier = SlackService::BoardGameNotifier
it 'pings Slack' do
error = nil
message = "test"
expect(notifier).to receive(:ping).with(message)
notifier.send_message()
end
end
end
But I keep getting the error:
NameError:
uninitialized constant SlackService
Does this have to do with how I set up the module?
My current setup:
slack_service/board_game_notifier.rb
module SlackService
class BoardGameNotifier < BaseNotifier
WEBHOOK_URL = Rails.configuration.x.slack.url
DEFAULT_OPTIONS = {
channel: "board-games-channel",
text: "board games alert",
username: "bot",
}
def create_alert(message)
message #testing
end
end
end
slack_service/base_notifier.rb
module SlackService
class BaseNotifier
include Singleton
def initialize
webhook_url = self.class::WEBHOOK_URL
options = self.class::DEFAULT_OPTIONS
#notifier = Slack::Notifier.new(webhook_url, options)
end
def self.send_message
message = instance.create_alert("test")
instance.notify(message)
end
def notify(message)
#notifier.post blocks: message
end
end
end
Add this to your spec_helper.rb
# spec_helper.rb
ENV["RAILS_ENV"] ||= "test"
require File.expand_path("../config/environment", __dir__)
When running RSpec, Rails doesn't automatically boot up, and therefore doesn't automatically load all the libraries.
Also, I'd suggest creating a .rspec in your app's root folder with the following lines so that spec_helper is automatically loaded for all your RSpec tests:
# .rspec
--format documentation
--color
--require spec_helper
I would use the described_class from Rspec
require 'spec_helper'
require 'slack-notifier'
RSpec.describe ::SlackService::BoardGameNotifier do
describe '#notify' do
it 'pings Slack' do
error = nil
message = "test"
expect(described_class).to receive(:ping).with(message)
notifier.send_message()
end
end
end

Why can't RSpec find the Airbrake env keys in a test involving Sidekiq when I specify environment?

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 { ... }.

Why is this Sidekiq fake test not increasing the size of the jobs array?

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)

prepare called on a closed database rails rspec

I am trying to integrate BDD in my rails app via rspec. I am using guard and spork-rails to speed the monitoring process up. I am getting this error:
An error occurred in an after hook
ActiveRecord::StatementInvalid: ArgumentError: prepare called on a closed database:
rollback transaction occurred at /Users/davidhahn/.rvm/gems/ruby-1.9.3-p286/gems/sqlite3-1.3.6/lib/sqlite3/database.rb:91:in initialize
I ran rake db:test:prepare and it ran without any errors. Since I'm using sqlite I checked to make sure that the user_man_test.sqlite file was in db/. My test is just a simple integration test:
require 'spec_helper'
describe "Authentication" do
describe "Login Page" do
it "should have the h1 'Welcome to User Management'" do
visit '/log_in'
page.should have_selector('h1', text: 'Welcome to User Management')
end
end
describe "Login" do
before { visit '/log_in' }
describe "with invalid information" do
before { click_button "Login" }
it { should have_selector('h1', text: 'Welcome to User Management') }
it { should have_selector('div.alert.alert-error', text: 'Invalid') }
end
end
end
My spec_helper.rb looks like:
require 'rubygems'
require 'spork'
Spork.prefork do
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
end
end
Spork.each_run do
# This code will be run each time you run your specs.
end
~
Thanks for the help
Instead of spending hours trying to configure spork properly, I would advise you to look at Zeus. I'm sorry if this doesn't exactly answer your question, but I spent almost one year with spork, had tons of trouble configuring every time that I added a new test gem, and when I made the switch, everything magically worked (and in my experience, Zeus' performance is much better than Spork).

MinitTest Spec confusion over second parameter

I'm new to BDD and I'm trying to play with MiniTest Spec:
require 'minitest/spec'
require 'minitest/autorun'
class Car
attr_accessor :type
def initialize(type)
#type = 'petrol'
end
end
describe Array do
it "must be diesel" do
Car.new('diesel').type.must_equal 'diesel'
end
end
This is great - running this I get the following output:
Failure:
test_0001_must_be_diesel(ArraySpec):
Expected "diesel", not "petrol".
Which makes sense - "Expected diesel, not petrol" is exactly what I'm expecting. If I place a second parameter in my must_equal statement (which I assume is the message I want to return on failure) - I get an odd result:
require 'minitest/spec'
require 'minitest/autorun'
class Car
attr_accessor :type
def initialize(type)
#type = 'petrol'
end
end
describe Array do
it "must be diesel" do
Car.new('diesel').type.must_equal 'diesel', 'it must be a diesel'
end
end
Running this I get:
1) Failure:
test_0001_must_be_diesel(ArraySpec):
it must be a diesel.
Expected "petrol", not "diesel".
For some reason, now it says "Expected petrol not diesel". So it seems that adding in what I assume is a message parameter (as it is in the Test Unit version) is making the assertion flip around.
Is the idea of a message param in the speccing framework void?
There's been some inconsistency in how MiniTest::Spec handles multiple arguments. It appears to have been fixed in https://github.com/seattlerb/minitest/commit/cd4fe89b0057edc2258876ad8c5f5e7e722f73c2.
Just install the latest version of MiniTest from RubyGems and you should be fine:
gem install minitest
Then add this on the top of your file to use the gem:
gem 'minitest'

Resources