Binding.pry failing in shared example block? - ruby

I have a project where placing a binding.pry will work when running RSpec tests except in classes where we use a shared example. We have it set up to where there are two blocks, one in the class itself that handles errors, and another in the shared example. The structure is like this (Sorry I can't get more specific, there is a lot of code):
Class -> (Code surrounded by block)
require 'pry'
def class(param, param)
ClassThatTakesBlock.handle() do |_some_event|
rest of class's code...
binding.pry # Fails dramatically when running RSpec
...
end
end
end
Shared example -> (Code surrounded by block)
RSpec.shared_examples 'some class' do |optional_param1, optional_param2|
subject { class(param: param_hash, param: param) }
...
end
end
Class spec ->
RSpec.describe 'directory/class' do
it_behaves_like 'some class', false do
...
end
end
The problem is when I require 'pry' and try to place a binding.pry, and run a spec that goes past that binding.pry, the spec will throw a bunch of errors with something like
Failure/Error: subject { class(param: param_hash, param: param) } (The subject line of shared example)
expected: ("AWS param in class")
got: ("DISABLE_PRY")
Please stub a default value first if message might be received with other args as well.
Shared Example Group: "some class" called from ./file_directory/path/class_spec.rb:40
Along with a big wall of what looks like params, bundler paths, rvm flags, etc.
Is there something special I need to do to get binding.pry working in a shared example? Or is there another way to place a debugger within the code? I can't even get print statements to surface when I run RSpec and I can't debug my specs.

the method name class is causing the issue, because the keyword class is reserved in ruby. My suggestion is to name it klass.

I believe the issue I had was incorrect stubbing. Adding
allow(ENV).to receive(:[])
fixed it, but I also think stubbing stdout was also causing issues.

Related

How can two specs share the same "it" block?

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

How to pass an external object to rspec tests?

I have a series of RSpec tests - each one living in their own files - that all need a specific object to be ran (shop).
Since setting up the shop object is complicated, I want to have a master file that builds the shop object and then passes it to all the RSpec test files in turn. The idea is that those writing the test scripts do not need to know anything about the setup step.
I could not figure out how to bring something from the outside world into an rspec test case. I have tried variations around the lines of this:
file master.rb:
describe "Testing tests/some_test.rb" do
before :all do
#shop = some_complex_operations
end
load 'tests/some_test.rb'
end
file tests/some_test.rb:
describe 'Some feature' do
it 'should know about #shop'
expect(#shop).not_to be nil
end
end
When I run rspec master.rb then some_test.rb fails.
I know what I outlined is probably a very wrong way to go about this, but I hope at least you get the idea.
How can I implement such a thing cleanly?
What you need is a global hook.
You can create a spec_helper and add there a before all global hook. For example:
RSpec.configure do |config|
config.before(:each) do
#shop = "in case you need a fresh instance of shop each time"
end
config.before(:all) do
#shop = "in case you need a single instance (i.e. you don't modify it)"
end
end
Then you should require this helper from each of your specs. The spec_helper can store any global hooks as well as helpers, or other dependencies for your tests.
When you are writing a test, it should test one thing. shop might be a very complex object, which many objects and methods interact with, but I should guess none of them are aware of the complexity of shop - each method is interested in some aspect of shop.
Therefore I suggest that instead of building a complex object for each test, make a double of it, make it expect what's relevant, and then simply stub the rest:
describe 'Some feature' do
let(:shop) { double('shop').as_nil_object }
let(:my_object) { MyClass.new(shop: shop) }
it 'should do something awesome with shop' do
expect(shop).to receive(:awesome_data).and_return(my_data: 'is_this')
expect(my_object.do_something_awesome).to eq 'how awesome is_this?'
end
end

Mock a `puts` to a file in Rspec

I have a silly "queue-class[1]" with the following method, that I want to spec out with Rspec. I am not interested in testing if writing to the file-system works (It works, my computer works) but in whether or not the correct data gets written away.
def write transaction
File.open("messages/#{#next_id}", "w") {|f| f.puts transaction }
#next_id += 1
end
The spec for testing this is:
describe TransactionQueue do
context "#write" do
it "should write positive values" do
open_file = mock File
open_file.stub(:puts)
File.any_instance.stub(:open).and_yield(open_file)
File.any_instance.should_receive(:open)
open_file.should_receive(:puts).with("+100")
#queue = TransactionQueue.new
#queue.write("+100")
end
end
end
Running this, fails, because my Mocks never receive the expected "open" and "puts" messages.
Can I mock File this way? Did I use the any_instance correctly; is my attempt to stub a "block-yield" correct?
I'd rather not use extra gems like FakeFS when it can be avoided; this is not so much about getting it to work; bu mostly about actually understanding what is going on. Hence my attempt to avoid extra gems/layers of complexity.
[1] Class is from The Cucumber Book; but these tests have littel to do with Cucumber itself. I somehow broke the code when following the book; and want to find out what, by writing unit-tests for the parts that the book does not write tests for: the helper classes.
It's not "any instance" of the File class that you expect to receive the open method; it's the File class itself:
File.stub(:open).and_yield(open_file)
File.should_receive(:open)
Furthermore, don't use both a stub and an expectation. If you want to verify that File.open is actually called:
File.should_receive(:open).and_yield(open_file)
If you merely want to stub the open method in case it gets called, but don't want to require it as behaviour of the #queue.write method:
File.stub(:open).and_yield(open_file)
(This is from memory, I haven't used RSpec for a few months.)

Cucumber/Capybara - Using RSpec matchers with a "Page Object" pattern

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.

How to mock an TCP connecting in Cucumber

I want to test one program which can capture and send IP packets to some clients, so how to mock request or client in Cucumber? thanks
Normally I would answer the question with the cavet that it's a bad idea but this is such a bad idea I'm only going to answer half of it, how to mock in Cucumber generically.
You see Cucumber is meant to be a total test from the outside in so it's meant to completely run your code without any test doubles. The whole point is you are not unit testing but are testing your whole application.
"We recommend you exercise your whole stack when using Cucumber. [However] you can set up mocks with expectations in your Step Definitions." - Aslak Hellesøy, Creator of Cucumber
Granted you can do this but you are going to need to write your own the TCPServer and TCPSocket classes to avoid using the network and that can actually introduce bugs since your writing specs against your mock Net classes not the actual Net classes. Again, not a good idea.
Enough yapping, here's how to use mocks in Cucumber. (I'm going to assume you have a basic understanding of Cucumber and Ruby so I will skip some steps like how to require your class files in Cucumber.)
Let's say you have the following classes:
class Bar
def expensive_method
"expensive method called"
end
end
class Foo
# Note that if we don't send a bar it will default to the standard Bar class
# This is a standard pattern to allow test injection into your code.
def initialize(bar=Bar.new)
#bar = bar
puts "Foo.bar: #{#bar.inspect}"
end
def do_something
puts "Foo is doing something to bar"
#bar.expensive_method
end
end
You should have the Bar and Foo classes required in your features/support/env.rb file but to enable RSpec mocks you need to add the following line:
require 'cucumber/rspec/doubles'
Now create a feature file like this one:
Feature: Do something
In order to get some value
As a stake holder
I want something done
Scenario: Do something
Given I am using Foo
When I do something
Then I should have an outcome
And add the steps to your step definitions file:
Given /^I am using Foo$/ do
# create a mock bar to avoid the expensive call
bar = double('bar')
bar.stub(:expensive_method).and_return('inexpensive mock method called')
#foo = Foo.new(bar)
end
When /^I do something$/ do
#outcome = #foo.do_something
# Debug display of the outcome
puts ""
puts "*" * 40
puts "\nMocked object call:"
puts #outcome
puts ""
puts "*" * 40
end
Then /^I should have an outcome$/ do
#outcome.should_not == nil
end
Now when you run your feature file you should see:
****************************************
Mocked object call:
inexpensive mock method called
****************************************

Resources