Use object created inside RSpec expect block in assertion - ruby-on-rails

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

Related

How do you cause an expectation to halt execution in RSpec?

I want to test that a class receives a class-method call in RSpec:
describe MyObject do
it "should create a new user" do
expect(User).to receive(:new)
MyObject.new.doit
end
end
class MyObject
def doit
u = User.new
u.save
end
end
The problem is that the expectation does not halt execution. It simply stubs the class method .doit and continues execution.
The effect of the expectation is to ensure that User.new returns nil. So when we get to the next line which is User.save it then fails because there is no user object to call .save on.
I would like execution to halt as soon as the RSpec expectation has been satisfied - how can I do that?
nb
This is just an illustrative example - while an expect to change would work for User.new, it's not this actual code that I need to test
There is a great method for this and_call_original:
expect(User).to receive(:new).and_call_original
based on your test description, you're testing that a record was created, in those cases I would suggest you to do this:
expect {
MyObject.new.doit
}.to change{User.count}
or if you want to make sure it only created one:
expect {
MyObject.new.doit
}.to change{User.count}.by(1)

Expect to receive always succeeds

There are several cases, where my specs always return true, making the test superfluous...
Take this Achievement model for instance:
class Achievement < ActiveRecord::Base
has_many :stages
def call_name_method
name
end
def name
key
end
end
# for simplicity sake, I'm just testing a method which simply calls another method.
achievement_specs.rb
require 'rails_helper'
describe Achievement do
describe '#call_name_method' do
subject { achievement.call_name_method }
let(:achievement) { create(:achievement) }
it 'calls #name' do
expect(achievement).to receive(:name)
subject
end
end
end
This test succeeds, but I can change it to expect(achievement).to receive(:foobar) and it will still succeed, although I am not calling foobar.
According to this answer, it is the correct syntax, but it somehow never fails. Is this a bug?
I also tried using .to have_received(:call_name_method), but that results in this error:
1) Achievement#call_name_method calls #name
Failure/Error: expect(achievement).to have_received(:call_name_method)
# expected to have received call_name_method, but that object is not a spy or method has not been stubbed.
The problem was in the rails_helper.rb
config.after(:each) do
DatabaseCleaner.clean
RSpec::Mocks.teardown # <-- this line was fault
end
After removing that line the specs worked as expected. A coworker accidentally commited this although it was not needed.
Since all test were 'succeeding', it did not catch our attention until now.

RSpec: expect.to receive fails if object is not referenced directly

In my Rails application I have a User model:
class User
def self.foo
User.all.each{ |user| user.bar }
end
def bar
end
end
In my spec file I want to check that foo calls bar for every user, so far that's what I have:
describe '::foo' do
let!(:users) { Fabricate.times(5, :user) }
it 'calls bar for every user' do
users.each do |user|
expect(user).to receive(:bar)
end
User.foo
end
end
Although the method gets called (I debugged it, so I'm sure of that) the spec is red.
Also I tried to write this code to understand where the problem was:
let!(:user) { Fabricate(:user) }
it 'fails' do
expect(user).to receive(:bar)
User.first.bar
end
it 'pass' do
expect(user).to receive(:bar)
user.bar
end
It seems that if I reference my instance directly it works, if I obtain it from the DB the expectation doesn't work.
I use mongoid, not sure if this is relevant.
I believe it cannot be done due to how RSpec works: When you set an expectation, RSpec essentially 'wraps' the object so that it can keep track of the messages it receives.
But when the implementation code fetches records from the database, they are not wrapped, so RSpec isn't able to record their messages.
RSpec does have a method allow_any_instance_of which can help in some cases, but its use is discouraged, and don't think it would be suitable here.
In this situation, I would suggest stubbing User.all to return some doubles (two should be sufficient). You can then verify that bar is called on each one.

Create RSpec context inside a function

In order to avoid repeating myself a lot in my Rspec tests, I'd like to write a function like this
def with_each_partner(&block)
PARTNER_LIST.each do |partner|
context "with partner #{partner.name}" { yield partner }
end
end
I have such a function and it works in the sense that all the tests run with the correct value for partner supplied, but they don't print during output as being part of the "with partner X" context: instead if I have a test like this:
describe Thing do
subject { Thing.new(partner) }
with_each_partner do |partner|
it 'does its thing' do
expect(subject.do).to eq 'its thing'
end
end
end
I end up with output like this:
Thing
does its thing
Instead of the desired output, which is like:
Thing
with partner X
does its thing
with partner Y
does its thing
How can I get RSpec to correctly work with the context created in my function?
TL;DR: do this:
def with_each_partner(&block)
PARTNER_LIST.each do |partner|
context "with partner #{partner.name}" do
class_exec(&block)
end
end
end
Explanation
RSpec's DSL works by evaluating the blocks with a changed self -- this is how it is a method within a describe or context block, but not outside of it. When you yield, the provided block is evaluated with the original self that was self at the point the block was defined. What that means is that with your original with_each_partner definition, this code:
describe Thing do
subject { Thing.new(partner) }
with_each_partner do |partner|
it 'does its thing' do
expect(subject.do).to eq 'its thing'
end
end
end
Is really being evaluated like this:
describe Thing do
subject { Thing.new(partner) }
outer_example_group = self
with_each_partner do |partner|
outer_example_group.it 'does its thing' do
expect(subject.do).to eq 'its thing'
end
end
end
...and so the individual examples are defined on the outer example group, not on the "with partner #{partner.name}" nested group.
class_exec evaluates the provided block in the context of the class/module. In this case, the class is the example group subclass that RSpec has generated for your context. Using class_exec ensures that when it is called, the receiver is your nested context example group rather than the outer example group, creating the result you want.

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