Testing gems within a Rails App - ruby-on-rails

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.

Related

Use object created inside RSpec expect block in assertion

I'm trying to write a test where I need the value created by the expected block to write the assertion.
class Identification < ApplicationRecord
include Wisper::Publisher
after_save :publish_identification_declined
private
def publish_identification_declined
if status_previously_changed? && status == "declined"
broadcast(:identification_declined, identification: self)
end
end
end
I tried to do something like this but unfortunately identification_a ends up not being set.
require "rails_helper"
RSpec.describe Identification do
it "publish event identification_declined" do
identification_a = nil
expect { identification_a = create(:identification, :declined, id: 1) }
.to broadcast(:identification_declined, identification: identification_a)
end
end
I also have a feeling that this might not be a good idea.
An alternative could be using the instance_of matcher but then I don't know how to check if it's the right instance.
I think you shouldn't test the private functions because these are like black box and no matter how it works, as long as it returns as expected, obiously some times is necessary but in that case I think that the function shouldn't being private.
If you want test that the function is called, you can use receive rspec function. Some like:
require "rails_helper"
RSpec.describe Identification do
subject { described_class.new(identification: nil, :declined, id: 1) }
it "updates the state after save(or the event that should runs)" do
expect { subject }.to receive(:publish_identification_declined)
subject.save
end
end
You also can see the the documentation

Getting FactoryBot object attributes for API requests with RSpec

I am setting up RSpec request tests, and I have the following test:
require 'rails_helper'
RSpec.describe "ClientApi::V1::ClientContexts", type: :request do
describe "POST /client_api/v1/client_contexts" do
let(:client_context) { build :client_context }
it "creates a new context" do
post "/client_api/v1/client_contexts", params: {
browser_type: client_context.browser_type,
browser_version: client_context.browser_version,
operating_system: client_context.operating_system,
operating_system_version: client_context.operating_system_version
}
expect(response).to have_http_status(200)
expect(json.keys).to contain_exactly("browser_type", "browser_version", "operating_system", "operating_system_version")
# and so on ...
end
end
end
The corresponding factory is this:
FactoryBot.define do
factory :client_context do
browser_type { "Browser type" }
browser_version { "10.12.14-blah" }
operating_system { "Operating system" }
operating_system_version { "14.16.18-random" }
end
end
Now, obviously, that all seems a bit redundant. I have now three places in which I specify the attributes to be sent. If I ever want to add an attribute, I have to do it in all of these places. What I actually want to do is send the particular attributes that the Factory specifies via POST, and then check that they get returned as well.
Is there any way for me to access the attributes (and only these!) that I defined in the Factory, and re-use them throughout the spec?
I should prefix this with a warning that abstracting away the actual parameters from the request being made could be seen as detrimental to the overall test expressiveness. After all, now you'd have to look into the Factory to see which parameters are sent to the server.
You can simply get the Factory-defined attributes with attributes_for:
attributes_for :client_context
If you need more flexibility, you can implement a custom strategy that returns an attribute Hash from your Factory without creating the object, just building it.
Create a file spec/support/attribute_hash_strategy.rb:
class AttributeHashStrategy
def initialize
#strategy = FactoryBot.strategy_by_name(:build).new
end
delegate :association, to: :#strategy
def result(evaluation)
evaluation.hash
end
end
Here, the important part is evaluation.hash, which returns the created object as a Ruby Hash.
Now, in your rails_helper.rb, at the top:
require 'support/attribute_hash_strategy'
And below, in the config block, specify:
# this should already be there:
config.include FactoryBot::Syntax::Methods
# add this:
FactoryBot.register_strategy(:attribute_hash, AttributeHashStrategy)
Now, in the Spec, you can build the Hash like so:
require 'rails_helper'
RSpec.describe "ClientApi::V1::ClientContexts", type: :request do
describe "POST /client_api/v1/client_contexts" do
let(:client_context) { attribute_hash :client_context }
it "creates a new context" do
client = create :client
post "/client_api/v1/client_contexts",
params: client_context
expect(response).to have_http_status(200)
end
end
end
The attribute_hash method will be a simple Hash that you can pass as request parameters.

How to stub zendesk_api current_user for a spec?

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.

Rspec: how to create a method to call and create all mocks?

I am using Rspec to test my rails application and FactoryBot (new nome for factory girl) to create mocks. But in each file I need to call a lot of mocks. And it's almost the something, call some mocks, create an user, login and confirm this user. Can I create a method to call all this mocks, login and confirm the user? Then I just need to call this method.
Here is what I call in each rspec file:
let!(:nivel_super) { create(:nivel_super) }
let!(:gestora_fox) { create(:gestora_fox) }
let!(:user_mock) { create(:simple_super) }
let!(:super_user) { create(:super_user,
nivel_acesso_id: nivel_super.id, gestora_id: gestora_fox.id) }
let!(:ponto_venda) { create(:ponto_venda1) }
let!(:tipo_op) { create(:tipo_op) }
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
super_user.confirm
sign_in super_user
end
I'm thinking something like:
def call_mocks
# put the code above
end
Than in each rspec file:
RSpec.describe OrdemPrincipalsController, type: :controller do
describe 'Ordem Principal tests' do
call_mocks
it 'go to new page' do
# my test
end
end
end
You should simply use shared context for that
https://relishapp.com/rspec/rspec-core/v/3-4/docs/example-groups/shared-context
It is similar to shared_example but for context which is what you want
Nevertheless, you you got exactly the same setting for a lot of tests, consider putting them together in the same context
Enjoy :)

allow_any_instance_of and allow using the instance_double is not working properly

Just want to ask if you encounter that when you refactor your spec that before you used the allow_any_instance_of then you change it to allow it didn't work as what you expect. As we all know in the documentation the allow_any_instance_of was already deprecated and they're encourage us to use the allow. I still don't know why it didn't work.
Btw, I can't reproduce my code here as this is own by company but the structure is like this.
Before
feature `Something Page Spec here`, retry: 0, js: true do
# some `let` here
before do
sign_in user
setup_something_here
end
describe 'feature here' do
let( :user ) { create( :user ) }
before do
allow_any_instance_of( ActionDispatch::Request ).to receive( :headers ) { { 'something' => 'here' } }
end
context 'something here' do
# then some expectation here
end
end
end
After
feature `Something Page Spec here`, retry: 0, js: true do
# some `let` here
before do
sign_in user
setup_something_here
request = instance_double( ActionDispatch::Request )
allow( request ).to receive( :headers ) { { 'something' => 'here' } }
end
describe 'feature here' do
let( :user ) { create( :user ) }
context 'something here' do
# then some expectation here
end
end
end
I did pry when I stubbed it in before I can get the correct value but then in the after it's already nil. I'm confused why it didn't work. Hope there some can help me with this confusion. Thanks!
It doesn't work because you created just a local variable request that lives only inside before block.
If you want this to work you need to also stub controller request method to actually return this variable.
Something like this
request = instance_double( ActionDispatch::Request )
allow( request ).to receive( :headers ) { { 'something' => 'here' } }
allow_any_instance_of(YourRelatedController).to receive(:request).and_return(request)
You have a few issues here
You should not be mocking/doubling in feature tests since it defeats the whole purpose of feature tests (which really makes the rest of these issues moot)
allow_any_instance_of isn't deprecated, it's use is discouraged because in properly structured tests it shouldn't be needed 99.9% of the time, but it is not deprecated
With instance_double you create an instance of ActionDispatch::Request - but it's not an instance any other code in your test or app are going to use.
It's not clear what exactly your test is checking but, if you're trying to test that headers are set that may be more appropriate for a request spec than feature spec, depending on what headers and what the purpose of them is

Resources