I have a Rails project and use RSpec as a testing framework. What I need is to subscribe to the event when some matcher fails, e.g. I got this:
true.should be_false
I want to do some action on every fail. Is this functionality provided by the RSpec?
You can monkey-patch this behavior into RSpec::Core::Example class:
require 'spec_helper'
class RSpec::Core::Example
def failed?
!#exception.nil?
end
end
describe "my_tested_things" do
...
end
Then you can ensure it will run your desired code after all failing matches:
after(:each) do
run_my_code if example.failed?
end
Check out the hooks, not certain it will help but where it passes example in to the block (in the docs) you may be able to get the result...
https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/hooks
Related
So far I'm using 'expect' in my test framework which will stop the execution when it meets a fail condition. I want something like, the execution should happen even if it meets the fail condition. I could see that there is a matcher called 'Verify' in rspec where I need to inherit 'Test::Unit::TestCase' class, but my issue is that, I need the matcher in my spec file which is not written under a ruby class.
There isn't a way to do this with RSpec, out of the box.
Because Rspec is designed to test small, isolated logics.
On failure, Rspec matchers raise Error, So what you can do is to wrap the matchers in a rescue block.
To satisfy your need, you could write a wrapper like this:
def report_last(&block)
begin
yield
rescue Exception => e
puts "Failure: #{e}"
end
end
In your test case:
describe Calculator do
it “should add 2 numbers” do
report_last do
expect(described_class.new(2, 3).sum)to eq(5)
end
end
end
I have two tests that are very similar. In fact, both tests should produce the same results, but for different inputs. Each needs its own before block but in the interest of DRY I'd like them to share the same it block.
Is that even possible? If so, how?
Shared Examples in Rspec are designed to be used for this purpose. You can keep common it blocks inside a shared example and include them in describe or context blocks.
Simplest example of shared_examples would be,
RSpec.shared_examples "unauthorized_response_examples" do
it { expect(subject).to respond_with(403) }
it { expect(json['message']).to eq(I18n.t("unauthorized")) }
end
And inside your controller specs whenever you need to check unauthorized response you can include examples like,
...
include_examples "unauthorized_response_examples"
Also, you can pass on parameters, action names and controller names and have before(:each|:all) hooks and nested contexts or describe.
For more, you can look at rspec documentation.
Helper methods. (Excuse the horribleness of the example. Would have been better if you'd posted yours :P)
describe "soup" do
def soup_is_salty # helper method! \o/
soup.add(:meat)
soup.add(:egg)
soup.cook
soup.salty?
end
describe "with carrot" do
before(:all) do
soup.add(:carrot)
end
it "should be salty" do
soup_is_salty # get help from helper method! \o/
end
end
describe "soup with potato" do
before(:all) do
soup.add(:potato)
end
it "should be salty" do
soup_is_salty # get help from helper method! \o/
end
end
end
Take the block and create and external method
for example I have some tests that require me to login to my app. So I have a helper.rb file that I include in each spec and that contains a "login" block. Then in each test I can just call login
Is there a way that I can configure RSpec to raise an error when I am trying to stub/mock a non-existing method.
So, If I have the class
class A
def foo
end
end
and I write something like:
describe A do
describe '#foo' do
it 'foos' do
expect(subject).to receive(foo2)
# fire
subject.foo
end
end
end
then RSpec will fail on first line
expect(subject....
by telling me that :foo2 is not message that subject responds to.
This functionality was introduced in the rspec-fire gem and was recently ported into RSpec 3 (currently in beta) as described in this github issue. It is documented in https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
I have this situation in my project - I have a Singleton class representing browser used during the test:
class Browser
include Singleton
def initialize
#browser = Watir::Browser.new :ff
end
def goto url
#browser.goto url
end
def close
#browser.close
end
end
With this rakefile I wanted to make sure browser gets closed when the tests are finished:
desc "default test task"
task :test_all do
Rake::Task[:all_rspec_tests].invoke
Rake::Task[:close_browser].invoke
end
desc "runs all rspec tests"
RSpec::Core::RakeTask.new(:all_rspec_tests) do |t|
# run all rspec tests according to pattern for filename
# tests make use of Singleton class representing browser
end
desc "closes browser used during tests"
task :close_browser do
Browser.instance.close
end
But this doesnt work as expected - RSpec runner instantiates it's own singleton object instance which the close_browser task does not see. So when the close_browser task gets scheduled another browser instance is instantiated and closed right away but the one used during the test remains open. How can I achieve that after all RSpec tests have ran the browser gets closed?? I guess this must be done with some configuration of the RSpec global hooks?? Would someone point me to an example of such hooks? Thank you!
You don't want to do these things in Rake tasks. If you just want one browser instance to start at the beginning and persist for all your tests, you can do that in spec/spec_helper.rb using a before :suite hook. Something like:
RSpec.configure do |config|
# (other RSpec config)
config.before(:suite) do
Browser.initialize # or whatever
end
config.after(:suite) do
Browser.close
end
end
This will launch the browser once at the beginning and close it at the end. It's more likely that you'll want to use before and after hooks only for certain test files, or collections of tests. Start with the RSpec docs.
You might want to consider using Capybara instead of Watir; it integrates well with RSpec and you won't have to do this stuff manually. There's also watir-rspec, but I've never used it myself. This is a solved problem, in any case. It'll be easier to find something to help you than to set this up on your own.
I'm currently refactoring a whole load of cucumber tests to use a "Page Object" pattern, but I'm having a lot of problems using the RSpec matchers.
The existing step I have is as follows:
Then /^I should (not )?see the following alerts:$/ do |negate, alerts|
expectation = negate ? :should_not : :should
within(ALERT_TABLE_ID) do
alerts.hashes.each do |alert|
page.send(expectation, have_content(alert["Original ID"]))
end
end
end
My refactored step is:
Then /^I should (not )?see the following alerts:$/ do |negate, alerts|
expectation = negate ? :should_not : :should
#alert_reporting_panel = AlertReportingPanel.new(Capybara.current_session)
#alert_reporting_panel.verify_contents expectation, alerts
end
And my Panel Object is:
class AlertReportingPanel
def initialize(session)
#session = session
end
def verify_contents(expectation, alerts)
#session.within(ALERT_TABLE_ID) do
alerts.hashes.each do |alert|
#session.send(expectation, have_content(alert["Original ID"]))
end
end
end
end
Unfortunately, I get undefined method 'have_contents' for #<AlertReportingPanel:0x3f0faf8> (NoMethodError).
I have tried adding require 'rspec' to the top of the class and also tried fully qualifying the have-content method thus: Capybara::RSpecMatchers::HaveMatcher.have_content, but I just get uninitialized constant Capybara::RSpecMatchers (NameError).
I'm pretty new to Ruby and I'm sure this is trivial to fix... but I just can't seem to work it out for myself.
Please help. Thankyou.
This was a while back so I'm guessing you may have your answer by now but here goes.
You need to include the necessary modules in order bring in and have access to the likes of *have_content*. So your Panel Object would look like:
class AlertReportingPanel
include Capybara::DSL
include Capybara::Node::Matchers
include RSpec::Matchers
def initialize... etc
Instead of writing your own Page Object system you could try using SitePrism
I'm a little biased (I wrote that gem) but it might make life easier for you.