Matching a complex data structure (an array of structs) with Rspec - ruby-on-rails

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

Related

Writing test for Spree, cant create variants for products

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

How to test correctness of arguments sent to external API

I've external API endpoint, let's say: http://www.fake_me_hard.com/api. I would like to make some calls to this from my app.
Endpoint accepts following structure as argument:
{
:amount => amount,
:backurl => root_path,
:language => locale,
:orderid => order_id,
:pm => payment_method,
:accept_url => "/payment/success",
:exception_url => "/payment/failure",
}
For collecting this hash is responsible method EndpointRequestCollector.give_me_hash.
How I should test if give_me_hash returns proper structure ?
I can use the same strategy for creating this structure in specs and class as well so:
class EndpointRequestsCollector
def self.give_me_hash
{
#....collecting hash #1
}
end
end
describe EndpointRequestCollector do
context '.give_me_hash' do
it 'returns proper structure' do
expect(described_class.give_me_hash).to eq(
{
#... collecting hash #2
}
)
end
end
end
...but it would be repeating the same code in 2 places, and won't test anything.
Do you know any good approach to this problem ?
This is the way that i usually test my json api's:
If you just want to test the format, you can use include matcher:
%w(my awesome keys).each do |expected_key|
expect(described_class.give_me_hash.keys).to include(expected_key)
end
By doing this, you have the guarantee that the formar is correct, until someone break you method.
If you want to test the returned values, you can use something like that:
let(:correct_value) { 42 }
it 'must have correct value' do
expect(described_class.give_me_hash[key]). to eq correct_value
end
But i recomment you to separate this the logic to get the value to another method, and make another test just for it.
Perhaps:
let(:args) {["amount", "backurl", "language", "orderid", "pm", "accept_url", "exception_url"]}
#...
it 'returns proper structure' do
described_class.give_me_hash.each_key do |key|
expect(key).to satisfy{|key| args.include?(key)}
end
end

Testing an expected order of an array in RSpec / Rails

In a RSpec spec file I have the following test
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.find(:all,
:select => ["*,(abs(rating - current_user.rating)) as player_rating"],
:order => "player_rating",
:limit => 5)
# test that matched_players array returns what it is suppose to
end
How would I complete this to test that matched_players is returning the correct users.
I think you should first introduce some test users to the test DB (using for example a Factory) and afterwards see that the test is returning the correct ones.
Also it would make more sense to have a method in your model that would return the matched users.
For example:
describe "Player matching" do
before(:each) do
#user1 = FactoryGirl.create(:user, :rating => 5)
...
#user7 = FactoryGirl.create(:user, :rating => 3)
end
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.matched_players
matched_players.should eql [#user1,#user3,#user4,#user5,#user6]
end
end
Your model shouldn't know about your current user (the controllers know about this concept)
You need to extract this as a method on the User class otherwise there's no point in testing it, i.e. why test logic that isn't even in your app code?
The function that gets the matched players doesn't need to know about the current user, or any user for that matter, just the rating.
To test it, create a bunch of User instances, call the method, and see that the result is a list of the correct user instances you expect.
models/user.rb
class User < ActiveRecord::Base
...
def self.matched_players(current_user_rating)
find(:all,
select: ["*,(abs(rating - #{current_user_rating)) as match_strength"],
order: "match_strength",
limit: 5)
end
...
end
spec/models/user_spec.rb
describe User do
...
describe "::matched_players" do
context "when there are at least 5 users" do
before do
10.times.each do |n|
instance_variable_set "#user#{n}", User.create(rating: n)
end
end
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4.2)
matched_players.should == [#user4, #user5, #user3, #user6, #user2]
end
context "when multiple players have ratings close to the given rating and are equidistant" do
# we don't care how 'ties' are broken
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4)
matched_players[0].should == #user4
matched_players[1,2].should =~ [#user5, #user3]
matched_players[3,4].should =~ [#user6, #user2]
end
end
end
context "when there are fewer than 5 players in total" do
...
end
...
end
...
end

Error writing test case in rspec

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.

How do I remove duplication in shoulda tests?

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

Resources