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

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

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 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)

Rspec + Factory girl (without rails!)

I am using Rspec with selenium-webdriver gem to test a web app. And I wanted to unclude factories in my tests to emulate users and not to create a user manually each time.
So, I made gem install factory_girl, added required lined in my spec_helper, created a factory and included some lines in my spec file. And when running the test I get an error
Failure/Error: FactoryGirl.build(:user)
NameError:
uninitialized constant User
Here is my spec_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
My factories.rb file:
FactoryGirl.define do
factory :user do
name "testuser"
password "freestyle"
inventory true
end
end
And my test_spec file:
require "json"
require "selenium-webdriver"
require "rspec"
require "factory_girl"
FactoryGirl.find_definitions
include RSpec::Expectations
describe "MallSpec" do
before(:all) do
FactoryGirl.build(:user)
#driver = Selenium::WebDriver.for :firefox
#base_url = "http://localhost:9000/"
#accept_next_alert = true
#driver.manage.timeouts.implicit_wait = 30
#driver.manage.window.resize_to(1301, 744)
#verification_errors = []
end
My spec_file is in the root dir of the project. my factories.rb file is in /spec dir as well as the test_spec.rb itself.
Can anyone help me with this issue or point what i am doing wrong?
If you don't actually have a User class but you want to use FactoryGirl to generate the attributes, you can override the class:
require "ostruct"
FactoryGirl.define do
factory :user, class: OpenStruct do
name "testuser"
password "freestyle"
inventory true
# This isn't necessary, but it will prevent FactoryGirl from trying
# to call #save on the built instance.
to_create {}
end
end
You can then use attributes_for if you just want a Hash, or create if you want an object that responds to methods like name.
You can use a library like Hashie::Mash if you want to generate JSON for use in your API:
factory :user, class: Hashie::Mash do
# ...
end
# In your tests:
user_json = create(:user).to_json
And when running the test I get an error Failure/Error:
FactoryGirl.build(:user) NameError: uninitialized constant User
Your User class has to be defined. The following is a test with no User class defined:
require 'factory_girl'
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
FactoryGirl.define do
factory :user do
name 'Alice'
age 10
end
end
describe "MallSpec" do
let(:test_user) { FactoryGirl.build(:user) }
describe "user's name" do
it "equals 'Alice'" do
expect(test_user.name).to eq('Alice')
end
end
end
--output:--
$ rspec 1.rb
F
Failures:
1) MallSpec user's name equals 'Alice'
Failure/Error: let(:user) { FactoryGirl.build(:user) }
NameError:
uninitialized constant User
...
Adding a definition for the User class:
require 'factory_girl'
#====NEW CODE=====
class User
attr_accessor :name, :age
end
#=================
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
FactoryGirl.define do
factory :user do
name 'Alice'
age 10
end
end
describe "MallSpec" do
let(:test_user) { FactoryGirl.build(:user) }
describe "user's name" do
it "equals 'Alice'" do
expect(test_user.name).to eq('Alice')
end
end
end
--output:--
$ rspec 1.rb
.
Finished in 0.0024 seconds (files took 0.35197 seconds to load)
1 example, 0 failures
I expect that the factory() method here:
factory :user do
name 'Alice'
age 10
end
...does something like this:
def factory(model_name)
target_class = constant_get(model_name.capitalize)
...in order to construct a real instance of the User class. In other words, factory_girl constructs instances of classes that already exist in your app--factory_girl does not mock a class.

Sinatra: Log noise when running rspec tests

New to Sinatra; I'm running some rspec tests but getting a bunch of unwanted noise in the logs. How do I get rid of the excessive noise in the logs? I've double checked that the environment is set to :test, which means logger level should be set to WARN instead of DEBUG.
spec_helper:
require "./app"
require "sinatra"
require "rspec"
require "rack/test"
require "database_cleaner"
require "factory_girl"
set :environment, :test
FactoryGirl.definition_file_paths = %w{./factories ./test/factories ./spec/factories}
FactoryGirl.find_definitions
RSpec.configure do |config|
config.include Rack::Test::Methods
config.include FactoryGirl::Syntax::Methods
# Use color in STDOUT
config.color_enabled = true
# Use color not only in STDOUT but also in pagers and files
config.tty = true
# Use the specified formatter
config.formatter = :documentation # :progress, :html, :textmate
config.order = "random"
config.before(:suite) do
DatabaseCleaner.clean_with(:deletion)
end
config.before(:each) do
DatabaseCleaner.strategy = :deletion
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
def app
Sinatra::Application
end
app.rb
configure :test do
set :database, 'sqlite3:///test.sqlite'
set :logging, Logger::ERROR
end
noise:
D, [2014-01-16T22:14:28.481790 #75797] DEBUG -- : (0.6ms) commit transaction
D, [2014-01-16T22:14:28.484622 #75797] DEBUG -- : (0.1ms) begin transaction
With regards to Ben's answer: I put this in my spec helper:
ActiveRecord::Base.logger = nil unless ENV['LOG'] == true
There were some rare cases where I found that output useful, and including the conditional with environment variable made it super easy to turn logging on, while keeping it off by default.
It turns out that the noise is coming from the ActiveRecord logger.
Setting ActiveRecord::Base.logger = nil in the spec helper gets rid of the SQL noise.

Automatically share context in RSpec

I want to share a memoized method between my specs. So I tried to use shared context like this
RSpec.configure do |spec|
spec.shared_context :specs do
let(:response) { request.execute! }
end
end
describe 'something' do
include_context :specs
end
It works ok. But I have about 60 spec files, so I'm forced to explicitly include context in each of them. Is there an way to automatically include shared context (or at least let definition) for all example groups in spec_helper.rb?
Something like this
RSpec.configure do |spec|
spec.include_context :specs
end
You can set up global before hooks using RSpec.configure via configure-class-methods and Configuration:
RSpec.configure {|c| c.before(:all) { do_stuff }}
let is not supported in RSpec.configure, but you can set up a global let by including it in a SharedContext module and including that module using config.before:
module MyLetDeclarations
extend RSpec::Core::SharedContext
let(:foo) { Foo.new }
end
RSpec.configure { |c| c.include MyLetDeclarations }
In RSpec 3+, this can be achieved as follows - based on Jeremy Peterson's answer.
# spec/supprt/users.rb
module SpecUsers
extend RSpec::SharedContext
let(:admin_user) do
create(:user, email: 'admin#example.org')
end
end
RSpec.configure do |config|
config.include SpecUsers
end
You can do it almost like that: there's a mechanism for including a module, and module inclusion has its own callback mechanism.
Suppose for example that we have a disconnected shared context that we want to use to run all our model specs without a database connection.
shared_context "disconnected" do
before :all do
ActiveRecord::Base.establish_connection(adapter: :nulldb)
end
after :all do
ActiveRecord::Base.establish_connection(:test)
end
end
You can now create a module that will include that context on inclusion.
module Disconnected
def self.included(scope)
scope.include_context "disconnected"
end
end
Finally, you can include that module into all specs in the normal manner (I've demonstrated doing it only for models, just to show that you can), which is almost exactly what you asked for.
RSpec.configure do |config|
config.include Disconnected, type: :model
end
That works with rspec-core 2.13.0 and rspec-rails 2.13.0.
Another way to go is to automatically share examples via metadata. So:
shared_context 'a shared context', a: :b do
let(:foo) { 'bar' }
end
describe 'an example group', a: :b do
# I have access to 'foo' variable
end
The most common way I use it is in rspec-rails, with some shared context depending on the example group type. So if you have config.infer_spec_type_from_file_location!, you can simply do:
shared_context 'a shared context', type: :controller do
let(:foo) { 'bar' }
end
describe SomeController do
# I have access to 'foo' variable
end
Also if you need ability to use shared data in before blocks inside specs, as me, try to include this (if its Rails project):
module SettingsHelper
extend ActiveSupport::Concern
included do
attr_reader :default_headers
before :all do
#default_headers = Hash[
'HTTP_HOST' => 'test.lvh.me'
]
end
after :all do
#default_headers = nil
end
end
end
RSpec.configure do |config|
config.include SettingsHelper
end
Or try something similar, look at #threedaymonk answer.

Resources