Hi I am learning to write test cases with Rspec in ruby and am following this link. So while testing the following case
require 'spec_helper'
describe "Library object" do
before :all do
lib_obj = [
Book.new ("Javascript: The Good Parts", "Douglas Crockford", :development),
Book.new ("Designing with Web Standards", "Jeffrey Zeldman", :design),
Book.new ("Don't make me Think", "Steve krug", :usability),
Book.new ("Javascript Patterns", "Stoyam Stefanov", :development),
Book.new ("Responsive Web Design", "Ethan Marcotte", :design)
]
File.open "books.yml", "w" do |f|
f.write YAML::dump lib_obj
end
end
before :each do
#lib = Library.new "books.yml"
end
describe "#new" do
context "with no parameters" do
it "has no books" do
lib = Library.new
lib.should have(0).books
end
end
context "with a yaml file paramater" do
it "has five books" do
#lib.should have(5).books
end
end
end
it "returns all the books in a given category" do
#lib.get_books_in_category(:development).length.should == 2
end
it "accepts new books" do
#lib.add_book(Book.new("Designing for the Web", "Mark Boulton", :design))
#lib.get_book("Designing for the Web").should 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 }
books.should eql books2
end
end
I am getting the following error:-
syntax error, unexpected ',', expecting keyword_end
Book.new ("Don't make me Think", "Steve krug", :usability),
This stands for all the entries in array lib_obj.
I am using ruby 1.9.3 and rails 3.2.6
Kindly help
You have an extra space between your method calls and argument lists.
Book.new (...)
...is not the same as:
Book.new(...)
I see you are passing in string value's for a new Book class although you are not specifying the attribute you want to bind it to.
try:
new_book = Book.new(:name => "string", :cover_image => "string") etc.
also make sure your model validations and mass_assignment security are set correctly but your test will point you there soon.
Related
I'm trying to write rspec tests for my spree customizations and i need to create products with variants. i cant seem to do this even though i appear to be doing the exact same thing as the rspec tests that are part of spree core.
def build_option_type_with_values(name, values)
ot = create(:option_type, :name => name)
values.each do |val|
ot.option_values.create(:name => val.downcase, :presentation => val)
end
ot
end
let(:number_size_option_type) do
size = build_option_type_with_values("number sizes", %w(1 2 3 4))
end
let(:product1) { create(:product, name: 'product1') }
it "should have variants" do
hash = {number_size_option_type.id.to_s => number_size_option_type.option_value_ids}
product1.option_values_hash = hash
product1.save
product1.reload
expect(product1.variants.length).to eq(4)
end
no matter what i do, the number of variants for my product is always zero.
Turns out the product.option_values_hash needs to be added during product creation in order to invoke the variant creation code. here is the changed line and then i removed the hash from the test "should have variant"
let(:product1) { create(:product, name: 'product1', option_values_hash: {number_size_option_type.id.to_s => number_size_option_type.option_value_ids}) }
it "should have variants" do
product1.save
expect(product1.option_type_ids.length).to eq(1)
expect(product1.variants.length).to eq(4)
end
I know this isn't really related to the actual code itself, but I'm just starting off with Rspec and am having trouble making the tests sound like English. I think it would improve the quality of my tests a lot if I understood what to put in the strings I pass to the it and describe blocks. Ex:
Category
validations
is valid with a title and description
is invalid without a title
is invalid without a description
when it has subcategories
returns the right children subcategories
should be accessible by its subcategories
when it has no subcategories
returns an empty set
This is what's coming out of my Category specs. Is there a certain way/pattern I have to write the strings that I pass to it and describe? For you Rspec experts, what do you usually think of when you're writing the describe string, and how does that differ from when you're writing the it string or context string? Below is an example of my specs in case you need some actual code to work with:
describe 'validations' do
let(:category) { Category.new }
it 'is valid with a title and description' do
category.title = 'Category'
category.description = 'Lorem Ipsum'
category.should be_valid
end
it 'is invalid without a title' do
category.description = 'Lorem Ipsum'
category.should_not be_valid
end
it 'is invalid without a description' do
category.title = 'Category'
category.should_not be_valid
end
end
context 'when it has subcategories' do
let(:category) { FactoryGirl.create(:category) }
it 'returns the right children subcategories' do
child = category.subcategories.build(title: 'Child Category', description: 'Lorem Ipsum')
category.subcategories.should include(child)
end
it 'should be accessible by its subcategories' do
child = category.subcategories.build(title: 'Child Category', description: 'Lorem Ipsum')
child.parent_category.should_not be_nil
end
end
context 'when it has no subcategories' do
let(:category) { FactoryGirl.create(:category) }
it 'returns an empty set' do
category.subcategories.should be_empty
end
end
Basically:
describe is for "something". "Something" can be a instance or class method, or an action in features specs. ".method_name" if it is a class method and "#method_name" if it is a instance method.
context is for describe a special case of spec (context is an alias for describe). Usually start with "when".
it is what does 'something'. Usually start with "should".
describe ".to_s"
context "when is a number"
it "convert the number in a string"
context "when is a string"
it "return the same object"
But this is not strictly. Here's a guidance:
https://www.relishapp.com/rspec/rspec-core/docs/example-groups/basic-structure-describe-it
http://blog.teamtreehouse.com/an-introduction-to-rspec
http://eggsonbread.com/2010/03/28/my-rspec-best-practices-and-tips/
I generally begin my test case with should. So your test run will have something like
Category
validations
should be valid with a title and description
should be invalid without a title
should be invalid without a description
with subcategories
should return the right children subcategories
should be accessible by its subcategories
without subcategories
should return an empty set
I find it easier to understand the functionality when it is like above.
I'm using RSpec (2.10.1) to test validations on a model and have extracted some code to share with other model validations. The validations were first written on the Companies table, so the code looks like this:
# support/shared_examples.rb
shared_examples "a text field" do |field, fill, length|
it "it should be long enough" do
#company.send("#{field}=", fill * length)
#company.should be_valid
end
etc...
end
and the usage is:
# company_spec.rb
describe Company do
before { #company = Company.new( init stuff here ) }
describe "when address2" do
it_behaves_like "a text field", "address2", "a", Company.address2.limit
end
etc...
end
I'd like to pass the #company as a parameter to the shared example so I can reuse the code for different models, something like this:
# support/shared_examples.rb
shared_examples "a text field" do |model, field, fill, length|
it "it should be long enough" do
model.send("#{field}=", fill * length)
model.should be_valid
end
etc...
end
and the usage is:
# company_spec.rb
describe Company do
before { #company = Company.new( init stuff here ) }
describe "when address2" do
it_behaves_like "a text field", #company, "address2", "a", Company.address2.limit
end
etc...
end
However, when I do this I get undefined method 'address2' for nil:NilClass. It appears #company is not being passed (not in scope?) How do I get something like this to work?
The problem is that self within the example group is different from self within a before hook, so it's not the same instance variable even though it has the same name.
I recommend you use let for cases like these:
# support/shared_examples.rb
shared_examples "a text field" do |field, fill, length|
it "it should be long enough" do
model.send("#{field}=", fill * length)
model.should be_valid
end
end
# company_spec.rb
describe Company do
describe "when address2" do
it_behaves_like "a text field", "address2", "a", Company.address2.limit do
let(:model) { Company.new( init stuff here ) }
end
end
end
How can I match the following array with Rspec ?
[#<struct Competitor html_url="https://github.com/assaf/vanity", description="Experiment Driven Development for Ruby", watchers=845, forks=146>,
#<struct Competitor html_url="https://github.com/andrew/split", description="Rack Based AB testing framework", watchers=359, forks=43>]
I need to check if a class method return an array of struct like the previous or a more extensive one which include the previous.
UPDATE:
I currently have this test which go green,
require 'spec_helper'
describe "Category" do
before :each do
#category = Category.find_by(name: "A/B Testing")
end
describe ".find_competitors_by_tags" do
it "returns a list of competitors for category" do
competitors = Category.find_competitors_by_tags(#category.tags_array).to_s
competitors.should match /"Experiment Driven Development for Ruby"/
end
end
end
end
but I'd like to know if it is the correct way to test the following method or you think it could be better :
class Category
...
Object.const_set :Competitor, Struct.new(:html_url, :description, :watchers, :forks)
def self.find_competitors_by_tags(tags_array)
competitors = []
User.all_in('watchlists.tags_array' => tags_array.map{|tag|/^#{tag}/i}).only(:watchlists).each do |u|
u.watchlists.all_in(:tags_array => tags_array.map{|tag|/^#{tag}/i}).desc(:watchers).each do |wl|
competitors << Competitor.new(wl.html_url, wl.description, wl.watchers, wl.forks)
end
end
return competitors
end
end
I would test the minimum needed to make sure that your find function works correctly. You probably don't need to check every field for the returned records for that. What you have does that. I'd modify it a bit, to just look at the description (or whatever other field is appropriate):
it "returns a list of competitors for category" do
competitors = Category.find_competitors_by_tags(#category.tags_array)
descriptions = competitors.map(&:description).sort
descriptions.should == [
"Experiment Driven Development for Ruby",
"Rack Based AB testing framework",
]
end
Here is what I have:
context "Create ingredient from string" do
context "1 cups butter" do
setup do
#ingredient = Ingredient.create(:ingredient_string => "1 cups butter")
end
should "return unit" do
assert_equal #ingredient.unit, 'cups'
end
should "return amount" do
assert_equal #ingredient.amount, 1.0
end
should "return name" do
assert_equal #ingredient.name, 'butter'
end
end
context "1 (18.25 ounce) package devil's food cake mix with pudding" do
setup do
#ingredient = Ingredient.create(:ingredient_string => "1 (18.25 ounce) package devil's food cake mix with pudding")
end
should "return unit" do
assert_equal #ingredient.unit, '(18.25 ounce) package'
end
should "return amount" do
assert_equal #ingredient.amount, 1.0
end
should "return name" do
assert_equal #ingredient.name, 'devil\'s food cake mix with pudding'
end
end
end
Clearly there is a lot of duplication there. Any thoughts on how to remove it, if only at the very least the context and the string?
Here's a solution to your specific problem. The idea is to create a class method (like Shoulda's context, setup and should).
Encapsulate the repetition in a class method accepting all varying parts as arguments like this:
def self.should_get_unit_amount_and_name_from_string(unit, amount, name, string_to_analyze)
context string_to_analyze do
setup do
#ingredient = Ingredient.create(:ingredient_string => string_to_analyze)
end
should "return unit" do
assert_equal #ingredient.unit, unit
end
should "return amount" do
assert_equal #ingredient.amount, amount
end
should "return name" do
assert_equal #ingredient.name, name
end
end
end
Now you can call all these encapsulated tests with one liners (5-liners here for readability ;-)
context "Create ingredient from string" do
should_get_unit_amount_and_name_from_string(
'cups',
1.0,
'butter',
"1 cups butter")
should_get_unit_amount_and_name_from_string(
'(18.25 ounce) package',
1.0,
'devil\'s food cake mix with pudding',
"1 (18.25 ounce) package devil's food cake mix with pudding")
end
In some cases, you may want to accept a block which could serve as your Shoulda setup.
Duplication in tests is not necessarily a Bad Thing(tm)
I suggest you read the following articles from Jay Field
http://blog.jayfields.com/2007/06/testing-one-assertion-per-test.html
http://blog.jayfields.com/2008/05/testing-duplicate-code-in-your-tests.html
They make a convinving case for code duplication in the tests and keeping one assertion per test.
Tests/specs are not production code and so being dry is not a priority.
The principle is that the specs should be clear to read, even if it means there is duplication of text across tests.
Don't be too concerned about specs being dry. Overemphasis of dry tests tends to make things more difficult as you have to jump around to the definitions of things to understand what is happening.
Personally for this test, I wouldn't use Shoulda.
You can easily remove duplication by using dynamic method creation as follows:
class DefineMethodTest < Test::Unit::TestCase
[{:string => '1 cups butter', :unit => 'cups', :amount => 1.0, :name => 'butter'},{:string => '1 (18.25 ounce) package devil's food cake mix with pudding', :unit => '(18.25 ounce) package', :unit => 1.0, :name => "devil's food cake mix with pudding"}].each do |t|
define_method "test_create_ingredient_from_string_#{t[:string].downcase.gsub(/[^a-z0-9]+/, '_')}" do
#ingredient = Ingredient.create(:ingredient_string => t[:string])
assert_equal #ingredient.unit, t[:unit], "Should return unit #{t[:unit]}"
assert_equal #ingredient.amount, t[:amount], "Should return amount #{t[:amount]}"
assert_equal #ingredient.name, t[:name], "Should return name #{t[:name]}"
end
end
end