testing behaviour of method that scopes outputs into a hash - ruby-on-rails

I have a method which creates a key value pair of delivery costs, the key being the type, and the value being the cost.
def calculate_scoped_job_delivery_costs
delivery_hash = {}
['install', 'fuel', 'breakdown'].each do |scope|
delivery_hash[scope.humanize] = job_delivery_costs.send(scope).inject(0) { |total, item| total + (item.cost_per_unit * item.hour_count) * item.quantity }
end
delivery_hash.delete_if {|key, value| value <= 0 }
end
the key is a scope in the job delivery costs model, which retrieves all associated costs with that scope and adds them up. It works, but I want to test its behaviour, albeit retrospectively.
So its core expected behaviour is:
it should output a hash
it should calculate each scope value
it should remove blank values from the hash
So I have written this test (factories posted below)
let(:jdc1){FactoryGirl.create :job_delivery_cost, job: job, delivery_cost: delivery_cost}
let(:jdc2){FactoryGirl.create :job_delivery_cost, job: job, delivery_cost: delivery_cost}
let(:jdc3){FactoryGirl.create :job_delivery_cost, job: job, delivery_cost: delivery_cost}
describe "calculate_scoped_job_delivery_costs" do
before do
allow(jdc1).to receive(:timing).and_return('fuel')
jdc2.update_attributes(quantity: 4)
jdc2.delivery_cost.update_attributes(timing: 'breakdown')
allow(job).to receive(:job_delivery_costs).and_return(JobDeliveryCost.where(id: [jdc1,jdc2,jdc3].map{|jdc| jdc.id}))
end
it "should retrieve a hash with jdc scopes" do
expect(job.calculate_scoped_job_delivery_costs.is_a?(Hash)).to be_truthy
end
it "should calculate each hash value" do
expect(job.calculate_scoped_job_delivery_costs).to eq "Fuel"=>15.0
end
it "should remove blank values from hash" do
expect(job.calculate_scoped_job_delivery_costs).to_not include "Breakdown"=>0
end
end
So in the last test, it passes, why? I have purposefully tried to make it break by updating the attributes in the before block on jdc2 so that breakdown is another scoped value.
Secondly, by changing the state of jdc2 and its values, this should break test 2 as fuel is no longer calculated against the same values.
Here are my factories...
FactoryGirl.define do
factory :job_delivery_cost do
job
delivery_cost
cost_per_unit 1.5
quantity 3
hour_count 1.0
end
end
FactoryGirl.define do
factory :delivery_cost do
title
timing "Fuel"
cost_per_unit 1.5
end
end
FactoryGirl.define do
factory :job do
job_type
initial_contact_id_placeholder {FactoryGirl.create(:contact).id}
title "random Title"
start "2013-10-04 11:21:24"
finish "2013-10-05 11:21:24"
delivery "2013-10-04 11:21:24"
collection "2013-10-05 11:21:24"
delivery_required false
collection_required false
client { Client.first || FactoryGirl.create(:client) }
workflow_state "offer"
admin
end
end
job has_many :job_delivery_costs.
job_delivery_cost belongs_to :delivery_cost
has_many :job_delivery_costs
has_many :jobs, through: :job_delivery_costs
I am really struggling with the logic of these tests, I am sure there are more holes than what I have laid out above. I welcome criticism in that regard.
thanks

A couple of suggestions:
Remember that let is lazy-evaluated; factories within the block are not created until the symbol defined by let is encountered in the code. This can have unexpected consequences for things like scopes, where you might think that the database already includes your factory-generated rows. You can get around this by using let!, which is evaluated immediately, or by restructuring the spec to ensure things get created in the right order.
I prefer not to do partial stubbing where it can be avoided. For scopes, you are probably better off using factories and just letting the scopes retrieve the rows instead of stubbing the relations. In your case this means getting rid of the code in the before block and setting up each example with factories, so that the scopes are retrieving the expected values.

Related

the right way to change the associated object in rspec

I recently started to test with rspec, so I can strongly be mistaken, correct me if there is a better way
I create two related models
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user}
and before one of the tests change one of the related objects
context "when" do
before {participation.prize = 100}
it "" do
binding.pry
end
end
But inside it
participation.prize => 100
user.participatons.select(:prize) => nil
what am I doing wrong ? and how to fix it?
When you say user.participations.select(:prize), you're making a query to the db to get values in the user's participations' prize columns. But when you say before {participation.prize = 100} you're only setting the prize attribute on the participation object. Try saving the participation before the select line:
participation.prize # => 100
participation.save
user.participatons.select(:prize) # => nil
Another possible issue is that user.participations has been memoized by a previous call. Ensure that user.participations.first == participation. If it doesn't, check
1) puts participation.user_id and
2) puts user.participations, user.reload.participations
Lastly, a better way of setting up the test so that you run into this issue less often is something along the lines of:
# let(:price) { 0 } # default price. Optional so that tests won't throw errors if you forget to set it in a context/describe block.
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user, price: price}
# ...
context "when ..." do
let(:price) { 100 }
it "" do
binding.pry
end
end
This way, the price is set when you create the model. Following this pattern generally means running into this problem less.

failure with creating an instance in specs

I have a model Ticket which has department_id, and Department with
enum name: { dept1: 0, dept2: 1, dept3: 2 }
I have seeded db with these three departments
Department.create(name: :dept1)
Department.create(name: :dept2)
Department.create(name: :dept3)
So I try to write specs on Ticket method
def dept
self.department.name.humanize
end
here is an example
describe '.dept' do
let!(:ticket){ create :ticket, department_id: Department.first.id }
it 'should return right dept' do
expect(ticket.dept).to eq 'Dept1'
end
end
And I recieve an error
ActiveRecord::RecordInvalid:
Validation failed: Department can't be blank
I'm a new guy to rails, so please i9f you don't mind explain me how to write such specs( with seeded db). Any advises would be very useful for me. Thanks!
You'll want to refrain from seeding your database and instead create records that you need for each test.
describe '#dept' do
let(:department) { create :department, title: 'dept1' }
let(:ticket) { build :ticket, department: department }
it 'should return right dept' do
expect(ticket.dept).to eq 'Dept1'
end
end
Notice that I also changed ticket so it's generated by build instead of create. Based on what I see, it doesn't look like you need the overhead of persisting ticket to the database in order to run this particular test.
Also, another small point... But the "convention" (if such a thing exists) is to describe instance methods with hashes in front of them instead of a dot. (Dot denotes a class method.)

Unit Testing Tire (Elastic Search) - Filtering Results with Method from to_indexed_json

I am testing my Tire / ElasticSearch queries and am having a problem with a custom method I'm including in to_indexed_json. For some reason, it doesn't look like it's getting indexed properly - or at least I cannot filter with it.
In my development environment, my filters and facets work fine and I am get the expected results. However in my tests, I continuously see zero results.. I cannot figure out where I'm going wrong.
I have the following:
def to_indexed_json
to_json methods: [:user_tags, :location_users]
end
For which my user_tags method looks as follows:
def user_tags
tags.map(&:content) if tags.present?
end
Tags is a polymorphic relationship with my user model:
has_many :tags, :as => :tagable
My search block looks like this:
def self.online_sales(params)
s = Tire.search('users') { query { string '*' }}
filter = []
filter << { :range => { :created_at => { :from => params[:start], :to => params[:end] } } }
filter << { :terms => { :user_tags => ['online'] }}
s.facet('online_sales') do
date :created_at, interval: 'day'
facet_filter :and, filter
end
end
end
I have checked the user_tags are included using User.last.to_indexed_json:
{"id":2,"username":"testusername", ... "user_tags":["online"] }
In my development environment, if I run the following query, I get a per day list of online sales for my users:
#sales = User.online_sales(start_date: Date.today - 100.days).results.facets["online_sales"]
"_type"=>"date_histogram", "entries"=>[{"time"=>1350950400000, "count"=>1, "min"=>6.0, "max"=>6.0, "total"=>6.0, "total_count"=>1, "mean"=>6.0}, {"time"=>1361836800000, "count"=>7, "min"=>3.0, "max"=>9.0, "total"=>39.0, "total_count"=>7, "mean"=>#<BigDecimal:7fabc07348f8,'0.5571428571 428571E1',27(27)>}....
In my unit tests, I get zero results unless I remove the facet filter..
{"online_sales"=>{"_type"=>"date_histogram", "entries"=>[]}}
My test looks like this:
it "should test the online sales facets", focus: true do
User.index.delete
User.create_elasticsearch_index
user = User.create(username: 'testusername', value: 'pass', location_id: #location.id)
user.tags.create content: 'online'
user.tags.first.content.should eq 'online'
user.index.refresh
ws = User.online_sales(start: (Date.today - 10.days), :end => Date.today)
puts ws.results.facets["online_sales"]
end
Is there something I'm missing, doing wrong or have just misunderstood to get this to pass? Thanks in advance.
-- EDIT --
It appears to be something to do with the tags relationship. I have another method, ** location_users ** which is a has_many through relationship. This is updated on index using:
def location_users
location.users.map(&:id)
end
I can see an array of location_users in the results when searching. Doesn't make sense to me why the other polymorphic relationship wouldn't work..
-- EDIT 2 --
I have fixed this by putting this in my test:
User.index.import User.all
sleep 1
Which is silly. And, I don't really understand why this works. Why?!
Elastic search by default updates it's indexes once per second.
This is a performance thing because committing your changes to Lucene (which ES uses under the hood) can be quite an expensive operation.
If you need it to update immediately include refresh=true in the URL when inserting documents. You normally don't want this since committing every time when inserting lots of documents is expensive, but unit testing is one of those cases where you do want to use it.
From the documentation:
refresh
To refresh the index immediately after the operation occurs, so that the document appears in search results immediately, the refresh parameter can be set to true. Setting this option to true should ONLY be done after careful thought and verification that it does not lead to poor performance, both from an indexing and a search standpoint. Note, getting a document using the get API is completely realtime.

Passing a simple test

I'm using Rails 3.2's rake tests function. I'm trying to pass a test but it's giving me errors. Btw, when see you how I write, I'm a noob. It's a hacked way of testing, but at least I want to try passing it first.
test "product title must have at least 10 characters" do
ok = %w{ aaaaaaaaaa aaaaaaaaaaa }
bad = %w{ a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa}
ok.each do |name|
assert new_product_title(name).valid?, "#{name} shouldn't be invalid"
end
bad.each do |name|
assert new_product_title(name).invalid?, "#{name} shouldn't be valid"
end
end
with the function
def new_product_title(title)
Product.new(title: title,
description: "yyy",
price: 1,
image_url: "fred.gif")
end
somehow it's not passing.
What's the reason here? And is there a better way to write it?
I'm more concerned about the method. I'm assuming this method is in a product model? It seems what you are trying to do should definitely be controlled by the model, but I don't think you can call a class's method inside the class's definition. I also don't see much utility in a method that creates a new product with specified title, but static description, price, and image_url. If you need default values for specific attributes, you can set those in an initialize method and overwrite them later if needed. Some people frown on setting defaults in initialize so instead you can set them in an after_initialize callback like this:
class Product < ActiveRecord::Base
after_initialize :init
def init
self.description ||= 'yyy'
self.price ||= 1
self.image_url ||= "fred.gif"
end
end
Then whenever you needed to create a new product with a title and the default attributes you can just use
Product.new(:title => "some title")
And if you don't want all the defaults you can just pass the values into new like usual
Product.new(:title => "some other title", :price => 400) # desc & url are still default
About your tests. I always test in RSpec. Since you are using Test Unit (or Mini Test or whatever it is now), my advice my not be correct. But first I would make the variable names more descriptive. Secondly, there are some commas at the end of your assertions that shouldn't be there.
test "product title must have at least 10 characters" do
valid_name = "a" * 10
short_name = "a" * 9
valid_product = Product.new(:name => valid_name)
assert valid_product.valid?
invalid_product = Product.new(:name => short_name)
assert invalid_product.invalid?
end
If you get that working you may want to verify that the product is invalid for the correct reason using an assert equals method on invalid_product.errors.full_messages and the expected string from the error.

Moching rails association methods

Here is my helper method which I want to test.
def posts_correlation(name)
if name.present?
author = User.find_by_name(name)
author.posts.count * 100 / Post.count if author
end
end
A factory for user.
factory :user do
email 'user#example.com'
password 'secret'
password_confirmation { password }
name 'Brian'
end
And finally a test which permanently fails.
test "should calculate posts count correlation" do
#author = FactoryGirl.create(:user, name: 'Jason')
#author.posts.expects(:count).returns(40)
Post.expects(:count).returns(100)
assert_equal 40, posts_correlation('Jason')
end
Like this.
UsersHelperTest:
FAIL should calculate posts count correlation (0.42s)
<40> expected but was <0>.
test/unit/helpers/users_helper_test.rb:11:in `block in <class:UsersHelperTest>'
And the whole problem is that mocha doesn't really mock the count value of author's posts, and it returns 0 instead of 40.
Are there any better ways of doing this: #author.posts.expects(:count).returns(40) ?
When your helper method runs, it's retrieving its own object reference to your author, not the #author defined in the test. If you were to puts #author.object_id and puts author.object_id in the helper method, you would see this problem.
A better way is to pass the setup data for the author in to your mocked record as opposed to setting up expectations on the test object.
It's been a while since I used FactoryGirl, but I think something like this should work:
#author = FactoryGirl.create(:user, name: 'Jason')
(1..40).each { |i| FactoryGirl.create(:post, user_id: #author.id ) }
Not terribly efficient, but should at least get the desired result in that the data will actually be attached to the record.

Resources