RSpec : Multiple scenarii for same context - ruby-on-rails

How to create multiple scenarii without to have to reinit context between 2 tests with RSpec ? I don't need context reinitialisation (that is very slow), but I need to check multiple things for the same given context. The example below works, but it's a bad example : the context is reinitialized. If I do before(:all), it doesnt works because of the stubs. Any idea ?
feature 'Aids page' do
context 'No active user' do
before(:each) do
create_2_different_aids
disable_http_service
visit aids_path
end
after(:each) do
enable_http_service
end
scenario 'Should display 2 aids NOT related to any eligibility' do
display_2_aids_unrelated_to_eligibility
end
scenario 'Should not display breadcrumb' do
expect(page).not_to have_css('.c-breadcrumb')
end
end
end

feature specs often have more than one expect in the same scenario. They are not like unit tests where each it should test only one thing... they are more like what a user actually does on a page: "go here, click on this thing, check I can see that thing, click there, check that the thing changes" etc... so you can just do something like this:
feature 'Aids page' do
context 'No active user' do
scenario 'Sees aids not related to eligibility' do
create_2_different_aids
disable_http_service
visit aids_path
expect(page).not_to have_css('.c-breadcrumb')
display_2_aids_unrelated_to_eligibility
enable_http_service
end
end
end
Alternatively... it's possible to use either shared setup (as you have already done). That is fairly common.

Found. Actually there is a hack you can use to initialize your test only once, as the example shown below :
feature 'Aides page' do
context 'No active user' do
that = nil
before do
if !that
create_2_different_aids
disable_http_service
visit aides_path
that = Nokogiri::HTML(page.html)
end
end
after do
enable_http_service
end
scenario 'Should display 2 aids NOT related to any eligibility' do
display_2_aids_unrelated_to_eligibility(that)
end
scenario 'Should not display breadcrumb' do
expect(that.css('.c-breadcrumb').size).to eq(0)
end
end
end

Related

Assigns has been extracted to a gem. Use gem 'rails-controller-testing'. Is there an alternative for the gem?

I am writing some tests for a controller tasks, the index action, which has an instance variable #tasks with all the tasks (Task.all).
If I follow the official documentation:
RSpec.describe TeamsController do
describe "GET index" do
it "assigns #teams" do
team = Team.create
get :index
expect(assigns(:teams)).to eq([team])
end
it "renders the index template" do
get :index
expect(response).to render_template("index")
end
end
end
The assigns method is moved to the gem file 'rails-controller-testing'.
I have two questions:
1 - How can I achieve the same as expect(assigns(:teams)).to eq([team]). I guess I am asking, how can I check if I have an instance variable in the index action with values [team]
2 - If this method was moved to the gem, I read in the Github issues, that the reason is: You shouldn't test it there, controller should just test response, cookies etc. But I am confuse, since in relish you can test the instance variable. Should I test it there or not? If not, where? In my views/index_spec.rb, testing if I have all the teams?
3 - Alternative: Since TeamsController is a normal class, should I create a spec in the spec/models/folder spec/models/tasks_controller.rb and there test if the method index has the instance variable #teams with the content that I want?
Thanks
The whole idea is that instead of poking inside your controller and testing its internal variables is flawed you should instead test your controllers by testing the output.
In RSpec you can do this with request and feature specs.
# config/specs/features/teams_spec.html
RSpec.feature 'Teams' do
scenario 'when a user views the teams' do
Team.create(name: 'Team Rocket')
visit '/teams'
expect(page).to have_content 'Team Rocket'
end
end
# config/specs/requests/teams_spec.html
RSpec.describe 'Teams', type: :request do
describe 'GET /teams.json' do
it "includes the team" do
team = Team.create(name: 'Team Rocket')
get teams_path(format: :json)
expect(parsed_response.first['name']).to eq 'Team Rocket'
end
end
describe 'GET /teams' do
it "includes the team" do
team = Team.create(name: 'Team Rocket')
get teams_path
expect(page).to have_content 'Team Rocket'
end
end
end
The key difference is that feature specs test the app from a user story POV by driving a browser simulator while request specs are lighter weight and you just test against the raw response.
1 - How can I achieve the same as expect(assigns(:teams)).to
eq([team]). I guess I am asking, how can I check if I have an instance
variable in the index action with values [team]
Either use the assigns gem for legacy compatiblity or test the rendered output.
2 - If this method was moved to the gem, I read in the Github issues,
that the reason is: You shouldn't test it there, controller should
just test response, cookies etc. But I am confuse, since in relish you
can test the instance variable. Should I test it there or not? If not,
where? In my views/index_spec.rb, testing if I have all the teams?
If by Relish you mean RSpec, then its been taking a while for RSpec-rails to catch up to the state-of-art in Rails testing. But the same still applies. The offical recommendation of the RSpec team is to not use assigns and faze out controller specs in favor of request specs. View specs are not really relevant here - they are used if you want to test complex views in isolation.
3 - Alternative: Since TeamsController is a normal class, should I
create a spec in the spec/models/folder
spec/models/tasks_controller.rb and there test if the method index has
the instance variable #teams with the content that I want?
Just no. Controllers are not just normal classes. You can't just instantiate a controller with MyController.new, thats why controller tests have all that stubbing in place.

Rspec test case in after_save

I have two models Page Article. For every article created a page gets created with the attributes of article. As follows:
class Article
after_save :article_page_create
def article_page_create
site = Site.find_by(data_proxy_id: self.data_proxy_id)
page = Page.where(entity_id: self.id)
if page.blank?
if article_type == 'StaticPage'
Page.create(entity_id: self.id, url: "/static/#{self.url_part}", page_type: 'static_page')
else
Page.create(entity_id: self.id, url: self.url, page_type: 'article_page')
end
else
return page.update(url: self.url) unless article_type == 'StaticPage'
page.update(url: "/static/#{self.url_part}")
end
end
end
I am trying test cases for the first time. So far this is how far I got.
article_spec.rb
require 'rails_helper'
RSpec.describe Article, type: :model do
context 'validation tests' do
it 'ensures article attrs presence' do
page = Page.create(entity_id: self.id, url: "/static/#{self.url_part}", page_type: 'static_page')
expect(page).to eq(true)
end
end
end
I just wanted know is this the way to test my after_save method. Correct me if I am wrong, please.
Hmmmm, I think I can help out here.
When testing callbacks, you need to test two assumptions:
Is it being called for the correct event?
Is it doing what it's supposed to be doing?
And remember, you want to try make sure your tests cover one specific case.
Your specs should be reading:
On saving an article,
I expect the class to receive a callback method
On saving a new article,
I expect the number of Page elements to increase by one
On saving an old article,
I expect an existing page to be updated
You can continue to flesh out based on the article types etc.
E.g.
it 'triggers a callback to create a page on save' do
expect(my_class_instance).to receive(:article_page_create)
#article.save
end
context 'when creating a new page' do
it "creates a new article" do
# expect number of pages to change by 1
end
end
context 'when updating an old page' do
it 'updates the corresponding article' do
# expect attribs to be correct for corresponding page
end
end

How to avoid bloated, deeply nested blocks when feature testing in RSpec/Cucumber?

Often when feature testing in RSpec/Cucumber I want to emulate a sequence of related procedural user actions. For instance, a user might sign in, then update profile, then take some other action on the site, then sign out, and so on...
In RSpec, an analogous feature test might look something like this:
describe "Step 1: Sign in" do
before do
# Capybara logic
end
describe "Step 2: Visit settings page" do
before do
end
describe "Step 3: Update profile" do
before do
# More Capybara/etc...
end
describe "Step 4: Making a new post" do
before do
# ......
end
describe "Step 5: Viewing inbox" do
before do
# ...
# More nested code etc, etc, etc...
end
end
end
end
end
end
However, clearly this nesting very quickly gets out of control, especially when testing complex behavior on a site. Furthermore, the "before" blocks in the outermost nesting get run more times than then inner before blocks, unnecessarily slowing down the test suite.
So, is there perhaps a more elegant way to handle this? I'm able to clean things up slightly by using shared_examples_for, it_behaves_like, shared_context, before(:all), helper methods, etc. But it seems rather awkward when all I want to do is run the tests procedurally without each test needing to run in isolation. In other words, I'm looking for a test DSL like this:
# Step 1:
test "Sign in" do
visit "/sign_in"
fill_in "whatever"
# ...
end
# Step 2:
test "Update profile", after: "Sign in" do
# Runs after "Sign in" sharing same context so variables and object states aren't reset
end
And yes I know in principle tests run in isolation, but let's be fair, doing so isn't always practical when each step in the test may rely on the outcome of a prior test.
Specs work great when grouped by requirements. It DRY's up the setup/teardown for each condition and makes them easy to scan when you know what the case is but don't know what the functionality would be.
For example:
describe "a logged in user" do
before do
# log in
end
describe "with 3 items" do
before do
# add 3 items
end
it "displays 3 items in the cart" do
end
end
describe "with 0 items" do
it "displays an empty cart" do
end
end
end
I don't think nesting specs works well for describing implementation (e.g. first do this, then do that). That seems to explode out really quickly

Is is it a good idea to test if records were displayed on a page with feature specs

I have a page where user should see items he created. So I decided I want a feature test for that(with rspec and capybara).
What I wanted to do something like this:
require 'spec_helper'
feature "Accounts pages" do
scenario "user views his accounts" do
user = create(:user)
sign_in user
expect(current_path).to eq accounts_path
expect(page).to have_content "Your accounts"
#here I want to check if records were loaded and/or displayed on page
end
end
So my questions:
Is it a good idea to test if records were loaded on a specific page in a feature spec, or should I write controller spec for that?
If it's ok to go with feature spec here, what would be idiomatically correct way to do that?
You can't use controller spec to test if records are shown by any page as controller doesn't care what's displayed and what's not. In that case using feature spec sounds just fine.

Best practice for reusing code in Rspec?

I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end

Resources