I want to run a specific block of code before one specific context, and it should run only once. I tried to use metadata for context block, but it calls my block of code before every example.
before do |context|
p 'test test' if context.medata[:something]
end
...
describe '#execute' do
context 'header with timelog fields', :something do
it '123' do
expect(true).to eq true
end
it '234' do
expect(true).to eq true
end
end
end
test test appears twice when I run rspec.
In rspec, writing before is shorthand for before(:each).
What you need to use, instead, is a before(:all):
describe '#execute' do
context 'header with timelog fields' do
before(:all) do
p 'test test'
end
it '123' do
expect(true).to eq true
end
it '234' do
expect(true).to eq true
end
end
end
Related
I've got simple sidekiq worker which, I don't know why it doesn't worked. I think maybe it's because of specs.
worker
class AdminPanelLogRemoverWorker
include Sidekiq::Worker
def perform
expired_logs = AdminPanelLog.where('created_at > ?', 1.year.ago)
expired_logs.delete_all
end
end
specs
require 'rails_helper'
describe AdminPanelLogRemoverWorker do
include_context 'with admin_user form'
subject { described_class.new.perform }
let!(:admin_panel_log1) do
create :admin_panel_log,
action_type: 'Update',
old_data: admin_user_form,
created_at: 2.years.ago
end
let!(:admin_panel_log2) do
create :admin_panel_log,
old_data: admin_user_form,
created_at: 2.days.ago
end
context 'when admin log is outdated' do
it 'calls remover worker' do
expect(AdminPanelLog.count).to eq(1)
end
end
end
The admin_panel_log1 and admin_panel_log2 is corresponding model AdminPanelLog and it forms correctly (maybe I should avoid let! ?). At the result specs failed with an error
Failure/Error: expect(AdminPanelLog.count).to eq(1)
expected: 1
got: 0
(compared using ==)
I justed tested with
RSpec.describe TestController, type: :controller do
subject { User.new }
let!(:test) do
p subject
p "dfvb"
end
it 'testing order of let and subject' do
# Spec
end
end
The subject is initialized before the let! block is called. So in your case, the lo AdminPanelLog is not even created while the job was running. So that the example failed.
context 'when the admin log is outdated' do
it 'calls remover worker' do
subject.new.perform #Perform the job here or after the initialization of AdminPanelLog
expect(AdminPanelLog.count).to eq(1)
end
end
and remove this subject { described_class.new.perform }, as the subject itself will hold the value of the current class.
As already transpires from the Aarthi answer, the issue was that you did not call subject, so the code was not executed and your worker was not called.
Still, I would improve the answer with the following
context 'when admin log is outdated' do
it 'remover worker deletes them' do
expect { subject }.to change(AdminPanelLog, :count).by(-2) #or whatever the amount is
end
end
The above test allows you to check if the worker indeed did it's job at deleting stuff.
I have such a code:
RSpec.describe CypherFileExecution do
describe "self.drop_data_and_execute" do
# (...)
it "drops old data" do
# (...)
end
it "executes cypher file" do
described_class.drop_data_and_execute('spec/seeds/neo4j_object_spec.cypher')
expect([
does_object_exist_in_db?("relationship-class"),
does_object_exist_in_db?("class-instance-class"),
]).to eql [true, true]
end
end
describe "self.execute" do
it "executes cypher file" do
described_class.execute('spec/seeds/neo4j_object_spec.cypher')
expect([
does_object_exist_in_db?("relationship-class"),
does_object_exist_in_db?("class-instance-class"),
]).to eql [true, true]
end
end
end
As we can see, the "executes cypher file" blocks are the same for both methods (in fact, one of them calls another one). What can I do to make this code DRY? If I'm not mistaken, shared examples and contexts work at the class level, but I have the method level here. What should I do?
This is a use case for shared examples, which we can use in conjonction with subject, leveraging RSpec's lazy evaluation.
RSpec.describe CypherFileExecution do
shared_examples 'executes cypher file' do
it "executes cypher file" do
subject
expect(does_object_exist_in_db?("relationship-class")).to be(true)
expect(does_object_exist_in_db?("class-instance-class")).to be(true)
end
end
describe "self.drop_data_and_execute" do
subject { described_class.drop_data_and_execute('spec/seeds/neo4j_object_spec.cypher') }
include_examples 'executes cypher file'
# (...)
it "drops old data" do
# (...)
end
end
describe "self.execute" do
subject { described_class.execute('spec/seeds/neo4j_object_spec.cypher') }
include_examples 'executes cypher file'
end
end
describe 'Feature' do
before do
setup
end
describe 'Success' do
before do
setup_for_success
end
specify 'It works' do
...
end
end
end
RSpec will always run the setup before setup_for_success. It there a way to run setup_for_success first?
You can do this by scoping a before(:all) to run before a before(:each) try this:
describe 'Feature' do
before(:each) do
puts "second"
end
describe 'Success' do
before(:all) do
puts "first"
end
specify 'It works' do
...
end
end
end
# =>
10:29:54 - INFO - Running: spec
Run options: include {:focus=>true}
first
second
.
Finished in 0.25793 seconds (files took 2.52 seconds to load)
1 example, 0 failures
EDIT:
In Rspec 2, the actions run in this order:
before suite
before all
before each
after each
after all
after suite
Here's a link to the docs showing the order that the methods are called in: https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#before/after-blocks-are-run-in-order
Apparently in Rspec 3.5, the before block calls have a different naming that also works. They run in this order:
before :suite
before :context
before :example
after :example
after :context
after :suite
describe 'Feature' do
before(:example) do
puts "second"
end
describe 'Success' do
before(:context) do
puts "first"
end
specify 'It works' do
...
end
end
end
10:59:45 - INFO - Running: spec
Run options: include {:focus=>true}
first
second
.
Finished in 0.06367 seconds (files took 2.57 seconds to load)
1 example, 0 failures
Here's the newer docs:
http://www.relishapp.com/rspec/rspec-core/v/3-5/docs/hooks/before-and-after-hooks
before filters are appended in the order they're specified. Since RSpec 2.10.0, you can prepend them instead by making them prepend_before filters.
Likewise, after filters are prepended by default, but you can append_after them instead.
Your code would end up as follows (compacted for brevity):
describe 'Feature' do
before { setup }
describe 'Success' do
prepend_before { setup_for_success }
it 'works' { ... }
end
end
It seems a little weird you put in a nested context what you need in the outer one. I suspect that you don't need that setup in all of the nested contexts. If that's the case you need to filtering your hooks.
RSpec.describe 'Feature' do
before :each, success: true do
setup_for_success
end
before :each do
setup
end
describe 'Success', success: true do
specify 'It works' do
...
end
end
describe 'Fail' do
specify 'Won´t work' do
...
end
end
end
You can do this without nesting:
RSpec.describe 'Feature' do
before :each, success: true do
setup_for_success
end
before :each do
setup
end
specify 'It works', success: true do
...
end
specify 'Won´t work' do
...
end
end
Here is the link to the docs:
https://relishapp.com/rspec/rspec-core/docs/hooks/filters
Is it possible to do something like this?
module MyHelper
before (:each) do
allow(Class).to receive(:method).and_return(true)
end
end
Then in my tests I could do something like:
RSpec.describe 'My cool test' do
include MyHelper
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
EDIT: This produces the following error:
undefined method `before' for MyHelper:Module (NoMethodError)
Essentially I have a case where many tests do different things, but a common model across off of them reacts on an after_commit which ends up always calling a method which talks to an API. I dont want to GLOBALLY allow Class to receive :method as, sometimes, I need to define it myself for special cases... but I'd like to not have to repeat my allow/receive/and_return and instead wrap it in a common helper...
You can create a hook that is triggered via metadata, for example :type => :api:
RSpec.configure do |c|
c.before(:each, :type => :api) do
allow(Class).to receive(:method).and_return(true)
end
end
And in your spec:
RSpec.describe 'My cool test', :type => :api do
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
You can also pass :type => :api to individual it blocks.
It is possible to do things like you want with feature called shared_context
You could create the shared file with code like this
shared_file.rb
shared_context "stubbing :method on Class" do
before { allow(Class).to receive(:method).and_return(true) }
end
Then you could include that context in the files you needed in the blocks you wanted like so
your_spec_file.rb
require 'rails_helper'
require 'shared_file'
RSpec.describe 'My cool test' do
include_context "stubbing :method on Class"
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
And it will be more naturally for RSpec than your included/extended module helpers. It would be "RSpec way" let's say.
You could separate that code into shared_context and include it into example groups (not examples) like this:
RSpec.describe 'My cool test' do
shared_context 'class stub' do
before (:each) do
allow(Class).to receive(:method).and_return(true)
end
end
describe "here I am using it" do
include_context 'class stub'
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
describe "here I am not" do
it 'Tests a Class Method' do
expect { Class.method }.not_to eq true
end
end
end
Shared context can contain let, helper functions & everything you need except examples.
https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-context
I'm having scope issues when using RSpec's `before(:all)' block.
Previously I was using before(:each), which worked fine:
module ExampleModule
describe ExampleClass
before(:each) do
#loader = Loader.new
end
...
context 'When something' do
before(:each) do
puts #loader.inspect # Loader exists
# Do something using #loader
end
...
end
end
end
But switching the nested before(:each) block tobefore(:all) means loader is nil:
module ExampleModule
describe ExampleClass
before(:each) do
#loader = Loader.new
end
...
context 'When something' do
before(:all) do
puts #loader.inspect # Loader is nil
# Do something using #loader
end
...
end
end
end
So why is #loader nil in the before(:all) block, but not in the before(:each) block?
All the :all blocks happen before any of the :each blocks:
describe "Foo" do
before :all do
puts "global before :all"
end
before :each do
puts "global before :each"
end
context "with Bar" do
before :all do
puts "context before :all"
end
before :each do
puts "context before :each"
end
it "happens" do
1.should be_true
end
it "or not" do
1.should_not be_false
end
end
end
Output:
rspec -f d -c before.rb
Foo
global before :all
with Bar
context before :all
global before :each
context before :each
happens
global before :each
context before :each
or not
As per the Rspec documentation on hooks, before :all hooks are run prior to before :each.