What is the correct way to define an alias to `after` in rspec? - ruby

Normally the before and after hooks are assumed to be "initializing" and "cleanup" code respectively. They are supposed to happen "outside of the tests themselves".
I find myself in a situation in which I want to use after as the last step of all the tests in a context. But since after is usually meant to be "cleanup", I am afraid that my tests won't be very explicit. Here's a sample:
describe "when removing" do
let!(:request) do
stub_request(:delete, "http://localhost:4567/containers/#{container.id}").
to_return(status: 200)
end
# returns an http response
subject { client.remove(container.id) }
it { should be }
it { should include('id' => container.id) }
after { expect(request).to have_been_made }
end
I would like to rename that last after to something more explicit, like invariant, to indicate that it is part of the test. I have tried doing this on my spec helper:
# spec_helper.rb
Rspec.configure do |c|
...
end
RSpec::Core::Hooks.class_eval do
alias_method :invariant, :after
end
requiring spec_helper does not seem to throw any errors, but when I run the tests replacing after with invariant I get "undefined method 'invariant' for #<Class:0x007f9872b46588> (NoMethodError)" when running the tests.

It seems there is no easy way to do this, just use before/after

Related

How do you test custom Bugsnag meta_data in Ruby?

How do you test custom Bugsnag meta_data (in Ruby, with Rspec)?
The code that I want to test:
def do_something
thing_that_could_error
rescue => e
Bugsnag.notify(e) do |r|
r.meta_data = { my_extra_data: "useful info" }
end
end
The test I want to write:
context "when there's an error" do
it "calls Bugsnag with my special metadata" do
expect(Bugsnag).to receive(:notify) # TODO test meta_data values contain "my useful info"
expect do
do_something() # exception is thrown and rescued and sent to Bugsnag
end.not_to raise_error
end
end
I am using:
Ruby 2.6.6
Rspec 3.9.0
Bugsnag 6.17.0 https://rubygems.org/gems/bugsnag
The data inside of the meta_data variable is considerably more complicated than in this tiny example, which is why I want to test it. In a beautiful world, I would extract that logic to a helper and test the helper, but right now it is urgent and useful to test in situ.
I've been looking at the inside of the Bugsnag gem to figure this out (plus some Rspec-fu to capture various internal state and returned data) but at some point it's a good idea to ask the internet.
Since the metadata is complicated, I'd suggest simplifying it:
def do_something
thing_that_could_error
rescue => e
Bugsnag.notify(e) do |r|
r.meta_data = error_metadata(e, self, 'foo')
end
end
# I assumed that you'd like to pass exception and all the context
def error_metadata(e, object, *rest)
BugsnagMetadataComposer.new(e, object, *rest).metadata
end
So now you can have a separate test for BugsnagMetadataComposer where you have full control (without mocking) over how you initialize it, and test for metadata output.
Now you only have to test that BugsnagMetadataComposer is instantiated with the objects you want, metadata is called and it returns dummy hash:
let(:my_exception) { StandardError.new }
let(:mock_metadata) { Hash.new }
before do
# ensure thing_that_could_error throws `my_exception`
expect(BugsnagMetadataComposer)
.to receive(new)
.with(my_exception, subject, anything)
.and_return(mock_metadata)
end
And the hard part, ensure that metadata is assigned. To do that you can cheat a little and see how Bugsnag gem is doing it
Apparently there's something called breadcrumbs:
let(:breadcrumbs) { Bugsnag.configuration.breadcrumbs }
Which I guess has all the Bugsnag requests, last one on top, so you can do something similar to https://github.com/bugsnag/bugsnag-ruby/blob/f9c539670c448f7f129a3f8be7d412e2e824a357/spec/bugsnag_spec.rb#L36-L40
specify do
do_something()
expect(breadcrumbs.last.metadata).to eq(expected_metadata)
end
And for clarity, the whole spec would look a bit like this:
let(:my_exception) { StandardError.new }
let(:mock_metadata) { Hash.new }
before do
# ensure thing_that_could_error throws `my_exception`
expect(BugsnagMetadataComposer)
.to receive(new)
.with(my_exception, subject, anything)
.and_return(mock_metadata)
end
specify do
do_something()
expect(breadcrumbs.last.metadata).to eq(expected_metadata)
end

How do I use rspec mock methods from inside a custom DSL

I'm writing a testing library that works on top of rspec. I have a custom dsl that looks like this:
rast Worker do
prepare do |day_type, dow|
allow(subject).to receive(:holiday?) { day_type == 'Holiday' }
allow(subject).to receive(:dow) { dow }
end
execute do
result subject.goto_work?
end
end
The two allow statements do not work because they are inside my custom DSL rast with the method prepare. How can I make it work?
Inside the execute method I invoke this prepare block like this:
def execute
prepare_block = #prepare_block
RSpec.describe "test" do
prepare_block&.call(*params)
...
I don't have the whole picture, but at a guess and off the top of my mind, you may fare better with something like
RSpec.describe "test" do
instance_eval(prepare_block, *params) if prepare_block
end
instance_eval will evaluate the block in the context of the receiver (so whatever self is inside the describe block).
If you just do prepare_block.call, it won't have access to any methods defined in the context where it happened to be called from, as you found out.
Good luck!

RSpec calls my slow test multiple times

I'm trying to test an object in RSpec. There are multiple things I want to check before and after, so I followed the examples I found around the web and ended up with something like this:
describe Processor do
before(:each) do
# create some data in temp to run the test against
end
after(:each) do
# wipe out the data we put in temp
end
let(:processor) { Processor.new }
describe '#process' do
subject { lambda { processor.process } }
# it should actually perform the processing
it { should change { count('...') }.from(0).to(1) }
it { should change { count('...') }.from(0).to(2) }
# it should leave some other things unaffected
it { should_not change { count('...') } }
end
end
This does work, but what I'm seeing is that both the before() code and #process are slow - and being executed by RSpec three times each.
Usually when you have a slow thing, people say "just mock it out", but this time, it's the very thing I am trying to test which is slow, so that would be pointless.
How can I avoid multiple calls to the subject of the test, in this situation where all the checks are of the before-and-after variety?
before(:each) and after(:each) is a callback that's called before and after each spec, i.e. each 'it'. If you wish to do something before and after the outer 'describe' block, use before(:all) and after(:all).
See rspec docs here (relishapp).
(Note, however, that if you're using rspec with rails, using before/after(:all) will run outside of the regular cleanup of the database, which might lead to crap in your test db.)
Good luck!

rails rspec - how to check for a model constant?

How can I do something like:
it { should have_constant(:FIXED_LIST) }
In my model (active record) I have FIXED_LIST = 'A String'
It's not a db attribute or a method and I haven't been able to use responds_to or has_attribute to test for it (they fail). What can I use the to check for it. - btw I have the shoulda-matchers installed.
Based on David Chelimsky's answer I've got this to work by slightly modifying his code.
In a file spec/support/utilities.rb (or some other in spec/support) you can put:
RSpec::Matchers.define :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
Note the use of "RSpec::Matchers.define" in stead of "matchers"
This allows to test for constants in your specs, like:
it "should have a fixed list constant" do
YourModel.should have_constant(:FIXED_LIST)
end
Note the use of "have_constant" in stead of "have_const"
It reads a little silly, but:
describe MyClass do
it { should be_const_defined(:VERSION) }
end
The reason is that Rspec has "magic" matchers for methods starting with be_ and have_. For example, it { should have_green_pants } would assert that the has_green_pants? method on the subject returns true.
In the same fashion, an example such as it { should be_happy } would assert that the happy? method on the subject returns true.
So, the example it { should be_const_defined(:VERSION) } asserts that const_defined?(:VERSION) returns true.
If you want to say have_constant you can define a custom matcher for it:
matcher :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
MyClass.should have_const(:CONST)
If you're trying to use the one-liner syntax, you'll need to make sure the subject is a class (not an instance) or check for it in the matcher:
matcher :have_constant do |const|
match do |owner|
(owner.is_a?(Class) ? owner : owner.class).const_defined?(const)
end
end
See http://rubydoc.info/gems/rspec-expectations/RSpec/Matchers for more info on custom matchers.
HTH,
David
Another option to simply make sure the constant is defined – not worrying about what it's defined with:
it 'has a WHATEVER constant' do
expect(SomeClass::WHATEVER).not_to be_nil
end
A warning to anyone trying to test that constants are defined: If your code references an undefined constant while defining a class, then your specs will crash before they get to your test.
This can lead you to believe that
expect { FOO }.to_not raise_error
is failing to catch the NameError, because you'll get a big stack trace, instead of a nice "expected not to raise error, but raised NameError."
Amidst the huge stack trace, it can be difficult to notice that your test is actually crashing on line 1: requre "spec/spec_helper" because your entire application is failing to load before it gets to your actual test.
This can happen if you have dynamically defined constants, such as is done by ActiveHash::Enum, and you then use them in the definition of another constant. Don't bother testing that they exist, every spec in your app will crash if one of them fails to be defined.
You could use
defined? YOUR_MODEL::FIXED_LIST
In RSpec 2, I was able to get this to work in one line as follows:
it { subject.class.should be_const_defined(:MY_CONST) }
That is, check against the class, instead of the instance.
In My model
class Role < ActiveRecord::Base
ROLE_ADMIN = "Administrador"
end
In My rspec
RSpec.describe Role, type: :model do
let(:fake_class) { Class.new }
describe "set constants" do
before { stub_const("#{described_class}", fake_class) }
it { expect(described_class::ROLE_ADMIN).to eq("Administrador") }
end
end
For ruby 2.1.5 and rspec 3.5.0 I am able to test that constant SEARCH_CHARS_TO_IGNORE is defined in the class DiffAlertsDatatable as follows:
expect(DiffAlertsDatatable.const_defined?(:SEARCH_CHARS_TO_IGNORE)).to eq(true)

Detect Rspec test failure on after each method

I am trying to run an RSpec test, and I want to detect if the test failed in the after method. I have something like this right now:
after(:each) do
cc = ConnectController.new()
cc.update(<TEST-SERVER-CONTROLLER>, <TC-RESULT-ID>, result?)
end
As you can see, the result? function is what I need to replace, to detect if the test fails or not, and to also get information about the test that failed.
In addition to Daniel's answer, in Rspec3 the example method was deleted (see here for more info).
You will have to do something like this:
after(:each) do |example|
if example.exception
# ...
end
end
EDIT: this answer is only valid for RSpec 2. for RSpec 3 see geekazoid's answer.
The after each block runs in the context of class which exposes example and you can detect failures by checking the exception method on example thusly:
after(:each) do
if example.exception != nil
# Failure only code goes here
end
end
I was looking for how to check if success for all examples in a group in a after(:context) / after(:all) block. Here's what I came up with:
after(:all) do |example_group|
all_groups = example_group.class.descendants
failed_examples = all_groups.map(&:examples).flatten.select(&:exception)
if failed_examples.empty?
# runs only if there are no failures
do('something')
end
end

Resources