In views tests, when to use before(:all)? - ruby-on-rails

I'm writing some views tests in an application and my tests expects something like:
describe 'form' do
it 'has a search form' do
render
expect(rendered).to have_selector 'form[id=mock_search]'
end
it 'has a name filter' do
render
expect(rendered).to have_selector 'label[for=q_name_cont]', text: 'Nome do simulado'
expect(rendered).to have_selector 'input[id=q_name_cont]'
end
it 'has a submit button' do
render
expect(rendered).to have_selector 'input[type=submit][value="Buscar"][name=commit]'
end
it 'has a reset button' do
render
expect(rendered).to have_selector 'input[type=submit][value="Limpar filtros"]'
end
end
But I have and before(:each) that iterates too many requests on application making my tests spend 25 seconds to run. I've changed to before(:all) and then become to 4 seconds.
Should I still use before(:each)?
Why before(:all) were not recommended?
EDIT: My before iterations:
before(:each) do
#school = build(:school)
#teacher = build(:teacher)
build_list(:mock_with_proccessed_statistics, 2, school: #school, teacher: #teacher)
#mocks = Mock.page(nil)
#q = Mock.ransack
allow(view).to receive(:current_school).and_return(#school)
allow(view).to receive(:format_date) { |date, format| date.strftime(format) }
end

describe 'form' do
#
#let! will create create instance variable school
#inside your test case and assign your school build. Different between
#let! and let is: let! create before every task. You won't need it to put
#on before block and let doesn't do that you have to call it explicitly to
#create that mock and get values..
let!(:school) { build(:school) }
let!(:teacher) { build(:teacher) }
let!(:statistics) { build_list(:mock_with_proccessed_statistics, 2, school: #school, teacher: #teacher) }
let!(:mocks) { Mock.page(nil) }
subject { render }
it 'has a search form' do
expect{ subject }.to have_selector 'form[id=mock_search]'
end
it 'has a name filter' do
expect{ subject }.to have_selector 'label[for=q_name_cont]', text: 'Nome do simulado'
expect{ subject }.to have_selector 'input[id=q_name_cont]'
end
end

before(:each) execute before each test case executes. and before(:all) once before all test cases inside a current context.

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.

Using Let to build multiple instances of a model

I'm trying to run a test on will_paginate. I know that it technically works, but I can't get the spec to work because of my inability to create multiple records. I'm using Capybara and Rspec on with Ruby on Rails.
Here is what I have in my feature spec.
RSpec.describe "Users Index", type: :feature do
describe "Pagination" do
let(:valid_user) { create(:user, name: "Mogli") }
let(:other_user) { create(:user, 50) }
it "successfully paginates" do
log_in_as_feature(valid_user)
visit users_path
puts URI.parse(current_url)
expect(page).to have_css('div.pagination')
expect(page).to have_link(href: user_path(valid_user), text: valid_user.name)
first_page_of_users = User.paginate(page: 1)
first_page_of_users.each do |user|
expect(page).to have_link(href: user_path(user), text: user.name)
unless user == valid_user
expect(page).to have_link(href: user_path(user), text: "delete")
end
end
end
end
end
My factory is simple:
FactoryGirl.define do
sequence(:name) { |n| "Person#{n}" }
factory :user do
name
email { "#{name}#example.com" }
password "foobar"
password_confirmation "foobar"
activated true
activated_at Time.zone.now
end
end
My fourth line is the culprit. It's not actually building the users. I tried to use a FactoryGirl.create(:user, 50), but that ends up breaking 27 tests across the board, and I have to reset the test database.
I don't know how else to create more than one dummy user at once, all the while keeping Mogli as first. Any insight is appreciated.
Edit: If I commented the have_css test, then my tests pass.
Here is the error of the div
1) Users Index Pagination successfully paginates
Failure/Error: expect(page).to have_css('div.pagination')
expected to find css "div.pagination" but there were no matches
# ./spec/features/users_index_spec.rb:13:inblock (3 levels) in '
Finished in 0.82368 seconds (files took 2.17 seconds to load)`
EDIT: adding my partial and index.html.erb view.
My view just renders #users partial
which is:
1 <li>
2 <%= gravatar_for user, size: 50 %>
3 <%= link_to user.name, user %>
4 <% if current_user.admin? && !current_user?(user) %>
5 | <%= link_to "delete", user, method: :delete,
6 data: { confirm: "You sure?" } %>
7 <% end %>
8
9 </li>
Your example has a few issues. As mentioned by others let is lazily evaluated, so to create objects that aren't directly referenced you would need to use let!. Additionally, FactoryGirl's create doesn't take a number of records to produce, you need create_list for that. Finally, Capybara's have_link takes the text of the link you're looking for as the first parameter so there's no need to pass a :text option
RSpec.describe "Users Index", type: :feature do
describe "Pagination" do
let!(:valid_user) { create(:user, name: "Mogli") }
let!(:other_users) { create_list(:user, 50) }
it "successfully paginates" do
log_in_as_feature(valid_user)
visit users_path
puts URI.parse(current_url)
expect(page).to have_css('div.pagination')
expect(page).to have_link(valid_user.name, href: user_path(valid_user))
User.paginate(page: 1).each do |user|
expect(page).to have_link(user.name, href: user_path(user))
expect(page).to have_link("delete", href: user_path(user)) unless user == valid_user
end
end
end
First of all, let is lazy-evaluated. That means it will not be evaluated until the moment you call it in your spec. You don't use other_user in your spec, so it is not evaluated.
Secondly, if you want to create a list of 50 users to set up your spec, use a before block:
before do
# using create_list
create_list(:user, 50)
# OR just
50.times { create(:user) }
end
it "successfully paginates" do
# ...
end
As mentioned already by Jan, let is lazily-evaluated. If you don't use other_user, it will never be created. There's an eager-counterpart, though.
let!(:other_user) { create(:user, 50) }
This one is always created.

rspec doesn't run first test case

The block #update doesn't run. Why? How to change it to run all of them. #anything works fine.
describe UsersController, type: :controller do
login_admin
describe '#update' do
def user_update_params(roles:)
{
role_ids: roles.map(&:id),
name: 'new'
}
end
shared_examples_for 'update user' do
it 'change the user' do
expect do
put :update, id: user.id, user: user_params
end.to change { user.reload.name }
end
end
end
describe '#anything' do
it 'is ok' do
#runs ok
end
end
end
It's a shared example, not a real test. It is supposed to be included in other test groups. Like this:
describe '#whatever' do
it_behaves_like 'update user'
it 'runs shared example' do
end
end

Rspec: tests in after block

I have a spec like this:
context 'index' do
let!(:article) { create :article }
subject { visit articles_path }
specify do
subject
expect(page).to have_content(article.title)
end
end
However when I try to refactor it like this, it says I have 0 examples:
context 'index' do
let!(:article) { create :article }
subject { visit articles_path }
context do
after { expect(page).to have_content(article.title) }
end
context do
before { login_as :user }
after { expect(page).to have_content(article.comment) }
end
context do
after {}
end
end
Shouldn't it be running subject and then the after hook? I am pretty sure I have used this setup before.
Please tell me how to correctly refactor this. Thanks.
Update
Could do it like this, but I don't really like it:
subject do
visit articles_path
page
end
specify do
expect(subject).to have_content(article.title)
end
'after' block is normally used to do something after the test example has been executed. In your second example you have no test executed prior to the 'after' block
I would refactor your code like this
context 'index' do
let!(:article) { create :article }
visit articles_path
expect(page).to_not have_content(category.title)
end
edit
context 'index' do
let!(:article) { create :article }
before do
visit articles_path
end
context do
it "displays article's title" do
expect(page).to have_content(article.title)
end
end
context do
before { login_as :user }
it "displays article's comments" do
expect(page).to have_content(article.comment)
end
end
context do
#another test
end
end
It seems to me from you refactoring attempt that there's a bit of confusion.
First, the reason why you were seeing 0 examples, 0 failures is because there is no subject invocation in your tests. Think about it like this:
subject { visit articles_path }
it 'has a nice title' do
subject
expect(page).to have_content(article.title)
end
it 'has a nice comment' do
subject
expect(page).to have_content(article.comment)
end
In order for your expectations to work you need to call the subject. In fact you could have even avoided using a subject by explicitly writing in your it/specify blocks visit articles_path
it 'has a nice title' do
visit articles_path
expect(page).to have_content(article.title)
end
it 'has a nice comment' do
visit articles_path
expect(page).to have_content(article.comment)
end
Tests that share the same subject can be dried up using subject { ... }.
Second, don't confuse context blocks with specify/it blocks(remember they're aliased).
A context is a way to make your tests more understandable by separating different results for the test.
subject { visit articles_path }
context 'user is logged in' do
it 'displays article's title' do
login_as :user
subject
expect(page).to have_content(article.title)
end
it 'displays article's title' do
login_as :user
subject
expect(page).to have_content(article.comment)
end
end
context 'user not logged in' do
it 'displays article's comments' do
subject
expect(page).to have_content('Log in')
end
end
You can have different expectations, it/specify blocks in the same context.
Use different context to specify different behaviour of the same functionality.
Finally last step. Group shared functionality in a before block. In our example:
subject { visit articles_path }
before do
subject
end
context 'user is logged in' do
before do
login_as :user
end
it 'displays article's title' do
expect(page).to have_content(article.title)
end
it 'displays article's title' do
expect(page).to have_content(article.comment)
end
end
context 'user not logged in' do
it 'displays article's comments' do
expect(page).to have_content('Log in')
end
end
As you can see, the two contexts run the subject but only the first content logs the user in to test the article page whereas the second context don't.
I hope this was useful.
Keep testing and soon it will become a matter of habit and you will be writing tests much easily.

RSpec refactoring

I'm new to Ruby, Rails and RSpec, so I'm doing the railstutorial. In Chapter 10, exercise 1, I've implemented a test for micropost pluralization that I'm trying to refactor.
My first try was:
describe "micropost pluralization" do
let(:new_user) { FactoryGirl.create(:user) }
before do
sign_in new_user
visit root_path
end
describe "with no microposts" do
it "should have no microposts" do
expect(page).to have_text("0 microposts")
end
end
describe "with one micropost" do
before do
FactoryGirl.create(:micropost, user: new_user, content: "Lorem ipsum")
visit root_path
end
it "should have one micropost" do
expect(page).to have_text("1 micropost")
end
end
describe "with two microposts" do
before do
FactoryGirl.create(:micropost, user: new_user, content: "Lorem ipsum")
FactoryGirl.create(:micropost, user: new_user, content: "Dolor sit amet")
visit root_path
end
it "should have two microposts" do
expect(page).to have_text("2 microposts")
end
end
end
This clearly violates the DRY principle. As this test case describes a sequence, I tried to refactor by nesting:
describe "micropost pluralization" do
let(:new_user) { FactoryGirl.create(:user) }
before do
sign_in new_user
visit root_path
end
it "should have no microposts" do
expect(page).to have_text("0 microposts")
end
describe "when one micropost is created" do
before do
FactoryGirl.create(:micropost, user: new_user, content: "Lorem ipsum")
visit root_path
end
it "should have one micropost" do
expect(page).to have_text("1 micropost")
end
describe "and then another micropost is created" do
before do
FactoryGirl.create(:micropost, user: new_user, content: "Dolor sit amet")
visit root_path
end
it "should have two microposts" do
expect(page).to have_text("2 microposts")
end
end
end
end
But I'm still repeating the "visit root_path" line, and that nesting looks ugly.
My questions are:
1 - Is there a better way than nesting to describe a sequence in RSpec? I tried mixing the creation of microposts and the assertions and didn't work.
2 - How could I remove the "visit root_path" duplication?
Thanks for reading this far!
You could try using shared_example_groups:
shared_examples_for 'page with n microposts' do |number_of_posts, expected_text|
before do
number_of_posts.times do
FactoryGirl.create(:micropost, user: new_user, content: "Lorem ipsum")
end
visit root_path
end
it "should have correct text" do
expect(page).to have_text(expected_text)
end
end
it_should_behave_like 'page with n microposts', 0, '0 microposts'
it_should_behave_like 'page with n microposts', 1, '1 micropost'
it_should_behave_like 'page with n microposts', 2, '2 microposts'
I would personally make just a helper method to extract my logic for creation of objects.Here is the code and a better solution would be to pull create_microposts in a helper module to be included in your tests.I use context which describe clearly my expectations and that those are just similar tests, connected to each other.
describe "micropost pluralization" do
def create_microposts(microposts)
microposts.each do |micropost|
FactoryGirl.create(:micropost, user: new_user, content: micropost)
end
end
let(:new_user) { FactoryGirl.create(:user) }
before do
sign_in new_user
create_microposts microposts
visit root_path
end
context "when no microposts exist for user" do
let(:microposts) { [] }
it { is_expected.to have_text("0 microposts") }
end
context "when one micropost is created" do
let(:microposts) { ["Lorem ipsum"] }
it { is_expected.to have_text("1 microposts") }
end
context "when two micropost are created" do
let(:microposts) { ["Lorem ipsum", "Dolor sit amet"] }
it { is_expected.to have_text("2 microposts") }
end
end
In order to avoid nesting to describe sequences in RSpec you might want to check out contexts. As was pointed out in the comment to my answer, you cannot remove the duplicity of the visit_root_path due to the fact that the various test cases need different prerequisites (i.e. one needs 1 post to be created, the other needs 2).
I'd write this test suite like so:
describe 'micropost pluralization' do
let :new_user { create :user }
before :all do
sign_in new_user
end
context 'no microposts' do
it 'should have no microposts' do
visit_root_path
expect(page).to have_text('0 microposts')
end
end
context 'one or more microposts' do
# you could have a before :each here do create the posts, but
# since you only have two test cases I don't feel it's necessary.
it 'should have one micropost' do
create :micropost, user: new_user, content: 'Lorem ipsum'
visit_root_path
expect(page).to have_text('1 micropost')
end
it 'should have two microposts' do
2.times.do { create :micropost, user: new_user, content: 'Lorem ipsum' }
visit_root_path
expect(page).to have_text('2 microposts')
end
end # end context
end # end describe
It might not look perfectly DRY, but it certainly looks readable to me.
I cannot recommend betterspecs enough for test writing by the way :-)

Resources