Create spec for model with joins associations - ruby-on-rails

how can I do a spec for a model that calls the joins method
I have this code but not working i get errors for the method joins
def applicants_income_average
users.joins(:financial_data).sum(:net_income_verified) /
users.joins(:financial_data).where("financial_data.
net_income_verified IS NOT NULL").size
end
context "#applicants_income_average" do
before do
user_a.build_financial_data(net_income_verified: 12_000)
user_b.build_financial_data(net_income_verified: 13_000)
allow(whitelabel).to receive(:users).and_return([user_a, user_b])
allow(user_a).to receive(:join).and_return(user_a.financial_data)
allow(user_b).to receive(:join).and_return(user_b.financial_data)
end
it "Should return 12_500" do
expect(whitelabel.applicants_income_average).to eql(12_500)
end
end

Admittedly, it may be easier to test ActiveRecord through the database than it is to mock, thanks to ActiveRecord's method chaining. But, if you can put up with the chained allow statements, your tests will be SOOOO much faster if you mock ActiveRecord. Plus, you won't be testing something that has already been thoroughly tested by the Rails core team.
Here's how I would approach the problem above. I annotated some assumptions and modifications I made to the code. Hope it helps!
class Whitelabel
# I modified this for readability, but in the process discovered
# that the same query could probably be used for both the numerator
# and the denominator.
def applicants_income_average
sum_of_verified_net_income / count_of_users_with_verified_net_income
end
private
def sum_of_verified_net_income
users_with_verified_net_income.sum(:net_income_verified)
end
def count_of_users_with_verified_net_income
users_with_verified_net_income.size
end
# Using a single query and memoizing the results will improve performance
# This will also make it easier to test since we'll only call it once
def users_with_verified_net_income
#users_with_verified_net_income ||= do
users
.joins(:financial_data)
.where('financial_data.net_income_verified IS NOT NULL')
end
end
# I'm assuming this method must exist, given that the
# implementation of applicants_income_average referenced it
def users
User.where('some criteria')
end
end
describe Whitelabel do
subject(:whitelabel) { described_class.new }
describe '#applicants_income_average' do
subject(:applicants_income_average) { whitelabel.applicants_income_average }
let(:users) { instance_double('users') }
let(:users_with_financial_data) { instance_double('users_with_financial_data') }
let(:users_with_verified_net_income) do
instance_double('users_with_verified_net_income', sum: sum, size: size )
end
let(:sum) { 25000 }
let(:size) { 2 }
let(:average) { sum / size }
before do
allow(User).to receive(:where).and_return(users)
allow(users).to receive(:joins).and_return(users_with_financial_data)
allow(users_with_financial_data)
.to receive(:where)
.and_return(users_with_verified_net_income)
end
it 'selects the correct users' do
applicants_income_average
expect(User).to have_received(:where).with('some criteria')
end
it 'joins with financial data model' do
applicants_income_average
expect(users).to have_received(:joins).with(:financial_data)
end
it 'selects the users with verified net income' do
applicants_income_average
expect(users_with_financial_data)
.to have_received(:where)
.with('financial_data.net_income_verified IS NOT NULL')
end
it 'calculates average' do
expect(applicants_income_average).to eq(average)
end
end
end

Related

How to skip part of method code in Rails specs

I've got this method
def finalize_inquiry_process(form)
if finalize_process == true
inquiry_process.campaign_code.update(state: 'used')
document_creator_class = InquiryProcessDocumentCreatorFetcher.new(inquiry_process).call
document_creator_class.new(inquiry_process).call
end
Success(form)
end
and I want to skip in specs this part which is really trouble maker, implementation is an unnecessary waste of time (pdf generator with tons of fields)
document_creator_class = InquiryProcessDocumentCreatorFetcher.new(inquiry_process).call
document_creator_class.new(inquiry_process).call
To do so I wrote a specs:
let(:fetcher_instance) { instance_double(InquiryProcessDocumentCreatorFetcher) }
before do
allow(InquiryProcessDocumentCreatorFetcher).to receive(:new).and_return(fetcher_instance)
allow(fetcher_instance).to receive(:call).and_return(nil)
end
it 'updates state of assigned campain code' do
updated_inquiry_process = process_update.value!
expect(updated_inquiry_process.campaign_code.state).to eq('used')
end
end
InquiryProcesses::Update.call campain code updates state of assigned campain code
Failure/Error: document_creator_class.new(inquiry_process).call
NoMethodError:
undefined method `new' for nil:NilClass
Is there any chance to skip this part of code in specs?
Ok I managed it by using receive_message_chain helper. Specs should looked like this:
describe 'finalize inquiry process' do
subject(:process_update) do
described_class.new(
inquiry_process: inquiry_process,
form: loan_application_inquiry_process_update_form,
finalize_process: true,
).call
end
let!(:inquiry_process) do
create :inquiry_process, inquiry_template: loan_inquiry_template, campaign_code_uid: campaign_code.uid
end
before do
allow(InquiryProcessDocumentCreatorFetcher).to receive_message_chain(:new, :call, :new, :call)
end
it 'updates state of assigned campain code' do
updated_inquiry_process = process_update.value!
expect(updated_inquiry_process.campaign_code.state).to eq('used')
end
end
You can try your luck with dependency injection:
(It's a rough sketch, but I don't know the context of the system, not even the whole class)
def initialize(inquiry_process:, form:, finalize_process:, creator_fetcher:)
#creator_fetcher = creator_fetcher
# all the other initializetions
# maybe you can initialize creator_fetcher outside, and no need to pass inquiry_process anymore?
end
def creator_fetcher
#creator_fetcher ||= InquiryProcessDocumentCreatorFetcher.new(inquiry_process)
end
def finalize_inquiry_process(form)
if finalize_process == true
inquiry_process.campaign_code.update(state: 'used')
document_creator_class = creator_fetcher.call
document_creator_class.new(inquiry_process).call
end
Success(form)
end
and then
let(:creator_fetcher) { instance_double(InquiryProcessDocumentCreatorFetcher) }
let(:document_creator_class) { instance_double(whatever_fetcher_call_returns_or_just_unveryfying_double) }
before { allow(creator_fetcher.to_receive(:call).and_return(document_creator_class) }
subject(:process_update) do
described_class.new(
inquiry_process: inquiry_process,
form: loan_application_inquiry_process_update_form,
finalize_process: true,
).call
end
Anyway - your problems with tests show's that your code was not written with the tests, and it's a bad design.
Indirection (Dependency Injection here) might help a little to untangle the mess.

How do I 'expect' a chain of methods using Rspec where the first method takes a parameter?

I have a method call in a ruby model that looks like the following:
Contentful::PartnerCampaign.find_by(vanityUrl: referral_source).load.first
Within the models spec.rb file, I'm trying to mock that call and get a value by passing in a param. But I'm having trouble figuring out the correct way of calling it.
At the top of my spec.rb file I have:
let(:first_double) {
double("Contentful::Model", fields {:promotion_type => "Promotion 1"})
}
Within the describe block I've tried the following:
expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by, :load, :first).
and_return(first_double)
expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by, :load, :first).with(vanityUrl: 'test_promo_path').
and_return(first_double)
expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by => vanityUrl: 'test_promo_path', :load, :first).
and_return(first_double)
As you can probably guess, none of these are working. Does anyone know the correct way to do this sort of thing? Is it even possible?
Generally speaking, I prefer not to use stub chains, as they are often a sign that you are violating the Law of Demeter. But, if I had to, this is how I would mock that sequence:
let(:vanity_url) { 'https://vanity.url' }
let(:partner_campaigns) { double('partner_campaigns') }
let(:loaded_partner_campaigns) { double('loaded_partner_campaigns') }
let(:partner_campaign) do
double("Contentful::Model", fields {:promotion_type => "Promotion 1"}
end
before do
allow(Contentful::PartnerCampaign)
.to receive(:find_by)
.with(vanity_url: vanity_url)
.and_return(partner_campaigns)
allow(partner_campaigns)
.to receive(:load)
.and_return(loaded_partner_campaigns)
allow(loaded_partner_campaigns)
.to receive(:first)
.and_return(partner_campaign)
end
This is what I would do. Notice that I split the "mocking" part and the "expecting" part, because usually I'll have some other it examples down below (of which then I'll need those it examples to also have the same "mocked" logic), and because I prefer them to have separate concerns: that is anything inside the it example should just normally focus on "expecting", and so any mocks or other logic, I normally put them outside the it.
let(:expected_referral_source) { 'test_promo_path' }
let(:contentful_model_double) { instance_double(Contentful::Model, promotion_type: 'Promotion 1') }
before(:each) do
# mock return values chain
# note that you are not "expecting" anything yet here
# you're just basically saying that: if Contentful::PartnerCampaign.find_by(vanityUrl: expected_referral_source).load.first is called, that it should return contentful_model_double
allow(Contentful::PartnerCampaign).to receive(:find_by).with(vanityUrl: expected_referral_source) do
double.tap do |find_by_returned_object|
allow(find_by_returned_object).to receive(:load) do
double.tap do |load_returned_object|
allow(load_returned_object).to receive(:first).and_return(contentful_model_double)
end
end
end
end
end
it 'calls Contentful::PartnerCampaign.find_by(vanityUrl: referral_source).load.first' do
expect(Contentful::PartnerCampaign).to receive(:find_by).once do |argument|
expect(argument).to eq({ vanityUrl: expected_referral_source})
double.tap do |find_by_returned_object|
expect(find_by_returned_object).to receive(:load).once do
double.tap do |load_returned_object|
expect(load_returned_object).to receive(:first).once
end
end
end
end
end
it 'does something...' do
# ...
end
it 'does some other thing...' do
# ...
end
If you do not know about ruby's tap method, feel free to check this out
I think you need to refactor the chain in two lines like this:
model = double("Contentful::Model", fields: { promotion_type: "Promotion 1" })
campaign = double
allow(Contentful::PartnerCampaign).to receive(:find_by).with(vanityUrl: 'test_promo_path').and_return(campaign)
allow(campaign).to receive_message_chain(:load, :first).and_return(model)
Then you can write your spec that will pass that attribute to find_by and check the chain.

Getting Rspec unit test coverage with Rails and PostgreSQL

I am trying to write a unit test for the following model concern...
require 'active_support/concern'
module Streamable
extend ActiveSupport::Concern
def stream_query_rows(sql_query, options = 'WITH CSV HEADER')
conn = ActiveRecord::Base.connection.raw_connection
conn.copy_data("COPY (#{sql_query}) TO STDOUT #{options};") do
binding.pry
while row = conn.get_copy_data
binding.pry
yield row
end
end
end
end
So far I have battling this with the following spec...
context 'streamable' do
it 'is present' do
expect(described_class.respond_to?(:stream_query_rows)).to eq(true)
end
context '#stream_query_rows', focus: true do
let(:sql_query) { 'TESTQRY' }
let(:sql_query_options) { 'WITH CSV HEADER' }
let(:raw_connection) do
Class.new do
def self.copy_data(args)
yield
end
def self.get_copy_data
return Proc.new { puts 'TEST' }
end
end
end
before do
allow(ActiveRecord::Base).to receive_message_chain(:connection, :raw_connection).and_return(raw_connection)
described_class.stream_query_rows(sql_query)
end
it 'streams data from the db' do
expect(raw_connection).to receive(:copy_data).with("COPY (#{sql_query}) TO STDOUT #{sql_query_options};")
end
end
end
While I can get the first expect to pass, meaning, I can trigger the first binding.pry, no matter what I try, I can not seem to get past the second.
This is the error...
LocalJumpError:
no block given (yield)
I am only trying to unit test this and ideally not hit the db, only testing the communication of the objects. This also, can and will be used in many models as an option for streaming data.
Reference article: https://shift.infinite.red/fast-csv-report-generation-with-postgres-in-rails-d444d9b915ab
Does anyone have an pointers on how to finish this stub and or adjust the spec so I have the following block covered?
while row = conn.get_copy_data
binding.pry
yield row
end
ANSWER
After reviewing the comments and suggestions below, I was able to refactor the spec and now have 100% coverage.
context '#stream_query_rows' do
let(:sql_query) { 'TESTQRY' }
let(:sql_query_options) { 'WITH CSV HEADER' }
let(:raw_connection) { double('RawConnection') }
let(:stream_query_rows) do
described_class.stream_query_rows(sql_query) do
puts sql_query
break
end
end
before do
allow(raw_connection).to receive(:copy_data).with("COPY (#{sql_query}) TO STDOUT #{sql_query_options};"){ |&block| block.call }
allow(raw_connection).to receive(:get_copy_data).and_return(sql_query)
allow(ActiveRecord::Base).to receive_message_chain(:connection, :raw_connection).and_return(raw_connection)
end
it 'streams data from the db' do
expect(raw_connection).to receive(:copy_data).with("COPY (#{sql_query}) TO STDOUT #{sql_query_options};")
stream_query_rows
end
it 'yields correct data' do
expect { stream_query_rows }.to output("#{sql_query}\n").to_stdout_from_any_process
end
end
Like the error says, you're yielding, but you haven't supplied a block for it to call.
If your method expects a block, then you need to supply one when you call it.
To do that, you need to change this line:
described_class.stream_query_rows(sql_query)
to something like this:
described_class.stream_query_rows(sql_query) { puts "this is a block" }

Testing for uniqueness of before_create field in Rails and Rspec

I have a private method that generates a unique open_id for each user. The open_id is also indexed on the database level. How do I write a model test for uniqueness in RSpec?
before_create: generate_open_id!
def generate_open_id!
begin
self.open_id = SecureRandom.base64(64)
end while self.class.exists?(open_id: self.open_id)
end
UPDATE: solution based on accepted answer below
def generate_open_id!
if !self.open_id
begin
self.open_id = SecureRandom.base64(64)
end while self.class.exists?(open_id: self.open_id)
end
end
#users = FactoryGirl.create_list(:user, 10)
#user_last = #users.last
subject { #user_last }
it "has a random open_id" do
base_64_regex = %r{^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$}
expect(#user_last.open_id).to match base_64_regex
end
it "has a unique open_id" do
expect {FactoryGirl.create(:user, open_id: #user_last.open_id)}.to raise_error(ActiveRecord::RecordNotUnique)
end
Refactoring your original code will make testing what you're trying to do much easier. Change your generate_open_id! method to this
def generate_open_id!
(open_id = SecureRandom.base64(64)) unless open_id
end
And now you can test with the following
# spec/models/some_model_spec.rb
describe SomeModel do
subject(:some_model){ FactoryGirl.create(:some_model) }
describe 'open_id attribute' do
it 'is a random base64 string' do
base_64_regex = %r{^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$}
expect(some_model.open_id).to match base_64_regex
end
it 'is unique' do
expect {FactoryGirl.create(:some_model, open_id: some_model.open_id)}.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
You can use SecureRandom.uuid that will generate to you unique strings.
More info here.
Also, you can add validates_uniqueness_of :your_field that will do it for you.

How to test the number of database calls in Rails

I am creating a REST API in rails. I'm using RSpec. I'd like to minimize the number of database calls, so I would like to add an automatic test that verifies the number of database calls being executed as part of a certain action.
Is there a simple way to add that to my test?
What I'm looking for is some way to monitor/record the calls that are being made to the database as a result of a single API call.
If this can't be done with RSpec but can be done with some other testing tool, that's also great.
The easiest thing in Rails 3 is probably to hook into the notifications api.
This subscriber
class SqlCounter< ActiveSupport::LogSubscriber
def self.count= value
Thread.current['query_count'] = value
end
def self.count
Thread.current['query_count'] || 0
end
def self.reset_count
result, self.count = self.count, 0
result
end
def sql(event)
self.class.count += 1
puts "logged #{event.payload[:sql]}"
end
end
SqlCounter.attach_to :active_record
will print every executed sql statement to the console and count them. You could then write specs such as
expect do
# do stuff
end.to change(SqlCounter, :count).by(2)
You'll probably want to filter out some statements, such as ones starting/committing transactions or the ones active record emits to determine the structures of tables.
You may be interested in using explain. But that won't be automatic. You will need to analyse each action manually. But maybe that is a good thing, since the important thing is not the number of db calls, but their nature. For example: Are they using indexes?
Check this:
http://weblog.rubyonrails.org/2011/12/6/what-s-new-in-edge-rails-explain/
Use the db-query-matchers gem.
expect { subject.make_one_query }.to make_database_queries(count: 1)
Fredrick's answer worked great for me, but in my case, I also wanted to know the number of calls for each ActiveRecord class individually. I made some modifications and ended up with this in case it's useful for others.
class SqlCounter< ActiveSupport::LogSubscriber
# Returns the number of database "Loads" for a given ActiveRecord class.
def self.count(clazz)
name = clazz.name + ' Load'
Thread.current['log'] ||= {}
Thread.current['log'][name] || 0
end
# Returns a list of ActiveRecord classes that were counted.
def self.counted_classes
log = Thread.current['log']
loads = log.keys.select {|key| key =~ /Load$/ }
loads.map { |key| Object.const_get(key.split.first) }
end
def self.reset_count
Thread.current['log'] = {}
end
def sql(event)
name = event.payload[:name]
Thread.current['log'] ||= {}
Thread.current['log'][name] ||= 0
Thread.current['log'][name] += 1
end
end
SqlCounter.attach_to :active_record
expect do
# do stuff
end.to change(SqlCounter, :count).by(2)

Resources