Rspec to have(n).items undefined method - ruby

I'm trying to follow a guide on code.tuts and I keep getting an error.
Here is my Library spec:
require 'spec_helper'
describe Library do
before :all do
lib_arr = [
Book.new("JavaScript: The Good Parts", "Douglas Crockford", :development),
Book.new("Dont Make me Think", "Steve Krug", :usability),
]
File.open "books.yml", "w" do |f|
f.write YAML::dump lib_arr
end
end
before :each do
#lib = Library.new "books.yml"
end
describe "#new" do
context "with no parameters" do
it "has no book" do
lib = Library.new
expect(lib).to have(0).books
end
end
context "with a yaml file name parameters" do
it "has two books" do
expect(#lib).to_have(0).books
end
end
end
it "returns all the books in a given category" do
expect(#lib.get_books_in_category(:development).length).to eql 1
end
it "accepts new books" do
#lib.add_book(Book.new("Designing for the Web", "Mark Boulton", :design))
expect(#lib.get_book("Designing for the Web")).to be_an_instance_of Book
end
it "saves the library" do
books = #lib.books.map { |book| book.title}
#lib.save
lib2 = Library.new 'books.yml'
books2 = lib2.books.map { |book| book.title }
expect(books).to eql books2
end
end
I'm getting that have is undefined. I've figured out it's my lines
expect(#lib).to have(0).books
expect(lib).to have(0).books
Is my syntax out of date? I've googled and I can't find it.

The have/have_exactly, have_at_least and have_at_most matchers were removed from RSpec 3. They're now in the separate rspec-collection_matchers gem.
Or, as zishe says, instead of installing the gem, you can just use eq instead of have/have_exactly, and be >= instead of have_at_least and be <= instead of have_at_most.
Source: http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3

Related

RSpec why is before(:each) never executed?

I have this simple code
require 'json'
module Html
class JsonHelper
attr_accessor :path
def initialize(path)
#path = path
end
def add(data)
old = JSON.parse(File.read(path))
merged = old.merge(data)
File.write(path, merged.to_json)
end
end
end
and this spec (reduced as much as I could while still working)
require 'html/helpers/json_helper'
describe Html::JsonHelper do
let(:path) { "/test/data.json" }
subject { described_class.new(path) }
describe "#add(data)" do
before(:each) do
allow(File).to receive(:write).with(path, anything) do |path, data|
#saved_string = data
#saved_json = JSON.parse(data)
end
subject.add(new_data)
end
let(:new_data) { { oldestIndex: 100 } }
let(:old_data) { {"test" => 'testing', "old" => 50} }
def stub_old_json
allow(File).to receive(:read).with(path).and_return(#data_before.to_json)
end
context "when given data is not present" do
before(:each) do
puts "HERE"
binding.pry
#data_before = old_data
stub_old_json
end
it "adds data" do
expect(#saved_json).to include("oldestIndex" => 100)
end
it "doesn't change old data" do
expect(#saved_json).to include(old_data)
end
end
end
end
HERE never gets printed and binding.pry doesn't stop execution and tests fail with message No such file or directory # rb_sysopen - /test/data.json
This all means that before(:each) never gets executed.
Why?
How to fix it?
It does not print desired message because it fails at the first before block. Rspec doc about execution order
It fails because you provided an absolute path, so it is checking /test/data.json
Either use relative path to the test ie. ../data.json (just guessing),
or full path.
In case of rails:
Rails.root.join('path_to_folder_with_data_json', 'data.json')

Writing rspec for controllers with params?

I have a controller which has the following method
class <controllername> < ApplicationController
def method
if params["c"]
.....
elsif params["e"]
.....
else
.....
end
end
end
Now, I want to write rspec for the above code.
How can I write separate context for both the params and how will I mention them as a get method.
If I understand your question correctly, you can try approach like this:
RSpec.describe <controllername>, :type => :controller do
describe "GET my_method" do
context "param 'c' is provided"
get :my_method, { "c" => "sample value" }
expect(response).to have_http_status(:success)
end
context "param 'e' is provided"
get :my_method, { "e" => "sample value" }
expect(response).to have_http_status(:success)
end
end
end
Hope it puts you in proper direction.
Good luck!

Passing an object as subject to rspec

I am running rspec tests on a catalog object from within a Ruby app, using Rspec::Core::Runner::run:
File.open('/tmp/catalog', 'w') do |out|
YAML.dump(catalog, out)
end
...
unless RSpec::Core::Runner::run(spec_dirs, $stderr, out) == 0
raise Puppet::Error, "Unit tests failed:\n#{out.string}"
end
(The full code can be found at https://github.com/camptocamp/puppet-spec/blob/master/lib/puppet/indirector/catalog/rest_spec.rb)
In order to pass the object I want to test, I dump it as YAML to a file (currently /tmp/catalog) and load it as subject in my tests:
describe 'notrun' do
subject { YAML.load_file('/tmp/catalog') }
it { should contain_package('ppet') }
end
Is there a way I could pass the catalog object as subject to my tests without dumping it to a file?
I am not very clear as to what exactly you are trying to achieve but from my understanding I feel that using a before(:each) hook might be of use to you. You can define variables in this block that are available to all the stories in that scope.
Here is an example:
require "rspec/expectations"
class Thing
def widgets
#widgets ||= []
end
end
describe Thing do
before(:each) do
#thing = Thing.new
end
describe "initialized in before(:each)" do
it "has 0 widgets" do
# #thing is available here
#thing.should have(0).widgets
end
it "can get accept new widgets" do
#thing.widgets << Object.new
end
it "does not share state across examples" do
#thing.should have(0).widgets
end
end
end
You can find more details at:
https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#define-before(:each)-block

How to access the describe text in rspec

I'm writing some specs that test the template files in a gem that has generators for Rails. I'd love to access to "admin_layout.html.erb" in the rspec spec below:
require 'spec_helper'
describe "admin_layout.html.erb" do
it "has page title Admin" do
HERES WHERE I WOULD LOVE TO HAVE ACCESS TO "admin_layout.html.erb" AS A VARIABLE
end
end
You can use self.class.description to get this info:
it "has page title Admin" do
layout = self.class.description
# => "admin_layout.html.erb"
end
However, keep in mind this will only put out the first parent's description. So if you have contexts in your describe block, then the examples within the contexts would give the context name for self.class instead of the describe block's name. In that case, you could use metadata:
describe "admin_layout.html.erb", :layout => "admin_layout.html.erb"
context "foo" do
it "has page title Admin" do
layout = example.metadata[:layout]
end
end
end
In case you want the top-level description, you can use self.class.top_level_description:
RSpec.describe "Foo", type: :model do
context "bar" do
it "is part of Foo" do
self.class.top_level_description
# => "Foo"
end
end
end

RSpec: How do I refactor a group of tests that are repeated and the only thing that changes is the subject and expectations

I have a test suite that resembles the situation I describe with the following code. There are two contexts that define the subject. The subject is similar, the same kind of object, but with different values.
Over that subject I run two tests. One test is exactly the same for both and the other is different.
Suggest a refactor that would eliminate duplication, besides the obvious 'move the code to a method', which I don't like because it looses clarity.
require 'rspec'
describe "tests over numbers" do
context 'big numbers' do
subject { 5000 }
describe "#to_string" do
its(:to_s) {should be_a(String)}
end
describe "#+1" do
it "+1" do
sum = subject+1
sum.should == 5001
end
end
end
context 'small numbers' do
subject { 100 }
describe "#to_string" do
its(:to_s) {should be_a(String)}
end
describe "#+1" do
it "+1" do
sum = subject+1
sum.should == 101
end
end
end
end
Maybe shared examples is the way to go?
shared_example "numbers" do
describe "#to_string" do
it "should convert to a string" do
example.to_s.should be_a(String)
end
end
describe "#+1" do
it "should increment" do
sum = example+1
sum.should == example.next
end
end
end
describe "big_numbers" do
it_behaves_like "numbers" do
let(:example) { 5000 }
end
end
describe "small_numbers" do
it_behaves_like "numbers" do
let(:example) { 100 }
end
end
[5000, 100].each do |my_test|
subject { my_test }
describe "#to_string" do
its(:to_s) {should be_a(String)}
end
describe "#+1" do
it "+1" do
sum = subject+1
sum.should == my_test + 1
end
end
end

Resources