How to stub zendesk_api current_user for a spec? - ruby-on-rails

I have a model method which I am trying to write a spec for. The method is like this:
def my_method
puts current_user.user_attirbute
end
Where current_user is provided by an authentication gem, zendesk_api-1.14.4. To make this method testable, I changed it to this:
def my_method(user_attribute = nil)
if user_attribute = nil
user_attribute = current_user.user_attribute
end
puts user_attribute
end
This refactor works and is testable, but doesn't seem like a good practice. Ideally the gem would provide some sort of test helper to help stub/mock the current_user, but I haven't been able to find anything. Any suggestions?

You can go simple way and just test returning of proper value by current_user#user_attribute method. Example:
describe '#my_method' do
subject { instance.my_method } # instance is an instance of your class where #my_method is defined
let(:user) { instance_spy(ZendeskAPI::User, user_attribute: attr) }
let(:attr} { 'some-value' }
before do
allow(instance).to receive(:current_user).and_return(user)
end
it { is_expected.to eq(attr) }
end
But I would go with VCR cassette(vcr gem is here) because it is related 3rd party API response - to minimize a risk of false positive result. Next example demonstrates testing with recorded response(only in case if #current_user method performs a request to zendesk):
describe '#my_method', vcr: { cassette_name: 'zendesk_current_user' } do
subject { instance.my_method }
it { is_expected.to eq(user_attribute_value) } # You can find the value of user_attribute_value in recorded cassette
end
P.S. I assumed that you put puts in your method for debugging. If it is intentional and it is part of the logic - replace eq with output in my example.

Related

RSpec - Check whether method was called

My goal is to write rspec test that check whether method was called.
notify(result) if notification_allowed?(result)
Both notification_allowed? and notify are private methods. More precisely I need a test that check whether notify method has was called. I've tried to do something like below but it doesn't seem to be right.
subject { described_class.new }
it do
expect(subject).to receive(:notify)
subject.send(:notification_allowed?, true)
end
Calling notification_allowed? doesn't do anything except return the result of notification_allowed? It's not related to notify
You would need to call the function that contains the expression you want to test.
For example, the method might be...
def check_and_notify(result)
notify(result) if notification_allowed?(result)
end
so the test would be...
subject { described_class.new }
it 'calls notify' do
expect(subject).to receive(:notify)
subject.send(:check_and_notify, true)
end

RSpec & Rails: Stub #virtual_path for translation helper to test an application helper

I have a helper page_title_default in ApplicationHelper:
def page_title_default(options = {})
t '.title', options
end
Now I want to test it like this:
describe '#page_title' do
subject { page_title }
it { ... }
end
end
This results in the following error:
Cannot use t(".title") shortcut because path is not available
According to this post it should be possible to stub the #virtual_path variable like this:
helper.instance_variable_set(:#virtual_path, "admin.path.form")
But this doesn't seem to help: While I am able to stub it and then to call something like helper.t '.something' directly in the test, it doesn't work for the translation helper which is used in the page_title_default method (which still has #virtual_path set to nil). So it seems it's not the same instance of translation helper. But how can I find the page_title_default method one's?
How about something like:
RSpec.describe PageHelper, :type => :helper do
describe "#page_title_default" do
before do
allow(helper).to receive(:t).with(".title", {}) { "Hello!" }
end
subject { helper.page_title_default }
it { is_expected.to eq "Hello!" }
end
end
We're stubbing the "translated" string returned here to decouple the spec of helper from "real" translations, which may appear to be fragile for the test of PageHelper itself - the tests would fail every time you change the translations of ".title".
On the other hand - if you change the key used, eg. from ".title" to ".default_title" it should fail, because it is change of behaviour.
I think the proper text displayed should be tested on different level of test (integration tests, to be specific). Please, check the following answer.
Hope that helps!

Passing a model instance to a helper method in rspec testing

I have a helper method in my app located in spec/support/utilities.rb
I am trying to pass an instance of a model object to it but I haven't succeeded so the tests fail
here is the helper method
def attribute_not_present(model_instance,model_attrib)
describe "when #{model_attrib} is not present" do
before { model_instance.send("#{model_attrib}=", " ") }
it { should_not be_valid }
end
end
in spec/model/tool_spec.rb i have this
require 'spec_helper'
describe Tool do
before do
#tool = FactoryGirl.create(:tool)
end
#attribute_array = ["kind", "serial_number", "department", "size",
"description", "hours", "length"]
subject { #tool }
#checks for absence of any of the required attributes
#attribute_array.each { |tool_attribute|
attribute_not_present(#tool,tool_attribute)
}
end
the #tool seems not to be recognized in the helper
the sample failure is this
1) Tool when size is not present
Failure/Error: before { model_instance.send("#{model_attrib}=", " ") }
NoMethodError:
undefined method `size=' for nil:NilClass
# ./spec/support/utilities.rb:3:in `block (2 levels) in attribute_not_present'
I am rails newbie
At the point where attribute_not_present is called, #tool does not yet exist. Moreover, in one case self is the example group, where when the spec is actually run (and inside your before blocks) self is an instance of the example group.
You don't need to pass model_instance through at all though - you could instead just use subject i.e.
before { subject.send("#{model_attrib}=", " ") }
however.
You may also want to look at shared examples.
Ok - I think I understand what you're trying to do here. You're trying to do unit tests, specifically validations on your Tool class, is that right?
If so, I personally like to use the shoulda_matchers gem which I find to be very idiomatic with exactly this.
As an example, you could do:
describe Tool do
it { should validate_presence_of(:kind) }
it { should validate_presnece_of(:serial_number) }
end
You can even do more with the validations, say you knew the :serial_number can only be an integer, you could do:
it { should validate_numericality_of(:serial_number).only_integer }
This is probably a better way to do unit level validations than a helper method as it's more Ruby-like.

How can I DRY up my Rspec/Capybara suite?

In my suite I have this in many it blocks:
let(:user) { create(:user) }
let(:plan) { Plan.first }
let(:subscription) { build(:subscription, user: user ) }
it "something" do
subscription.create_stripe_customer
subscription.update_card valid_card_data
subscription.change_plan_to plan
login_as user
end
How could I DRY this up so I don't have to duplicate all these lines across many files?
You can also create a method like
def prepare_subscription
subscription.create_stripe_customer
subscription.update_card valid_card_data
subscription.change_plan_to plan
end
And in your it block like so:
it "something" do
prepare_subscription
login_as user
end
You ain't checking value for that spec so it always green.
If you need prepare some data before test then you could put that code into helper and call it when needed in (for example) before block.
If you need check spec passing again and again then you could use shared examples.

Testing gems within a Rails App

I'm attempting to test that my service is calling Anemone.crawl correctly. I have the following code:
spider_service.rb
class SpiderService < BaseService
require 'anemone'
attr_accessor :url
def initialize(url)
self.url = url
end
def crawl_site
Anemone.crawl(url) do |anemone|
end
end
end
spider_service_spec.rb
require 'spec_helper'
require 'anemone'
describe SpiderService do
describe "initialize" do
let(:url) { mock("url") }
subject { SpiderService.new(url) }
it "should store the url in an instance variable" do
subject.url.should == url
end
end
describe "#crawl_site" do
let(:spider_service) { mock("spider service") }
let(:url) { mock("url") }
before do
SpiderService.stub(:new).and_return(spider_service)
spider_service.stub(:crawl_site)
Anemone.stub(:crawl).with(url)
end
subject { spider_service.crawl_site }
it "should call Anemone.crawl with the url" do
Anemone.should_receive(:crawl).with(url)
subject
end
end
end
And here's the error that I'm getting, and can't understand, since I can call the service in the Rails console and I get back data from Anemone when I provide a valid URL:
Failures:
1) SpiderService#crawl_site should call Anemone.crawl with the url
Failure/Error: Anemone.should_receive(:crawl).with(url)
(Anemone).crawl(#<RSpec::Mocks::Mock:0x82bdd454 #name="url">)
expected: 1 time
received: 0 times
# ./spec/services/spider_service_spec.rb:28
Please tell me I've forgotten something silly (I can blame lack of coffee then, instead of general incompetence!)
Thank you for your time,
Gav
Your subject calls a method on the mock object that you're created (mock("spider_service")), not a real SpiderService object. You've also stubbed the call on the mock spider service to do nothing, so calling it in the subject will do nothing, hence why your test fails.
Also, you've stubbed new (although you never call it) on SpiderService to return a mock object. When you're testing SpiderService you'll want to have real instances of the class otherwise method calls will not behave as they would on a real instance of the class.
The following should achieve what you want:
describe "#crawl_site" do
let(:spider_service) { SpiderService.new(url) }
let(:url) { mock("url") }
before do
Anemone.stub(:crawl).with(url)
end
subject { spider_service.crawl_site }
it "should call Anemone.crawl with the url" do
Anemone.should_receive(:crawl).with(url)
subject
end
end
You might also want to move the require 'anenome' outside of the class definition so it is available elsewhere.

Resources