How to get rid of let using before - ruby-on-rails

I want to get rid of let! - to do so I want to move it to the before method (it has to be created before everything else).
describe AdminPanelLogRemoverWorker do
include_context 'with admin_user form'
subject(:delete_worker) { described_class.new.perform }
let!(:admin_panel_log1) do
create :admin_panel_log,
new_data: admin_user_form,
created_at: created_at
end
let!(:admin_panel_log2) do
create :admin_panel_log,
new_data: admin_user_form,
created_at: 2.days.ago
end
let(:created_at) { 2.years.ago }
context 'when admin log is outdated' do
it 'delete only outdated data' do
expect { delete_worker }.to change(AdminPanelLog, :count).by(-1)
end
end
I want to do something like this
before { admin_panel_log1 }
before { admin_panel_log2 }
but how to do it in one line?

You might want to change your code to this:
describe AdminPanelLogRemoverWorker do
include_context 'with admin_user form'
subject(:delete_worker) { described_class.new.perform }
let(:admin_panel_log1) {
create :admin_panel_log, new_data: admin_user_form, created_at: 2.years.ago
}
let(:admin_panel_log2) {
create :admin_panel_log, new_data: admin_user_form, created_at: 2.days.ago
}
before do
admin_panel_log1
admin_panel_log2
end
context 'when admin log is outdated' do
it 'delete only outdated data' do
expect { delete_worker }.to change(AdminPanelLog, :count).by(-1)
end
end
end

Related

How to test this method? (RSpec)

def is_doctor
user = User.find_by(role: 'doctor')
"Name: #{user.name} | Email: #{user.email}| isActive: #{user.is_active}"
end
Something like this, but I don't know how to implement it correctly ↓
context 'test' do
#it { expect(user.is_doctor).to eq("Taras") }
end
I assume doctor? is an instance method on a User model + you're using Faker which might help you a lot.
# models/user.rb
class User < ApplicationRecord
def doctor?
return 'Not a doc' unless role == 'doctor'
"Name: #{name} | Email: #{email}| isActive: #{is_active}"
end
end
# specs/models/user_spec.rb
describe User, type: :model do
context 'with instance method' do
describe '#doctor?' do
subject { user. doctor? }
context 'with a doctor' do
let(:user) { create(:user, role: 'doctor') }
it 'includes name' do
expect(subject).to include("Name: #{user.name}")
end
it 'includes email' do
expect(subject).to include("Email: #{email}")
end
it 'includes is_active' do
expect(subject).to include("isActive: #{is_active}")
end
end
context 'without doctor' do
let(:user) { create(:user, role: 'foo') }
it 'has static response' do
expect(subject).to eq('Not a doc')
end
end
end
end
end

Rspec DRY: apply example to all contexts

is it possible to shorten this Rspec?
I'd like to extract the line it { expect { author.destroy }.to_not raise_error } not to repeat it in every context. Shared examples are some way, but finally, it generates more code than below redundant version.
require 'rails_helper'
RSpec.describe Author, type: :model do
describe 'destroying' do
context 'when no books assigned' do
subject!(:author) { FactoryBot.create :author_with_no_books }
it { expect { author.destroy }.to_not raise_error }
# other examples
end
context 'when there are some books' do
subject!(:author) { FactoryBot.create :author_with_books }
it { expect { author.destroy }.to_not raise_error }
# other examples
end
context 'when there are some posts' do
subject!(:author) { FactoryBot.create :author_with_posts }
it { expect { author.destroy }.to_not raise_error }
# other examples
end
end
end
Use shared_examples with a parameter instead of abusing subject:
RSpec.describe Author, type: :model do
include FactoryBot::Syntax::Methods # you can move this to rails_helper.rb
RSpec.shared_examples "can be destroyed" do |thing|
it "can be destroyed" do
expect { thing.destroy }.to_not raise_error
end
end
describe 'destroying' do
context 'without books' do
include_examples "can be destroyed", create(:author_with_no_books)
end
context 'with books' do
include_examples "can be destroyed", create(:author_with_books)
end
context 'with posts' do
include_examples "can be destroyed", create(:author_with_posts)
end
end
end

Ruby on Rails - Is this a good way to eliminate duplicate code in RSpec?

I wrote this code for testing controller update function.
Wrote a method for eliminating duplicate code.
Is this an explicit way to do it?
users_controller_spec.rb
context 'Update failed' do
def render_edit
user.reload
expect(response.status).to eq(200)
end
it 'Name is nil' do
put :update, params: { id: user.id, user: { name: '' } }
render_edit
end
it 'Email is exist' do
create(:user, email: 'user#gmail.com')
put :update, params: { id: user.id, user: { email: 'user#gmail.com' } }
render_edit
end
it 'Email is nil' do
put :update, params: { id: user.id, user: { email: '' } }
render_edit
end
it 'Password must be at least 8 characters' do
put :update, params: { id: user.id, user: { password: '1234567', password_confirmation: '1234567' } }
render_edit
end
it 'Passwords do not match' do
put :update, params: { id: user.id, user: { password: '1234567890', password_confirmation: '123456789' } }
render_edit
end
end
I was thinking to use after(:each). But it looks a little wired in logic.
Or use loop to replace params.
Any suggestion?
You can use shared examples as suggested in the comments, but there's an easier way.
context 'Update failed' do
before do
put :update, params: params
user.reload # I'm not sure why you need this
end
subject { response }
context 'Name is nil' do
let(:params} { {id: user.id, user: { name: '' }} }
it { is_expected.to be_success }
end
context 'Email exists' do
let(:params) { { id: user.id, user: { email: 'user#gmail.com' } }
let(:user) { create(:user, email: 'user#gmail.com') }
it { is_expected.to be_success }
end
# and so on
end
The main rune I use is - make it obvious what change in each context. So instead of redefining put ..., extract it as a let and define it per context.
be_success is part of rspec magic, wherever you use be_something matcher it'll try to use something? method and check if it's true, i.e.
expect(foo).to be_empty? == expect(foo.empty?).to eq(true)
If you don't want it make it like this
subject { response.status }
# and later
is_expected.to eq 200
is_expected.to is just a shorthand for expect(subject).to

rspec + shoulda: setting up data

I have the following test. There are three it blocks. The first one doesn't use shoulda unlike the other two.
If I don't use the before block with post :create, product: attrs then the first test fails as expected. But If I put the before block there then the first test fails, but the other two pass. I have a uniqueness validation on product name, but that shouldn't be the problem as I'm using sequence with factory.
What should I do? How should I generally setup the data for testing when there are rspec and shoulda matchers present at the same time?
describe "when user logged in" do
before(:each) do
login_user #logged in user is available by calling #user
end
context "POST create" do
context "with valid attributes" do
let!(:profile) { create(:profile, user: #user) }
let!(:industry) { create(:industry) }
let!(:attrs) { attributes_for(:product, user_id: #user.id, industry_ids: [ industry.id ]).merge(
product_features_attributes: [attributes_for(:product_feature)],
product_competitions_attributes: [attributes_for(:product_competition)],
product_usecases_attributes: [attributes_for(:product_usecase)]
) }
it "saves the new product in the db" do
expect{ post :create, product: attrs }.to change{ Product.count }.by(1)
end
#If I don't use this the 2 tests below fail. If I use it, then the test above fails.
# before do
# post :create, product: attrs
# end
it { is_expected.to redirect_to product_path(Product.last) }
it { is_expected.to set_flash.to('Product got created!') }
end
end
end
factories
factory :product, class: Product do
#name { Faker::Commerce.product_name }
sequence(:name) { |n| "ABC_#{n}" }
company { Faker::Company.name }
website { 'https://example.com' }
oneliner { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
user
end
You can't have it both ways. If you execute the method you are testing in the before, then you can't execute it again to see if it changes the Product count. If you don't execute it in your before, then you must execute it in your example and therefore can't use the is_expected one liner format.
There are a variety of alternatives. Here is one that incorporates the execution of the method into all the examples.
describe "when user logged in" do
before(:each) do
login_user #logged in user is available by calling #user
end
describe "POST create" do
subject(:create) { post :create, product: attrs }
context "with valid attributes" do
let!(:profile) { create(:profile, user: #user) }
let!(:industry) { create(:industry) }
let!(:attrs) { attributes_for(:product, user_id: #user.id, industry_ids: [ industry.id ]).merge(
product_features_attributes: [attributes_for(:product_feature)],
product_competitions_attributes: [attributes_for(:product_competition)],
product_usecases_attributes: [attributes_for(:product_usecase)]
) }
it "saves the new product in the db" do
expect{ create }.to change{ Product.count }.by(1)
end
it("redirects") { expect(create).to redirect_to product_path(Product.last) }
it("flashes") { expect(create).to set_flash.to('Product got created!') }
end
end
end

Where to put mocks

I'm trying to spec this action.
def get
#asset = current_user.assets.find(params[:id])
send_file #asset.uploaded_file.path, type: #asset.uploaded_file_content_type
rescue ActionController::MissingFile
redirect_to assets_url, error: 'missing file'
end
To test the send file method we mock it out.
controller.should_receive(:send_file)
However, I have no idea where to put this mock:
Here's how my spec looks:
subject { response }
let!(:user) { FactoryGirl.create(:user) }
let!(:user_2) { FactoryGirl.create(:user) }
let!(:asset) { FactoryGirl.create(:asset, user_id: user.id) }
let!(:file) { fixture_file_upload('files/eve.jpg', 'image/jpeg') }
let!(:folder) { FactoryGirl.create(:folder, user_id: user.id, parent_id: nil) }
before do
sign_in user
end
describe '#get' do
context 'when exists' do
before do
get :get, id: asset.id
end
# controller.should_receive(:send_file).with(*args) <-- I need to test that
it { should have_http_status 302 }
end
context 'when doesn\'t exist' do
before do
get :get, id: 765
end
it { should redirect_to_location '/assets'}
it { should set_flash_type_to :error }
it { should set_flash_message_to 'missing file' }
end
end
How do I test line 6. I want to keep the one line syntax if possible.
Put it in the before block
before do
controller.should_receive(:send_file)
get :get, id: asset.id
end

Resources