`allow_any_instance_of` mock not working in scope - ruby-on-rails

My mock is only working when it's in the before block shown below. This is just my quick and dirty representation of my problem. Literally when I move the line from the before block to the does not quack assertion, it stops mocking :(
describe 'Ducks', type: :feature do
before do
...
allow_any_instance_of(Duck).to receive(:quack).and_return('bark!')
visit animal_farm_path
end
context 'is an odd duck'
it 'does not quack' do
expect(Duck.new.quack).to eq('bark!')
end
end
end
I want it here, but it doesn't work:
describe 'Ducks', type: :feature do
before do
...
visit animal_farm_path
end
context 'is an odd duck'
it 'does not quack' do
allow_any_instance_of(Duck).to receive(:quack).and_return('bark!')
expect(Duck.new.quack).to eq('bark!')
end
end
end

My bad. The original question was poorly written. Visiting the page is what makes the #quack call. The mocks must always be done before you do whatever it is that engages the method call. So this was my solution
describe 'Ducks', type: :feature do
before do
...
end
context 'is an odd duck'
it 'does not quack' do
allow_any_instance_of(Duck).to receive(:quack).and_return('bark!')
visit animal_farm_path
# In this crude example, the page prints out the animals sound
expect(page).to have_text('bark!')
end
end
end

Related

How can I detect the timing when I have to change a method in rspec?

Suppose I have the following code.
class Answer
enum type: %i[text checkbox image]
def round_type
case answer.type
when text, checkbox
:text
when image
:multimedia
else
raise 'Unknown type'
end
end
end
require 'rails_helper'
RSpec.describe Answer, type: :model do
describe '#round_type' do
context 'when type is text' do
it 'returns text' do
# omitted
end
end
context 'when type is checkbox' do
it 'returns text' do
end
end
context 'when type is image' do
it 'returns multimedia' do
end
end
end
end
Then I add video type to the enum. And I expect the method returns multimedia when the type is video.
But the round_type method and the test codes are not support video type. So I will finally realize it when I get an error in production.
I'd like to know what I have to change the method before the error occurs.
So, this is my question: How can I detect the timing when I have to change a method in rspec?
If I understood you correctly, you have to make your specs a bit more dynamic and you have to test the else statement as well:
class Answer < ApplicationRecord
enum type: %i[text checkbox image]
def round_type
case type
when 'text', 'checkbox'
:text
when 'image'
:multimedia
else
raise 'Unknown type'
end
end
end
RSpec.describe Answer, type: :model do
describe '#round_type' do
it 'raises error for unknown type' do
# empty `type` is an unknown type in this situation
expect { Answer.new.round_type }.to raise_error
end
it 'does not raise error for available types' do
# NOTE: loop through all types and check that `round_type` method
# recognizes each one.
Answer.types.each_key do |key|
expect { Answer.new(type: key).round_type }.to_not raise_error
end
end
end
end
Next time you add a new type and forget to update round_type method, the last spec will fail.
https://relishapp.com/rspec/rspec-expectations/v/3-11/docs/built-in-matchers/raise-error-matcher

Rspec shared_examples_for skips test

When I use shared_examples_for as shown below, my test, "makes an auth call" gets skipped. Why is this? If I comment out the shared_examples line then my assertion fails despite it being called in let!. I verified with pry that the function call is taking place. Can someone please explain why I'm seeing these behaviors and how to fix it. Thanks!
# frozen_string_literal: true
require "rails_helper"
describe MyFunc::Auth do
describe ".get_value" do
before do
allow(JWT).to receive(:decode).and_return("myDecodedValue")
end
shared_examples_for "authentication" do
context "success" do
let!(:stub_request) { stub_token(status: 200) }
it "makes an auth call" do
caller
expect(stub_request).to have_been_requested
end
end
end
end
end
You're only defining the shared example, after this you need to actually call it by
include_examples "authentication"
# or
it_behaves_like "authentication"
You can read about the difference in the docs.

Rails specs for simple sidekiq delete worker failling

I've got simple sidekiq worker which, I don't know why it doesn't worked. I think maybe it's because of specs.
worker
class AdminPanelLogRemoverWorker
include Sidekiq::Worker
def perform
expired_logs = AdminPanelLog.where('created_at > ?', 1.year.ago)
expired_logs.delete_all
end
end
specs
require 'rails_helper'
describe AdminPanelLogRemoverWorker do
include_context 'with admin_user form'
subject { described_class.new.perform }
let!(:admin_panel_log1) do
create :admin_panel_log,
action_type: 'Update',
old_data: admin_user_form,
created_at: 2.years.ago
end
let!(:admin_panel_log2) do
create :admin_panel_log,
old_data: admin_user_form,
created_at: 2.days.ago
end
context 'when admin log is outdated' do
it 'calls remover worker' do
expect(AdminPanelLog.count).to eq(1)
end
end
end
The admin_panel_log1 and admin_panel_log2 is corresponding model AdminPanelLog and it forms correctly (maybe I should avoid let! ?). At the result specs failed with an error
Failure/Error: expect(AdminPanelLog.count).to eq(1)
expected: 1
got: 0
(compared using ==)
I justed tested with
RSpec.describe TestController, type: :controller do
subject { User.new }
let!(:test) do
p subject
p "dfvb"
end
it 'testing order of let and subject' do
# Spec
end
end
The subject is initialized before the let! block is called. So in your case, the lo AdminPanelLog is not even created while the job was running. So that the example failed.
context 'when the admin log is outdated' do
it 'calls remover worker' do
subject.new.perform #Perform the job here or after the initialization of AdminPanelLog
expect(AdminPanelLog.count).to eq(1)
end
end
and remove this subject { described_class.new.perform }, as the subject itself will hold the value of the current class.
As already transpires from the Aarthi answer, the issue was that you did not call subject, so the code was not executed and your worker was not called.
Still, I would improve the answer with the following
context 'when admin log is outdated' do
it 'remover worker deletes them' do
expect { subject }.to change(AdminPanelLog, :count).by(-2) #or whatever the amount is
end
end
The above test allows you to check if the worker indeed did it's job at deleting stuff.

Rspec before(:each) works but before(:all) does not

My ProductCategory spec:-
require 'rails_helper'
RSpec.describe ProductCategory, type: :model do
before(:each) do
#product_category = create(:product_category)
end
context "validations" do
it "should have valid factory" do
expect(#product_category).to be_valid
end
it "should have unique name" do
product_category_new = build(:product_category, name: #product_category.name)
expect(product_category_new.save).to be false
end
end
end
The spec runs fine, but when I use before(:all) instead of before(:each), second example fails -
expected false got true I know the difference between before(:all) and before(:each) but I am not able to find the exact reason why second example fails with before(:all)
before :all only runs once before all the examples, so the #product_category is created once. If you have a something like a DatabaseCleaner truncation running after each test, the record is no longer in the database in the second test, thus passing the validation.
before :each on the other hand will be run before each example, so the record will be there in the second example even if the database was cleaned in the meantime.

Avoid excessive rspec nesting with subject, let, and alternative arguments

I'm trying to do some model_spec testing but having trouble with not having to further nest my rspec code. It would be great if in this case, I could just have a set of "it's" instead of having to add context everytime I want to switch the variable var. Here's the following code:
describe "#some_method" do
subject { course.some_method(var) }
context 'given a project' do
let(:var) {random[1]}
it 'returns the one after' do
is_expected.to eq(random[2])
end
context 'being the last' do
let(:vars) {random.last}
it 'returns nil' do
is_expected.to be_nil
end
end
context '...you get the point, being something else' do
let(:vars) { something.else }
it 'returns nil' do
is_expected.to.to be_nil
end
end
end
end
Maybe I'm just stuck in the wrong mode of thinking and someone could think of a better way for me to do this? I've been suggested that I absolutely must use the subject by someone I work for.
At first, I disagreed and thought it was getting a little burdensome but then I figured keeping subject and having let(:var) apply to it was pretty useful...
RSpecs subject is a tool which can be used to make tests more succinct. There are many cases where it makes sense to use the subject:
RSpec.describe User do
# with the help of shoulda-matchers
it { should validate_uniqueness_of :username } # implicit subject
end
RSpec.describe UsersController do
describe '#show' do
it 'is successful' do
get :show
expect(response).to have_http_status :success
end
it 'renders template show' do
get :show
expect(response).to render_template :show
end
end
#vs
describe '#show' do
subject { response }
before { get :show }
it { should have_http_status :success }
it { should render_template :success }
end
end
And there are cases where using subject will hurt the readability and acuity of your tests.
Your college is just plain wrong in insisting that you always use subject.
A good rule of hand is that if you need an it block then you should not be using subject or is_expected.
If you are describing the call signature of a method you should be calling it in your specs in the same way you would in real life.
let(:decorator){ described_class.new(user) }
describe "#link" do
it 'takes a class option' do
expect(decorator.link(class: 'button')).to match /class=\"button/
end
end
I would recommend running rspec with the --format documentation option and checking if the output actually makes sense. This can be quite important once you get 100s of specs as it gets harder to remember what a behavior a spec actually covers.
How about you write it like this?
expect(subject.call(foo)) is not very pretty but it gets rid of the nesting.
describe "#some_method" do
subject { course.method(:some_method) }
it 'returns the one after if given a project' do
expect(subject.call(random[1])).to eq(random[2])
end
it 'returns nil when it is the last' do
expect(subject.call(random.last)).to be_nil
end
it 'returns nil...' do
expect(subject.call(something.else)).to be_nil
end
end

Resources