Rspec shared setting variables for many test - ruby-on-rails

Tests are set by Rspec + factory girl, and I have around 20 Rspec test files, all of them share some of the required setting variables. Eg:
let!(:event) { create(:event) }
let!(:user) { create(:user) }
let!(:user) { create(:ticket) }
I don't want to copy paste these variables assignment for each test file, Is there a cleaner way to populate the vars? Thanks in advance.

If yo're using let, you don't need to repeat them. It will persist through the current describe/context block.
eg the following should all pass:
describe 'some stuff' do
let!(:event) { create(:event) }
it 'should get an event' do
expect(event).to_not be_nil
end
describe 'indented stuff' do
let!(:user) { create(:user) }
it 'should still get an event' do
expect(event).to_not be_nil
end
it 'should get a user' do
expect(user).to_not be_nil
end
end
end
To share setup amongst more than one file, define a method in rails_helper/spec_helper:
module CommonSetup
def setup_vars
let!(:event) { create(:event) }
let!(:user) { create(:user) }
let!(:user) { create(:ticket) }
end
end
...
...
Rspec.configure do |c|
...
c.extend CommonSetup
...
end
And then in your spec just call setup_vars in your spec

Related

This code can be rails unit test with rspec?

I'm studying rails and rspec.
And I made rspec unit test (request test) on rails application.
But after searching on google, I'm wonder if my job is on right way.
Can my code be a "Unit test by function(not a method, web site's feature ex)create, show, delete..) of rails application" ?
this is my code with request test.
require 'rails_helper'
RSpec.describe 'Users', type: :request do
let!(:users) { create_list(:user, 10) }
let(:user_id) { users.first.id }
let(:user) { create(:user) }
def send_request_to_store_user(name, mailaddress)
post '/users', params: {
user: {
name: users.first.name,
mailaddress: users.first.mailaddress
}
}
end
def http_status_success_and_body_element_check(body_element)
expect(response).to have_http_status(:success)
expect(response.body).to include(body_element)
end
describe 'GET' do
context 'Get /users test' do
it 'test user list page' do
get '/users'
http_status_success_and_body_element_check('User List')
end
end
context 'Get /users/create test' do
it 'test user create page' do
get '/users/create'
http_status_success_and_body_element_check('create user')
end
end
context 'Get /users/:id/edit' do
it 'test user edit page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('edit user')
end
end
context 'Get /users/:id' do
it 'test user show page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('show user')
end
end
end
describe 'POST' do
context 'test store new user' do
it 'test create new user' do
send_request_to_store_user(user.name, user.mailaddress)
expect do
create(:user)
end.to change { User.count }.from(User.count).to(User.count + 1)
end
it 'test redirect after create' do
send_request_to_store_user(user.name, user.mailaddress)
expect(response).to have_http_status(302)
end
end
end
describe 'DELETE' do
it 'test delete user' do
expect do
delete "/users/#{user_id}"
end.to change { User.count }.from(User.count).to(User.count - 1)
expect(response).to have_http_status(302)
end
end
describe 'PUT' do
context 'user update' do
it 'test user information update' do
old_name = users.first.name
new_name = 'new_name'
expect do
put "/users/#{user_id}", params: {
user: {
name: new_name
}
}
end.to change { users.first.reload.name }.from(old_name).to(new_name)
expect(response).to have_http_status(:redirect)
end
end
end
end
this is my code with test on model
require 'rails_helper'
RSpec.describe User, type: :model do
it 'user must have name and mailaddress' do
user = create(:user)
expect(user).to be_valid
expect(user.name).not_to be_nil
expect(user.mailaddress).not_to be_nil
end
it 'mailaddress must include #' do
# user = FactoryBot.create(:user)
# If rails_helper.rb has config.include FactoryBot::Syntax::Methods,
# Can use shortcut. Don't have to FactoryBot.create
user = create(:user)
# Test pass if email match with regexp
expect(user.mailaddress).to match(/\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/)
end
end
I don't think these tests are valuable (meaningful).
Here's my reasoning:
What are these tests telling you? That the Rails router is working? That the controller is responding with the right action? Neither of these are your responsibility to test. Rails has that covered.
If you want to know "does the index page render?" and "can I CRUD a user?" then write system tests with Capybara that simulate the whole flow. That way you are testing the real-world interaction with your whole system.

RuntimeError: #let or #subject called without a block

This is my first rspec test
I was using Hurtl's tutorial and figured that it is outdated.
I want to change this line because its is no longer a part of rspec:
its(:user) { should == user }
I tried to do this:
expect(subject.user).to eq(user)
But get an error
RuntimeError: #let or #subject called without a block
This is my full rspec test if you need it:
require 'spec_helper'
require "rails_helper"
describe Question do
let(:user) { FactoryGirl.create(:user) }
before { #question = user.questions.build(content: "Lorem ipsum") }
subject { #question }
it { should respond_to(:body) }
it { should respond_to(:title) }
it { should respond_to(:user_id) }
it { should respond_to(:user) }
expect(subject.user).to eq(user)
its(:user) { should == user }
it { should be_valid }
describe "accessible attributes" do
it "should not allow access to user_id" do
expect do
Question.new(user_id: user.id)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
describe "when user_id is not present" do
before { #question.user_id = nil }
it { should_not be_valid }
end
end
Yes, you must be following an outdated version since M. Hartl's Railstutorial book now uses Minitest instead of RSpec.
expect(subject.user).to eq(user)
Does not work since you are calling subject without wrapping it in a it block.
You could rewrite it as:
it "should be associated with the right user" do
expect(subject.user).to eq(user)
end
Or you can use the rspec-its gem which lets you use the its syntax with the current version of RSpec.
# with rspec-its
its(:user) { is_expected.to eq user }
# or
its(:user) { should eq user }
But its still not a particularly valuable test since you are just testing the test itself and not the behaviour of the application.
Also this spec is for an older version (pre 3.5) of rails where mass assignment protection was done on the model level.
You can find the current version of the Rails Turorial book at https://www.railstutorial.org/.
You can't translate its(:user) { should == user } directly into expect(subject.user).to eq(user). You have to surround it with an it block
it 'has a matchting user' do
expect(subject.user).to eq(user)
end

Testing current user in helper spec

I have this pretty basic helper which relies on current_user variable provided by Sorcery in controllers and helpers
def current_user_link
user_link current_user
end
def user_link(user, html_options = {}, &block)
link_to user.to_s, user, html_options, &block
end
How can I test this helper?
describe UsersHelper do
describe '#current_user_link' do
it 'should return a link to the current user' do
expected_link = link_to current_user.name, current_user
???
expect(current_user_link).to eq expected_link
end
end
Do I need to stub current_user somehow?
Is it even worth testing?
This is how I solved it.
describe '#current_user_link' do
it 'returns a link to the current user ' do
user = build(:user)
expected_link = link_to user.name, user
allow(controller).to receive(:current_user).and_return(user)
expect(helper.current_user_link).to eq(expected_link)
end
end
PSA: dont forget to call your method on helper.
I was trying to use current_user with Sorcery in an Rspec ApplicationHelper spec and none of the above answers worked for me.
What worked for me was first defining a user with FactoryGirl:
let(:user) { create(:user) }
Then, write an example like this:
it "does stuff" do
allow(helper).to receive(:current_user).and_return(user)
expect(helper.some_method_using_current_user).to do_something
end
Key difference is using the helper object in the example.
simpliest work around is to declare in spec:
let(:current_user) { create(:user) }
you can stub your current_user
describe UsersHelper do
describe '#current_user_link' do
let(:user) { FactoryGirl.build(:user) }
let(:expected_link) { link_to user.name, user }
before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) }
it { expect(current_user_link).to eq(expected_link) }
end
end
or set your user to session
than you should
let(:user) { FactoryGirl.create(:user) }
and
before { allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(user_id: user.id) }
This worked for me:
describe UsersHelper do
describe '#current_user_link' do
it 'should return a link to the current user' do
user = FactoryGirl.create(:user)
allow_any_instance_of(UsersHelper).to receive(:current_user).and_return(user)
expected_link = link_to user.name, user
expect(current_user_link).to eq(expected_link)
end
end
end
In the rails_helper.rb you need to have:
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :helper
end
When testing helper modules with RSpec, you need to stub the method in your Rspec::ExampleGroups target...
allow_any_instance_of(RSpec::ExampleGroups::UsersHelper).to receive(:current_user).and_return user
For those who came from Devise:
You can simply define the method inside the spec.
describe 'option_for_product_weight' do
before {
def helper.current_user
User.first
end
}
subject { helper.option_for_product_weight }
it 'returns the list' do
expect(subject).not_to be_empty
end
end

How can I run the same tests in rspec/capybara with one parameter throgh iteration in an array

I am working with rails rspec/capybara/declarative_authorization. I have to run the same test with a lot of different users:
describe "Revision in root folder" do
before do
with_user(#guest) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
...
describe "Revision in root folder" do
before do
with_user(#user1) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
The only parameter is the user calling with_user. Can I somehow use only one describe block, and iterate through an array of users, to keep my test DRY. It is important, that #guest and #user1 are created in a before(:all) block, so they are not available at the parsing of the spec.
Any help is appreciated.
describe "Revision in root folder" do
users = [#guest, #user1]
users.each do |user|
before do
with_user(user) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
end
Not that much DRYer, but do you mind nesting your specs? This way you'll be able to account for any different expected behaviour between users and guests.
describe "Revision in root folder" do
context "as a guest" do
before do
with_user(#guest) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
context "as a user" do
before do
with_user(#user1) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
end
If you end up with many more duplicate it statements, you could probably refactor them up into a shared example.
I had the same problem and I resolve it in following way:
[:user_type_1, :user_type_2].each do |user_type|
let(:user) { create(user_type) }
before do
with_user(user) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
Modern version of Rspec allows to duplicate examples without monkey-patching. Please have a look to this gist https://gist.github.com/SamMolokanov/713efc170d4ac36c5d5a16024ce633ea
different users might be provided as a shared_context - user will be available in actual tests:
shared_context "Tested user" do
let(:user) { |example| example.metadata[:user] }
end
During clone, we can do
USERS.each { |user| example.duplicate_with(user: user) }

Rspec and testing instance methods

Here is my rspec file:
require 'spec_helper'
describe Classroom, focus: true do
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
before(:each) do
#classroom = build_stubbed(:classroom)
end
context "when a classroom is active" do
it "should mark classroom as inactive" do
#classroom.archive!
#classroom.active.should_be == false
end
end
end
end
end
Here is my Classroom Factory:
FactoryGirl.define do
factory :classroom do
name "Hello World"
active true
trait :archive do
active false
end
end
end
When the instance method test runs above, I receive the following error: stubbed models are not allowed to access the database
I understand why this is happening (but my lack of test knowledge/being a newb to testing) but can't figure out how to stub out the model so that it doesn't hit the database
Working Rspec Tests:
require 'spec_helper'
describe Classroom, focus: true do
let(:classroom) { build(:classroom) }
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
context "when a classroom is active" do
it "should mark classroom as inactive" do
classroom.archive!
classroom.active == false
end
end
end
end
end
Your archive! method is trying to save the model to the database. And since you created it as a stubbed model, it doesn't know how to do this. You have 2 possible solutions for this:
Change your method to archive, don't save it to the database, and call that method in your spec instead.
Don't use a stubbed model in your test.
Thoughtbot provides a good example of stubbing dependencies here. The subject under test (OrderProcessor) is a bona fide object, while the items passed through it are stubbed for efficiency.

Resources