rspec private method instance validation testing - ruby-on-rails

There is a private method with the following code.
attr_reader :some_variable
validate :some_def
def some_def
unless some_variable.valid?
some_variable.errors.messages.each do |message|
errors.add(:some_variable, message)
end
end
I am new to rspec and not familiar with private method testing. Any help is appreciated.
I need to cover the lines of the private method.

you can do something like this:
describe 'validations' do
let(:some_variable_object) { SomeVariable.new }
let(:new_foo) { described_class.new(some_variable: some_variable_object) }
context 'when some_variable is valid' do
before do
allow(some_variable_object).to receive(:valid?) { true }
end
it 'is valid' do
expect(new_foo).to be_valid
end
it 'does not have errors related to some_variable' do
expect(new_foo.errors[:some_variables]).to be_empty
end
end
then you can do the same to test the opposite, when some_variable is not valid...
now, there are tools to help you setting up objects within the spec easily (FactoryBot).

Related

Rspec test that service function is called

I have a service and I want to test that a function is called. I'm not sure how to test it because it doesn't seem like there is a subject that's being acted on.
class HubspotFormSubmissionService
def initialize(form_data)
#form_data = form_data
end
def call
potential_client = createPotentialClient
end
def createPotentialClient
p "Step 1: Attempting to save potential client to database"
end
end
I want to test that createPotentialClient is called:
require 'rails_helper'
RSpec.describe HubspotFormSubmissionService, type: :model do
describe '#call' do
let(:form_data) { {
"first_name"=>"Jeremy",
"message"=>"wqffew",
"referrer"=>"Another Client"
} }
it 'attempts to process the form data' do
expect(HubspotFormSubmissionService).to receive(:createPotentialClient)
HubspotFormSubmissionService.new(form_data).call
end
end
end
What should I be doing differently?
You can just set the subject like this. Then in the test expect subject to receive the method like you have after it is mocked. I would also have a separate test for createPotentialClient to test that it is returning the value you expect.
subject { described_class.call }
before do
allow(described_class).to receive(:createPotentialClient)
end
it 'calls the method' do
expect(described_class).to receive(:createPotentialClient)
subject
end

How do you skip a service call in a rspec test

Im writing a test for this service.
def run
sort_offers(product).each do |product_code|
......
offer.update(poduct_params)
Importer::Partner.get_details(product_code).new
end
end
It's calling a service which in some cases will override the values that were saved when running offer.update(product_prams). How would I go about skipping the service call within my test?
Here is the example of my test
context 'is valid' do
.... .....
before do
Importer::ProductCodes(product).run
end
it ......
end
I would stub Importer::Partner.get_details to return a double that responds to new:
context 'is valid' do
before do
allow(Importer::Partner).to receive(:get_details).and_return(double(new: nil))
end
# it ...
end
Depending on your needs you might want to add an expectation that the mock was called with the correct parameters and that new was actually called on the mock too:
context 'is valid' do
let(:mock) { double(new: nil) }
before do
allow(Importer::Partner).to receive(:get_details).and_return(double(new: nil))
end
it "calls the service" do
an_instance.run
expect(Importer::Partner).to have_received(:get_details).with(
foo: 'bar' # the arguments you would expect
)
expect(mock).to have_received(:new)
end
end
RSpec has a very capable stubbing and mocking library built in (rspec mocks).
require 'spec_helper'
module Importer
class Partner
def self.get_details(product_code)
"original return value"
end
end
end
class FooService
def self.run
Importer::Partner.get_details('bar')
end
end
RSpec.describe FooService do
let(:partner_double) { class_double("Importer::Partner") }
before do
stub_const("Importer::Partner", partner_double)
allow(partner_double).to receive(:get_details).and_return 'our mocked value'
end
it "creates a double for the dependency" do
expect(FooService.run).to eq 'our mocked value'
end
end
class_double creates a double for the class and you can set the return values by using .expect and .allow and the mocking interface. This is quite useful since you can stub out the new or intialize methods to return a double or spy.
stub_constant will reset the constant to its previous value when the spec is done.
That said you can avoid the use of stub_constant by using constructor injection in your services:
class PhotoImportService
attr_accessor :client, :username
def initialize(username, api_client: nil)
#username = username
#client = api_client || APIClient.new(ENV.fetch('API_KEY'))
end
def run
client.get_photos(username)
end
end

any_instance is not instance-agnostic

I have a problem with testing one of my workers in rails app. It looks like this:
class UserStatisticsWorker
include Sidekiq::Worker
include Sidetiq::Schedulable
def perform(administration_id = nil)
administrations(administration_id).find_each do |administration|
User::StatisticsCalculator.new.recalculate_if_needed(administration.id)
end
end
private
def administrations(administration_id = nil)
administration_id.present? ? Administration.where(id: administration_id) : Administration.all
end
end
And it is tested with rspec:
require 'spec_helper'
describe UserStatisticsWorker do
describe 'perform' do
let!(:administration) { create(:administration) }
let!(:administration_2) { create(:administration) }
context 'when administration_id is present' do
it 'runs User::StatisticsCalculator for one administration' do
expect_any_instance_of(User::StatisticsCalculator).to receive(:recalculate_if_needed).once
subject.perform(administration.id)
end
end
context 'when administration_id is not present' do
it 'runs User::StatisticsCalculator for all administrations' do
expect_any_instance_of(User::StatisticsCalculator).to receive(:recalculate_if_needed).twice
subject.perform
end
end
end
end
The second spec is not pass with following error:
The message 'recalculate_if_needed' was received by #<User::StatisticsCalculator:85721520 > but has already been received by #<User::StatisticsCalculator:0x0000000a383498>
Why is that?
A very good practice is to avoid any_instance_of and instead extract private methods in your worker which can be more easily tested. A refactor would look something like this:
class UserStatisticsWorker
include Sidekiq::Worker
include Sidetiq::Schedulable
def perform(administration_id = nil)
administrations(administration_id).find_each do |administration|
recalculate_if_needed(administration)
end
end
private
def recalculate_if_needed(administration)
User::StatisticsCalculator.new.recalculate_if_needed(administration.id)
end
def administrations(administration_id = nil)
administration_id.present? ? Administration.where(id: administration_id) : Administration.all
end
end
Then, test it like this:
require 'spec_helper'
describe UserStatisticsWorker do
describe 'perform' do
let!(:administration) { create(:administration) }
let!(:other_administration) { create(:administration) }
context 'when administration_id is present' do
it 'tries to recalculate for the specific administration' do
expect(subject).to receive(:recalculate_if_needed).once
subject.perform(administration.id)
end
end
context 'when administration_id is not present' do
it 'tries to recalculate for all administrations' do
expect(subject).to receive(:recalculate_if_needed).twice
subject.perform
end
end
end
end
The problem is that you have set the expectation to happen twice on an instance... but what is actually happening is that it is being called Once on two different instances.
ie this is not the expectation that you're looking for...
see the other answer for what you could try instead.

Rspec: helper method to detect presence of form field

I want to test that my controller helper method honeypot_detected? will be true if the #params of a form field named birth_city is filled in.
Do I need to use mocks in order to test this?
helpers.rb:
def honeypot_detected?
#params[:birth_city].present?
end
helpers_spec.rb
require 'spec_helper'
describe WindowWashers::Controllers::Shared::Helpers do
.
.
.
before(:all) { #controller = ApplicationController.new }
context "when honeypot_detected? is called" do
it "returns true when birth_city is storing a value" do
#Not sure how to represent :birth_city => 'Dallas
expect(honeypot_detected?).to be_true
end
end
end
.
.
.
end
context "when honeypot_detected? is called" do
it "returns true when birth_city is storing a value" do
instance_variable_set(:#params, {:birth_city => "Dallas"})
expect(honeypot_detected?).to be_true
end
end
Since you're checking values stored in an instance variable, you should be able to use assign to set it. I'm assuming your instance variable #params is just a hash, in which case you probably don't need to go so far as to use a test double like you might for a more complicated object:
describe '#honeypot_detected?' do
let(:honeypot_detected) { helper.honeypot_detected? }
context 'when birth_city present in params' do
before { assign(:params, { birth_city: "Dallas" }) }
it 'returns true' do
expect(honeypot_detected).to be_true
end
end
context 'when birth_city absent from params' do
before { assign(:params, { foo: "bar" }) }
it 'returns false' do
expect(honeypot_detected).to be_false
end
end
end

Rspec and testing instance methods

Here is my rspec file:
require 'spec_helper'
describe Classroom, focus: true do
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
before(:each) do
#classroom = build_stubbed(:classroom)
end
context "when a classroom is active" do
it "should mark classroom as inactive" do
#classroom.archive!
#classroom.active.should_be == false
end
end
end
end
end
Here is my Classroom Factory:
FactoryGirl.define do
factory :classroom do
name "Hello World"
active true
trait :archive do
active false
end
end
end
When the instance method test runs above, I receive the following error: stubbed models are not allowed to access the database
I understand why this is happening (but my lack of test knowledge/being a newb to testing) but can't figure out how to stub out the model so that it doesn't hit the database
Working Rspec Tests:
require 'spec_helper'
describe Classroom, focus: true do
let(:classroom) { build(:classroom) }
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
context "when a classroom is active" do
it "should mark classroom as inactive" do
classroom.archive!
classroom.active == false
end
end
end
end
end
Your archive! method is trying to save the model to the database. And since you created it as a stubbed model, it doesn't know how to do this. You have 2 possible solutions for this:
Change your method to archive, don't save it to the database, and call that method in your spec instead.
Don't use a stubbed model in your test.
Thoughtbot provides a good example of stubbing dependencies here. The subject under test (OrderProcessor) is a bona fide object, while the items passed through it are stubbed for efficiency.

Resources