RSpec navigatable it_behaves_like/shared_examples using LSP - ruby

I have a legacy project that uses shared_examples feature a lot and it is very inconvenient to navigate between actual specs and shared_examples implementation.
For now, the only way to do it is to search globally within a project using "some example" example name.
RSpec.shared_examples "some example" do |parameter|
let(:something) { parameter }
it "uses the given parameter" do
expect(something).to eq(parameter)
end
end
RSpec.describe SomeClass do
# "some example" has to become *something*
# I can click and navigate to(jump-to-definition)
include_examples "some example", "parameter1"
end
I would like to use LSP/Solargraph for this kind of navigation.
Perhaps anyone did this before and willing to share how they did it?

This turned out simpler than I expected.
Just extract your example name as a string constant and put it somewhere next to RSpec.shared_examples implementation.
# spec/support/shared_examples.rb
# in case you prefer one-liner use:
# RSpec.shared_examples(A_COLLECTION = 'a collection') do
# otherwise:
A_COLLECTION = 'a collection'
RSpec.shared_examples A_COLLECTION do
let(:collection) { described_class.new([7, 2, 4]) }
context 'initialized with 3 items' do
it 'says it has three items' do
expect(collection.size).to eq(3)
end
end
end
# spec/array_spec.rb
RSpec.describe Array do
it_behaves_like A_COLLECTION
end
HINT: In case it doesn't work for you, check .solargraph.yml config which excludes "spec/**/*" from indexing by default.

Related

Accessing Ruby hash value via symbol key returning true not value

I'm trying to make a basic command line tool using command line arguments (starting simple and gradually building up). I am using Ruby and its OptionParser class to do this. I have the following code:
require 'optparse'
options = {}
OptionParser.new do |option|
option.banner = "Usage: todo_list.rb <list title> <tasks>"
option.on("-t", "--title", "Title of task list") do |value|
options[:title] = value
end
option.on("-c", "--content", "Content of task list (tasks)") do |value|
options[:content] = value
end
option.on("-h", "--help", "Show this help message") do ||
puts option
end
end.parse!
p options
p ARGV
if options[:title]
puts "Created task list with title: #{ options[:title] }"
end
if options[:content]
puts "Added task: #{ options[:content] }"
end
For reference I have been running the clt as todo_list.rb -t Test -c content.
In the final 2 if statements I am simply trying to access the value of the key :content/:title in the options hash if they exist (if they have been passed from the command line), however the program only returns true ("Created task list with title true") / ("Added task: true"), instead of the value ("Test" or "content")
Using p ARGV outputs ['Test', 'Content'] so the arguments are being passed correctly, I think. Using p options returns {:title=>true, :content=>true}.
I have no idea why this is happening. If anyone has a clue, any and all advice is appreciated. Thank you.
You need to tell the option parser that your switches require arguments:
option.on("-tTITLE", "--title TITLE", "Title of task list") do |value|
options[:title] = value
end
option.on("-cCONTENT", "--content CONTENT", "Content of task list (tasks)") do |value|
options[:content] = value
end
Otherwise the options are considered to be simple boolean flags.
The documentation for optparse covers this in the #make_switch section:
https://ruby-doc.org/stdlib/libdoc/optparse/rdoc/OptionParser.html#method-i-make_switch
but that's not entirely obvious unless you already know what you're looking for. You usually end up figuring out how it works by looking at the examples and experimenting, then you stumbling across the #make_switch method.

How to share data between implementation and description of a spec?

I wonder if there's any good way to reuse data between implementation and description of a spec... More particularly, I'd like to be able do something like the following:
describe "#some_method" do
let(:arg1) { "Sample String 1" }
let(:arg2) { "Sample String 2" }
context "with '#{arg1}', its result" do
specify { some_method(arg1).should == 1 }
end
context "with '#{arg2}', its result" do
specify { some_method(arg2).should == 2 }
end
end
Of course, this code won't work - arg1 and arg2 are not accessible outside of spec bodies.
Is it possible to achieve the similar result without using global variables or external classes?
Update:
I'm interested in the output of the spec. Something like this:
#some_method
with 'Sample String 1' its result
should == 1
with 'Sample String 2' its result
should == 2
The answer is that you don't use dynamic descriptions. The RSpec way to do this would be
describe "#some_method" do
it "extracts the number correctly" do
some_method("Sample String 1").should == 1
some_method("Sample String 2").should == 2
end
end
It is no problem to hard-code test data in your specs. If you want more complete output, you can use a custom matcher
require 'rspec'
class Test
def some_method(str)
str[/[0-9]+/].to_i
end
end
RSpec::Matchers.define :return_value_for_argument do |result, arg|
match do |actual|
actual.call(arg) == result
end
description do
"return #{result.inspect} for argument #{arg.inspect}"
end
end
describe Test do
let(:test) { Test.new }
describe "#some_method" do
subject { test.method(:some_method) }
it { should return_value_for_argument 1, "str 1" }
end
end
When doing API testing, I find it incredibly useful to be able to see the path, params, and response of each test. I have used the very useful tips given by Jeff Nyman to store things in the example.metatadata[:thing_i_want_to_store_like_url] of each test and with a custom formatter, print it out.
So my tests output look something like this:
that jonathan does not know it exists
:path : /user/20
:params: {}
=> response: {"error"=>{"message"=>"error", "code"=>404}}
that jonathan cannot edit
:path : /user/20/update
:params: {:name=>"evil_name"}
=> response: {"error"=>{"message"=>"error", "code"=>404}}
It's not appropriate to cite specific arguments in your descriptions. Your descriptions should provide a human-readable description of the desired behavior, without reference to specific arguments in most cases.

Cucumber HTML tag in feature

I have a cucumber scenario where I wan to test for an HTML tag.
Scenario: enter words
Given I enter "cat,dog"
When I set tag to "li" and the class to "word"
Then I should see "<li class=\"word\">cat</li>"
And I should see "<li class=\"word\">dog</li>"
Is this the correct way to write this scenario?
You should aim to have your scenario's read in plain english. If I weren't a developer then the scenario wouldn't make much sense to me. You could do something like this:
Then I should see cat within a word list element
The step for this would be:
Then /^(?:|I )should see "([^"]*)" within (.*)$/ do |text, parent|
with_scope(parent) do
if page.respond_to? :should
page.should have_content(text)
else
assert page.has_content?(text)
end
end
end
The cucumber generator should already provide the with_scope method but here it is anyways:
module WithinHelpers
def with_scope(locator)
locator ? within(*selector_for(locator)) { yield } : yield
end
end
World(WithinHelpers)
And just be sure to add the selector to your selectors.rb in features/support/selectors inside the case statement for locator:
module HtmlSelectorsHelpers
def selector_for(locator)
case locator
when ' a word list element'
'li.word'

Disable a group of tests in rspec?

I have a test spec which describes a class and within that has various contexts each with various it blocks.
Is there a way I can disable a context temporarily?
I tried adding a pending "temporarily disabled" call at the very top within a context I want to disable, and I did see something about pending when I ran the spec but then it just continued to run the rest of the tests.
This is what I kind of had:
describe Something
context "some tests" do
it "should blah" do
true
end
end
context "some other tests" do
pending "temporarily disabled"
it "should do something destructive" do
blah
end
end
end
but like I said it just went on to run the tests underneath the pending call.
Searching led me to this mailing list thread in which the the creator (?) of rspec says it's possible in rspec 2, which I'm running. I guess it did work but it didn't have the desired effect of disabling all of the following tests, which is what I think of when I see a pending call.
Is there an alternative or am I doing it wrong?
To disable a tree of specs using RSpec 3 you can:
before { skip }
# or
xdescribe
# or
xcontext
You can add a message with skip that will show up in the output:
before { skip("Awaiting a fix in the gem") }
with RSpec 2:
before { pending }
Use exclusion filters.
From that page:
In your spec_helper.rb (or rails_helper.rb)
RSpec.configure do |c|
c.filter_run_excluding :broken => true
end
In your test:
describe "group 1", :broken => true do
it "group 1 example 1" do
end
it "group 1 example 2" do
end
end
describe "group 2" do
it "group 2 example 1" do
end
end
When I run "rspec ./spec/sample_spec.rb --format doc"
Then the output should contain "group 2 example 1"
And the output should not contain "group 1 example 1"
And the output should not contain "group 1 example 2"
See what you think of this:
describe "something sweet", pending: "Refactor the wazjub for easier frobbing" do
it "does something well"
it "rejects invalid input"
end
I like to see reasons with my pending items when I'm disabling something for "a while". They serve as little comments/TODOs that are presented regularly rather than buried in a comment or an excluded example/file.
Changing it to pending or xit is quick and easy, but I prefer the hash construction. It gives you every-run documentation, is a drop-in (doesn't change describe/context/it so I have to decide what to use again later), and is just as easily removed if the decision is made or blocker is removed.
This works the same for groups and individual examples.
another one. https://gist.github.com/1300152
use xdescribe, xcontext, xit to disable it.
Update:
Since rspec 2.11, it includes xit by default. so the new code will be
# put into spec_helper.rb
module RSpec
module Core
module DSL
def xdescribe(*args, &blk)
describe *args do
pending
end
end
alias xcontext xdescribe
end
end
end
Usage
# a_spec.rb
xdescribe "padding" do
it "returns true" do
1.should == 1
end
end
Use pending instead of describe. If your block is:
context "some other tests" do
it "should do something destructive" do
blah
end
end
You can skip the whole block by:
pending "some other tests" do
it "should do something destructive" do
blah
end
end
describe "GET /blah" do
before(:each) { pending "Feature to be implemented..." }
it { expect(page).to have_button("Submit") }
it { expect(page).to have_content("Blah") }
end
Just to explain what's happening with your code. Including it where you have, it just gets evaluated (and hence run) when the file is loaded during startup. However you need it to be run when the tests run. That's why answers have suggested putting pending (RSpec 2) or skip (RSpec 3) into a before block.

DRY the SUT up - RSpec and Mocking question

n the .net world, my specs would follow the Arrange, Act, Assert pattern. I'm having trouble replicating that in rspec, because there doesn't appear to be an ability to selectively verify your mocks after the SUT has taken it's action. That, coupled with the fact that EVERY expectation is evaluated at the end of each 'It' block, is causing me to repeat myself in a lot of my specs.
Here's an example of what I'm talking about:
describe 'AmazonImporter' do
before(:each) do
Kernel.**stubs**(:sleep).with(1)
end
# iterates through the amazon categories, and for each one, loads ideas with
# the right response group, upserting ideas as it goes
# then goes through, and fleshes out all of the ideas that only have asins.
describe "find_new_ideas" do
before(:all) do
#xml = File.open(File.expand_path('../amazon_ideas_in_category.xml', __FILE__), 'r') {|f| f.read }
end
before(:each) do
#category = AmazonCategory.new(:name => "name", :amazon_id => 1036682)
#response = Amazon::Ecs::Response.new(#xml)
#response_group = "MostGifted"
#asin = 'B002EL2WQI'
#request_hash = {:operation => "BrowseNodeLookup", :browse_node_id => #category.amazon_id,
:response_group => #response_group}
Amazon::Ecs.**expects**(:send_request).with(has_entries(#request_hash)).returns(#response)
GiftIdea.expects(:first).with(has_entries({:site_key => #asin})).returns(nil)
GiftIdea.any_instance.expects(:save)
end
it "sleeps for 1 second after each amazon request" do
Kernel.**expects**(:sleep).with(1)
AmazonImporter.new.find_new_ideas(#category, #response_group)
end
it "loads the ideas for the given response group from amazon" do
Amazon::Ecs.**expects**(:send_request).
with(has_entries(#request_hash)).
returns(#response)
**AmazonImporter.new.find_new_ideas(#category, #response_group)**
end
it "tries to load those ideas from repository" do
GiftIdea.expects(:first).with(has_entries({:site_key => #asin}))
**AmazonImporter.new.find_new_ideas(#category, #response_group)**
end
In this partial example, I'm testing the find_new_ideas method. But I have to call it for each spec (the full spec has 9 assertion blocks). I further have to duplicate the mock setup so that it's stubbed in the before block, but individually expected in the it/assertion block. I'm duplicating or nearly duplicating a ton of code here. I think it's even worse than the highlighting indicates, because a lot of those globals are only defined separately so that they can be consumed by an 'expects' test later on. Is there a better way I'm not seeing yet?
(SUT = System Under Test. Not sure if that's what everyone calls it, or just alt.net folks)
You can use shared example groups to reduce duplication:
shared_examples_for "any pizza" do
it "tastes really good" do
#pizza.should taste_really_good
end
it "is available by the slice" do
#pizza.should be_available_by_the_slice
end
end
describe "New York style thin crust pizza" do
before(:each) do
#pizza = Pizza.new(:region => 'New York' , :style => 'thin crust' )
end
it_behaves_like "any pizza"
it "has a really great sauce" do
#pizza.should have_a_really_great_sauce
end
end
Another technique is to use macros, which is handy if you need similar specs in different classes.
Note: the example above is borrowed from The RSpec Book, Chapter 12.
you can separate them using "context" if that helps...
https://github.com/dchelimsky/rspec/wiki/faq

Resources