Testing an expected order of an array in RSpec / Rails - ruby-on-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

Related

Checking The WHERE CLAUSE clause in rspec testing

I have a named scope in rails and i have model by name Product
class Product < ApplicationRecord
scope :old_products, -> { where("tagged_with = ?","old") }
end
Have any body encountered the process of checking the subject which is using where in the active record and that can check the what where clause does the named scope actually holds
In rspec spec/models/product_spec.rb
describe Product do
describe "checking scope clauses" do
subject { Product.old_products }
its(:where_clauses) { should eq([
"tagged_with = 'old'"
]) }
end
end
end
By the way i use rspec-2.89 version with the rails-5 version so any chances that we can check and verify the where clauses
I personally don't think checking returned SQL of a scope is sufficient. The way I would test old_products is:
describe Product do
describe "scopes" do
describe "old_products" do
let!(:old_product) {Product.create(tagged_with: 'old')}
let!(:not_old_product) {Product.create(tagged_with: 'sth_else')}
subject { Product.old_products }
it "returns the product(s) with tagged_with = old" do
expect(subject).to eq([old_product])
end
end
end
end
If you still want to check the return query, may want to try:
it "..." do
expect(subject.to_sql).to eq("SELECT \"products\".* FROM \"products\" WHERE \"products\".\"tagged_with\" = 'old'")
end
# This is better, more readable syntax for scope declaration
class Product < ApplicationRecord
scope :old_products, -> { where(tagged_with: 'old') }
end
# Something like this would work
describe Product do
context 'scopes' do
# Set up something that will always be excluded from the scopes
let!(:product) { create :product }
let!(:scoped_list) { create :product, 3, tagged_with: tag }
shared_examples_for 'returns scoped records' do
# This should work with shoulda-matchers
# (https://github.com/thoughtbot/shoulda-matchers)
# Could also not use subject and do something like:
# expect(
# described_class.send(scope_name.to_sym)
# ).to contain_exactly(scoped_list)
# and declare let(:scope_name) in your describe blocks
it 'returns scoped products' do
should contain_exactly(scoped_list)
end
end
describe '.old_products' do
subject(:old_products) { described_class.old_products }
let(:tag) { 'old' }
it_behaves_like 'returns scoped records'
end
describe '.other_scope' do
subject(:other_scope) { described_class.other_scope }
let(:tag) { 'other_tag' }
it_behaves_like 'returns scoped records'
end
end
end
There is no value in testing the actual SQL -- it's generated by Rails; what you want to test is that your scope is returning the correct objects
I would use context instead of describe when declaring the scopes test block because you're not describing a class or instance method
Use single quotes instead of double quotes unless you are doing string interpolation -- it's more performant
If you're early in the project and each Product only has a single tag, I would also rename the tagged_with column to be tag
describe Product do
describe "checking scope clauses" do
subject { Product.old_products }
expect(subject.values[:where].instance_values['predicates'].to eq(["tagged_with = 'old'"])
end
end

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

Testing association validations with RSpec and FactoryGirl

I'm currently trying to write an RSpec test for a validation method. This method is triggered when the record is updated, saved or created. Here is what I have so far:
product.rb (model)
class Product < ActiveRecord::Base
validate :single_product
# Detects if a product has more than one SKU when attempting to set the single product field as true
# The sku association needs to map an attribute block in order to count the number of records successfully
# The standard self.skus.count is performed using the record ID, which none of the SKUs currently have
#
# #return [boolean]
def single_product
if self.single && self.skus.map { |s| s.active }.count > 1
errors.add(:single, " product cannot be set if the product has more than one SKU.")
return false
end
end
end
products.rb (FactoryGirl test data)
FactoryGirl.define do
factory :product do
sequence(:name) { |n| "#{Faker::Lorem.word}#{Faker::Lorem.characters(8)}#{n}" }
meta_description { Faker::Lorem.characters(10) }
short_description { Faker::Lorem.characters(15) }
description { Faker::Lorem.characters(20) }
sku { Faker::Lorem.characters(5) }
sequence(:part_number) { |n| "GA#{n}" }
featured false
active false
sequence(:weighting) { |n| n }
single false
association :category
factory :product_skus do
after(:build) do |product, evaluator|
build_list(:sku, 3, product: product)
end
end
end
end
product_spec.rb (unit test)
require 'spec_helper'
describe Product do
describe "Setting a product as a single product" do
let!(:product) { build(:product_skus, single: true) }
context "when the product has more than one SKU" do
it "should raise an error" do
expect(product).to have(1).errors_on(:single)
end
end
end
end
As you can see from the singe_product method, I'm trying to trigger an error on the single attribute when the single attribute is set to true and the product has more than one associated SKU. However, when running the test the product has no associated SKUs and therefore fails the unit test shown above.
How do I build a record and generate associated SKUs which can be counted (e.g: product.skus.count) and validated before they are all created in FactoryGirl?
You could write this like
it 'should raise an error' do
product = build(:product_skus, single: true)
expect(product).not_to be_valid
end

Matching a complex data structure (an array of structs) with Rspec

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

rails, `flash[:notice]` in my model?

I have a user model in which I have a method for seeing if the user has earned a "badge"
def check_if_badges_earned(user)
if user.recipes.count > 10
award_badge(1)
end
If they have earned a badge, the the award_badge method runs and gives the user the associated badge. Can I do something like this?
def check_if_badges_earned(user)
if user.recipes.count > 10
flash.now[:notice] = "you got a badge!"
award_badge(1)
end
Bonus Question! (lame, I know)
Where would the best place for me to keep all of these "conditions" for which my users could earn badges, similar to stackoverflows badges I suppose. I mean in terms of architecture, I already have badge and badgings models.
How can I organize the conditions in which they are earned? some of them are vary complex, like the user has logged in 100 times without commenting once. etc. so there doesn’t seem to be a simple place to put this sort of logic since it spans pretty much every model.
I'm sorry for you but the flash hash is not accessible in models, it gets created when the request is handled in your controller. You still can use implement your method storing the badge infos (flash message included) in a badge object that belongs to your users:
class Badge
# columns:
# t.string :name
# seed datas:
# Badge.create(:name => "Recipeador", :description => "Posted 10 recipes")
# Badge.create(:name => "Answering Machine", :description => "Answered 1k questions")
end
class User
#...
has_many :badges
def earn_badges
awards = []
awards << earn(Badge.find(:conditions => { :name => "Recipeador" })) if user.recipes.count > 10
awards << earn(Badge.find(:conditions => { :name => "Answering Machine" })) if user.answers.valids.count > 1000 # an example
# I would also change the finds with some id (constant) for speedup
awards
end
end
then:
class YourController
def your_action
#user = User.find(# the way you like)...
flash[:notice] = "You earned these badges: "+ #user.earn_badges.map(:&name).join(", ")
#...
end
end

Resources