Hello I am newbie of Rails and now trying to make a simple tweeting application.
I wrote a simple test and tried to use before(:all) because I don't want to visit page each time, but the test doesn't keep the visit page object.
Off course when I use before(:each) the test success, but when it increase to a large number, I suppose the time of test increase, too.
How can I write this test with before(:all)? Thanks for your kindness.
# /spec/requests/tweet_pages.spec
require 'spec_helper'
describe "TweetPages" do
describe "GET /tweet_pages" do
before(:each) {visit tweets_path} # this line pass test but...
#before(:all) {visit tweets_path} # next line fails in second it test.
subject {page}
context do
its(:status_code) {should == 200}
it {should have_selector 'input'}
end
end
end
If I'm not mistaken before(:all) runs for all examples within a given describe or context block. In this case, your examples are in their own context block. If you would remove the context block the tests should pass. Otherwise you could add the before(:all) block to the describe or context block you want the before(:all) to have access to.
N.B. It is also recommended to add an after(:all) block to save yourself from the trouble before(:all) can give you. In general, using before(:all) isn't really recommended.
before(:each) runs once for each it statement.
before(:all) runs once for each context or describe block that it's in.
Trying to force before(:all) in your case would be counter-productive, but one approach would be to store the result in a ##class_variable and later reuse it in your subject.
Related
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.
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
Here's my integration spec. I would like to define those users at the global scope so I can make expectations based on them in all of the spec's expectations. However, doing this means the variables #user_0 and the rest aren't available inside the expectation.
Notice I've got that ap #user_0 in the first expectation to print the hash. It returns nil.
First question is, how do I make globally available variables in an integration spec?
It's worth noting that if I build the variables in the expectation, everything works fine then, it's just when the variables are built outside the expectation.
Am I right in thinking that this is an error? I mean surely I'm going to want to write expectations that use users defined in other specs? Or is each spec a sort of self contained thing, and if transitional fixtures are turned on, the test database is wiped between each expectation? It's just odd that these instance variables aren't scoped by default, as though it's been done on purpose.
require 'spec_helper'
require 'factory_girl'
#user_0 = FactoryGirl.build(:user_0)
#user_1 = FactoryGirl.build(:user_1)
#user_2 = FactoryGirl.build(:user_2)
#user_3 = FactoryGirl.build(:user_3)
describe "foo", js: true do
it "can create a user" do
ap #user_0 #=> nil
end
end
Add call to :before method to evaluate the specified block before each it operation:
require 'spec_helper'
require 'factory_girl'
describe "foo", js: true do
before :each do
#user_0 = FactoryGirl.build(:user_0)
#user_1 = FactoryGirl.build(:user_1)
#user_2 = FactoryGirl.build(:user_2)
#user_3 = FactoryGirl.build(:user_3)
end
it "can create a user" do
ap #user_0 #=> nil
end
end
And make sure that your user factory has been properly written.
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
I have a test suite structured as follows:
describe ... do
[list of dates].each do
describe
before(:all) do
base_date = ...
end
describe ... do
[list of times].each do
describe ... do
before(:all) do
base_time = base_date + ...
DateTime.stub!(:now).and_return(base_time)
end
describe ... do
<test using records within date-time range based on base_time>
end
describe ... do
<another test using records within date-time range based on base_time>
end
end
end
end
end
end
end
The first test has DateTime(now) == base_time, but the second test as DateTime(now) == my computer's date-time, indicating that the stub is no longer in effect. Moving the stub! call into each describe loop resolves the problem, but I would like to understand why it doesn't work as written.
The reason lies probably elsewhere, stubs work fine with multiple nested describe blocks. Maybe :all vs :each is the problem: before(:all) is executed once before all describe blocks are executed, while before(:each) is executed each time before a describe block is executed.
Or maybe it has something to do with stubbing DateTime, have you tried
DateTime.any_instance.stub(:now).and_return(base_time)