I am using Rubymine to create a project in Rails4, rspec and capybara. When I use the let syntax for defining variables in Capybara features, it seems RubyMine isn't able to detect the existence of the variables. For instance in this code below, the variable capsuleHash, capsuleForm and capsuleViewPage are all not being recognized in intelliJ in the scenario section. Does anyone have a workaround?
require 'spec_helper'
feature 'Capsules Feature' do
let(:capsuleHash) {attributes_for(:tdd_capsule)}
let(:capsuleForm) {CapsuleCreateForm.new}
let(:capsuleViewPage) {CapsuleViewPage.new}
scenario 'Add a new capsule and displays the capsule in view mode' do
visit '/capsules/new'
expect{
capsuleForm.submit_form(capsuleHash)
}.to change(Capsule,:count).by(1)
capsuleViewPage.validate_on_page
expect(page).to have_content capsuleHash[:title]
expect(page).to have_content capsuleHash[:description]
expect(page).to have_content capsuleHash[:study_text]
expect(page).to have_content capsuleHash[:assignment_instructions]
expect(page).to have_content capsuleHash[:guidelines_for_evaluators]
expect(page).to have_link 'Edit'
end
end
I'm not familiar with RubyMine other than it's an IDE for Ruby. The way you phrased your question, though, I'm assuming that you're referring to some feature of RubyMine which displays the "variables" defined at any particular point in a program.
If this is case, the reason that the symbols you've passed to let wouldn't "show up" as variables is because they are not being defined as variables. They are being defined as methods which return the value of the associated block. On the first call from within each it block, the value of the block is remembered and that value returned on subsequent calls within the same block.
Note that there is nothing wrong with the RSpec code in terms of defining those methods. The following code passes, for example:
class Page
def has_content?(content) true ; end
def has_link?(link) true ; end
end
page = Page.new
class CapsuleCreateForm
def submit_form(hash)
Capsule.increment_count
end
end
class CapsuleViewPage
def validate_on_page
end
end
def attributes_for(symbol)
{}
end
def visit(path)
end
class Capsule
##count = 0
def self.count
##count
end
def self.increment_count
##count += 1
end
end
describe 'Capsules Feature' do
let(:capsuleHash) {attributes_for(:tdd_capsule)}
let(:capsuleForm) {CapsuleCreateForm.new}
let(:capsuleViewPage) {CapsuleViewPage.new}
it 'Add a new capsule and displays the capsule in view mode' do
visit '/capsules/new'
puts method(:capsuleHash)
expect{
capsuleForm.submit_form(capsuleHash)
}.to change(Capsule,:count).by(1)
capsuleViewPage.validate_on_page
expect(page).to have_content capsuleHash[:title]
expect(page).to have_content capsuleHash[:description]
expect(page).to have_content capsuleHash[:study_text]
expect(page).to have_content capsuleHash[:assignment_instructions]
expect(page).to have_content capsuleHash[:guidelines_for_evaluators]
expect(page).to have_link 'Edit'
end
end
RubyMine does support let blocks, but you'll need to be sure to use the latest version, 6.0.2. See http://youtrack.jetbrains.com/issue/RUBY-14673
Related
I am experiencing strange very test behavior, with logged in state being handled inconsistently.
The spec logs a user in, visits a (nested or un-nested) index page, and checks that the correct content is displayed. Records are fetched asynchronously, though I don't think this should have an impact.
When each spec is run individually, they each pass. When all specs are run together, they fail because the expected content is missing. Using save_and_open_page reveals this is because the login page is being rendered, rather than the expected index page.
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
The tests look something like this
let(:user) {create :user}
let(:team) {create :team}
let(:country) {create :country}
before :each do
login_as( user, scope: :user )
end
describe 'unnested' do
it 'should have the expected content', :js do
visit users_path
is_expected.to have_content "some content on the page"
end
end
describe 'nested by team' do
it 'should have the expected content', :js do
visit team_users_path(team)
is_expected.to have_content "some content on the page"
end
end
describe 'nested by nationality' do
it 'should have the expected content', :js do
visit country_users_path(country)
is_expected.to have_content "some content on the page"
end
end
The specs all require javascript (I don't know whether that is important here).
Authentication is handled by Devise, and my rails_helper.rb includes
config.append_after(:each) do
DatabaseCleaner.clean
Warden.test_reset!
end
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
It took a long time to get to the bottom of this. Posting this hear in case it is of help to anyone else encountering the same issue.
After much searching I eventually found this small mention that login_as may not work with Poltergeist when js is enabled on your test scenarios.
I tried the suggested fix to deal with shared DB connections. Unfortunately this resulted in the following errors:
PG::DuplicatePstatement at /session/users/signin
ERROR: prepared statement "a1" already exists
I tried using the Transactional Capybara gem, but this did not seem to work well with Poltergeist.
Eventually I abandonned login_as completely, and instead wrote a short method that visits the login page, fills in email and password, and logs in that way.
This solution appears to be working. It adds a little overhead, so I'm only using it for tests with JS.
If you are using Capybara gem then there is no need to use :js with test cases
What I did if this helps-
scenario "visit with user signed in" do
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
visit "/"
expect(current_path).to eq('/')
expect(page).to have_title "Some Random Title"
end
The other way you can login user using feature specs like-
feature 'User signs in' do
before :each do
#user = FactoryGirl.create(:user)
end
scenario "Signing in with correct credentials" do
visit "/"
fill_in "Email", with: #user.email
fill_in "Password", with: #user.password
click_button "Log In"
expect(current_path).to eq("/login/useremail/verification")
expect(page).to have_content "Signed in successfully"
end
end
If your pages are ajax then refer to this https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara
I have a new action which creates a circle and assigns the current parent as its administrator:
def new
return redirect_to(root_path) unless parent
#circle = Circle.new(administrator: parent)
end
I'm trying to test that the administrator ID is properly set, and have written out my test as such:
context 'with a parent signed in' do
before do
sign_in parent
allow(controller).to receive(:circle).and_return(circle)
allow(Circle).to receive(:new).and_return(circle)
end
it 'builds a new circle with the current parent as administrator' do
get :new
expect(#circle.administrator).to equal(parent)
end
end
This obviously throws an error as #circle is nil. How can I access the new object that hasn't yet been saved from my controller tests? I'm guessing it is some variety of allow / let but as I say all my searches have yielded nothing so far.
You're approaching the problem wrong. Test the behavior of the controller. Not its implementation.
If this is a legacy application you can use assigns to access the #circle instance variable of the controller:
context 'with a parent signed in' do
before do
sign_in parent
end
it 'builds a new circle with the current parent as administrator' do
get :new
expect(assigns(:circle).administrator).to equal(parent)
end
end
But Rails 5 removes assigns and using it is not encouraged in new projects.
Instead I would use a feature spec and actually test the steps of creating a circle:
require 'rails_helper'
RSpec.feature 'Circles' do
let(:parent) { create(:parent) }
context "a guest user" do
scenario "can not create circles" do
visit new_circle_path
expect(page).to have_content 'Please sign in'
end
end
context "when signed in" do
background do
login_as parent
end
scenario "can create circles" do
visit new_circle_path
fill_in 'name', with: 'Test Circle'
expect do
click_button 'Create circle'
end.to change(parent.circles, :count).by(+1)
expect(page).to have_content 'Test Circle'
end
end
end
You can use assigns:
expect(assigns(:circle).administrator).to eq parent
However, note that with rails 5, this gets deprecated. The rational being that checking the assigned instance variables in controllers is too fragile and implementation specific.
The recommended alternative is either to test side effects (for example if this actually got persisted to the db) or do full feature tests.
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.
My spec file:
require 'spec_helper'
describe "State Contracts page" do
#state_data = {
:state_slug => 'Alabama',
:state_name => 'California'
}
before(:each) { visit state_path(:state=>"#{#state_data[:state_slug]}" )}
it 'should have header' do
page.should have_content("#{#state_data[:state_name]} Contracts")
end
# show statistics specification for State Contract
it "should have #{#state_data[:state_name]} Statistics details" do
page.should have_content("#{#state_data[:state_name]} Statistics")
page.should have_content('Total Obligated Amount')
page.should have_content('Total Transactions')
page.should have_content('Total Contractors')
page.should have_content('Total Contract Recipients')
page.should have_content('Total Offers')
end
end
# show State link
it "should have visible #{#state_data[:state_name]} Links" do
page.should have_content("#{#state_data[:state_name]} Links")
assert_equal(true, find_link("Agencies in #{#state_data[:state_name]}").visible?)
assert_equal(true, find_link("Contractors in "{#state_data[:state_name]}").visible?)
assert_equal(true, find_link("Contracts in #{#state_data[:state_name]}").visible?)
end
end
After when I run the test, I got next Error:
undefined method `[]' for nil class for "#{#state_data[:state_name]}"
I think i am interpolating hash variable but now not getting right.
You can't use instance variables in an it block without declaring it somewhere in a before. Wrap #state_data in your before(:each) block and it should work.
It would look like the following:
before do
#state_data = {
:state_slug => 'Alabama',
:state_name => 'California'
}
visit state_path(:state=>"#{#state_data[:state_slug]}"
end
My understanding is that using instance variables is considered an antipattern and you should consider using let() or subject() instead
Using let() would change this to:
let(:state_data) do
{
:state_slug => 'Alabama',
:state_name => 'California'
}
end
before { visit state_path(:state=>"#{state_data[:state_slug]}" }
it 'should have header' do
page.should have_content("#{state_data[:state_name]} Contracts")
end
Local variables or instance variables defined in a describe block are not accessible in any contained it blocks.
If you want to make arbitrary variables or methods available across multiple it blocks, you need to use let, let! or before. The let methods let you memoize helper methods while the before method let's you execute arbitrary Ruby code prior to executing the it block. The subject method is also available as means of defining the subject helper.
Of course, you can also define methods or variables within each it block.
I am using Steak to do acceptance testing because I didn't like cucumber at all although I am using some cucumber concepts in the way I test. I liked the declarative vs imperative styles for testings and I am abstracting some expectations into elaborated custom rspec matchers that insinde the match method use other matchers, heres an example:
RSpec::Matchers.define :show_post do |post|
match do |page|
within '.post' do
page.should have_content post.title
page.should have_content post.tagline
page.should have_content post.body
page.should list_author post.author
end
end
end
The only problem I am having is that if my matcher fails I get a generic message that doesn't give me any insight on what's missing, when what I really want is to now which one of the expectation that compose the custom matcher is not meet.
I've been living with this nuisance for a while because I really like the expressiveness of being able to do:
page.should show_post Post.last
Got it:
class ShowsPost
include Capybara::RSpecMatchers
def initialize post
#post = post
end
def matches? page
page.should have_content #post.title
page.should have_content #post.tagline
page.should have_content #post.body
page.should list_author #post.author
end
end
def show_post post
ShowsPost.new(post)
end
Better yet:
module DefineMatcher
def define_matcher name, &block
klass = Class.new do
include Capybara::RSpecMatchers
attr_reader :expected
def initialize expected
#expected = expected
end
define_method :matches?, &block
end
define_method name do |expected|
klass.new expected
end
end
end
module PageMatchers
extend DefineMatcher
define_matcher :show_notice do |page|
within '.alert-notice' do
page.should have_content expected
end
end
end
RSpec.configuration.include PageMatchers, :type => :acceptance