I have a project in which I am using RSpec and Capybara to do unit testing. I have fully flushed out model and controller tests which are passing nicely and handle the heavy lifting for pre-database validation.
I am now testing user experience and front end items and want to know how i could verify that a form did NOT submit. If a user mismatches passwords or some other erred data, I have script to set the error and prevent submission.
I know i could search for error text but where there a way to check that the 'submit' never happened and feel confident that no server trip was made.
I want something like:
it "should not sumbit if user name is less than 3 characters" do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
# HOW DO I DO THIS?
expect( ... ).not_to submit_to_server
end
This is not something that you should be testing in an integration test. In integration tests you are taking the point of view of the end user, and therefore you should only be testing what the user can actually see. If the only evidence the user sees that the form has not been submitted is an error message, then that is what you should test for.
In integration tests we most test what the user would see like if an empty field is given then there must be error message according to validations. But still if you want you check as follow
describe "User Registration" do
before do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
end
it "should not sumbit if user name is less than 3 characters" do
page.should have_content "your error message"
end
it "should create the user and redirect to blah_path" do
current_path.should eq blah_path
end
it "should add user in users table" do
expect { user.create }.to change(User, :count).from(0).to(1)
end
end
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 rails app that includes a blog feature.
In one single feature test (using Capybara::Rails::TestCase, with ruby tests (asserts/refutes) i.e, not spec) for the blog, I want to test adding a post, adding comments, editing the post, etc. as individual tests - each of these tests builds upon the last one, as the post created in the first test is commented on in the second test, and so on.
I have seen posts which show workarounds for doing this in a unit test (global variables, use setup/teardown), but I wondered if there is a more direct way to do it in a feature test, since it is likely more common here.
Ideally, I want the login session to persist, as well as database records created in previous tests to persist across each test in the TestCase. Setup and teardown could be used to login each time, but not the intermediate records created for posts, comments, etc.
I want something like:
class BlogTest< Capybara::Rails::TestCase
test 'can sign in' do
user = User.create!(name: "user",
email: "user#example.com",
password: "passw0rd!", password_confirmation: "passw0rd!")
visit new_user_session_path
fill_in('Login', :with => user.email)
fill_in('Password', :with => user.password)
check('Remember me')
click_button('Sign in')
end
test 'can create post' do
visit new_post_path # how can I have user logged in?
fill_in "Title", with: "My first post title!"
fill_in "Body", with: "My first post body!"
click_button "Publish"
end
test 'can comment on post' do
visit post_path(Post.first) # should go to post created in last test
click_button "Add comment"
...
end
end
I have heard that this may be possible in Cucumber, but chose not to use Cucumber for other reasons, so want it to work with Minitest and Capybara.
Capybara::Rails::TestCase inherits from ActiveSupport::TestCase. One of ActiveSupport::TestCase's main features is that it runs each test in a database transaction. There are ways to work around this, but I would not recommend them.
Instead, I suggest you work with the behavior of the rails test classes. In this case, you want to share actions between tests. I recommend you extract those actions into methods and call those methods in your tests. Here is how I would implement this with your test code:
class BlogTest< Capybara::Rails::TestCase
def user
#user ||= User.create!(name: "user",
email: "user#example.com",
password: user_password,
password_confirmation: user_password)
end
def user_password
"passw0rd!"
end
def sign_in(email, password)
visit new_user_session_path
fill_in('Login', :with => email)
fill_in('Password', :with => password)
check('Remember me')
click_button('Sign in')
end
def create_post(title = "My first post title!",
body = "My first post body!")
visit new_post_path # how can I have user logged in?
fill_in "Title", with: title
fill_in "Body", with: body
click_button "Publish"
end
def comment_on_post(post, comment)
visit post_path(post)
click_button "Add comment"
# ...
end
test "can sign in" do
sign_in(user.email, user_password)
# add assertions here that you are signed in correctly
end
test "can't sign in with a bad password" do
sign_in(user.email, "Not the real password")
# add assertions here that you are not signed in
end
test "can create post when signed in" do
sign_in(user.email, user_password)
create_post
# add assertions here that post was created correctly
end
test "can't create post when not signed in" do
create_post
# add assertions here that post was not created
end
test "can comment on post when signed in" do
sign_in(user.email, user_password)
create_post
post = user.posts.order(:created_at).last
comment_on_post(post, "I can comment because I'm signed in!")
# add assertions here that comment was created correctly
end
test "can't comment on post when not signed in" do
post = Post.first
comment_on_post(post, "I can't comment because I'm not signed in!")
# add assertions here that comment was not created
end
end
Each action has a good name, and you can reuse those actions for different types of tests. Each test is executed within a database transaction, so each time the each test method is run the database looks the same.
I'm using rspec, capybara and Selenium to test my whole application stack. I've turned off transactional fixtures, and I'm using database cleaner to clean my database only after the whole suite has been run. These allows me to test things based using objects created in preceding tests.
Anyway, let's say I want to create user a999 (via a form, so a test in itself) and then proceed to test logging him out and logging him back in.
def sign_up(first_name, last_name, profile_name, email, password)
visit "/"
click_link "Register"
fill_in('First name', with: first_name)
fill_in('Last name', with: last_name)
fill_in('Profile name', with: profile_name)
fill_in('Email', with: email)
fill_in('Password', with: password)
fill_in('Password confirmation', with: password)
click_button 'Sign up'
end
feature "user a999 sign up", js: true do
before(:each){
sign_up( #a999.first_name, #a999.last_name, #a999.profile_name, #a999.email, #a999.password )
}
scenario "welcome message" do
expect(page).to have_content ("Welcome," + #a999.first_name)
end
scenario "can log out" do
end
scenario "can log in" do
end
end
The code above almost works. This is what happens when it's run:
The before block signs up the user before the "welcome message" expectation (I see it physically happening in Firefox thanks to Selenium), and then the welcome message appears after a redirect so the "welcome message" spec passes.
However, because I have the before block set to 'each' the before block is run another two times, meaning I now have three a999 users in the database.
Of course, and setting the before block to (:all) should fix this problem. The user is signed up one, and we go from there, signing the exact same user in and out. It's a feature test that tests the whole stack remember, so I want to do this properly, emulate how a real user will be using my app.
def sign_up(first_name, last_name, profile_name, email, password)
visit "/"
click_link "Register"
fill_in('First name', with: first_name)
fill_in('Last name', with: last_name)
fill_in('Profile name', with: profile_name)
fill_in('Email', with: email)
fill_in('Password', with: password)
fill_in('Password confirmation', with: password)
click_button 'Sign up'
end
feature "user a999 sign up", js: true do
before(:all){
sign_up( #a999.first_name, #a999.last_name, #a999.profile_name, #a999.email, #a999.password )
}
scenario "welcome message" do
expect(page).to have_content ("Welcome," + #a999.first_name)
end
scenario "can log out" do
end
scenario "can log in" do
end
end
But with this code nothing happens at all. Seriously, just nothing. Selenium doesn't follow the code in the before block at all! Firefox doesn't even start up.
Why is this? I mean, that should work at the very least.
before(:each) = signs user up before my eyes
before(:all) = completely dead
I can't explain why nothing comes up at all, but based on numerous posts*, you can't reasonable use before(:all) with capybara, since it resets the session between each example.
*Related posts:
Capybara and before(:all) in rspec
capybara/selenium with rspec before :all hook
So I have this spec
describe "visit /signup" do
before(:each) do
get signup_path
end
it "has an email input field" do
page.has_field?("#user_email")
end
it "accepts an email address" do
page.fill_in('#user_email', :with=>Faker::Internet.email)
end
end
The first test (has an email input) passes, the second fails with
Failure/Error: page.fill_in('#user_email', :with=>Faker::Internet.email)
Capybara::ElementNotFound:
cannot fill in, no text field, text area or password field with id, name, or label '#user_email' found
The input[type='text'] element exists on the page with that DOM ID, have tried locating with the ID with and without the hash, and using its input:name as a locator too.
What am I doing wrong?
It's because you're using get when you should be using visit inside the before block. This:
before(:each) do
get signup_path
end
Should be this:
before(:each) do
visit signup_path
end
Otherwise you're telling Rack::Test to visit that path, not Capybara! A small distinction that trips quite a few people up frequently!
maybe you should remove the #, e.g.
fill_in('user_email', :with=>Faker::Internet.email)
I think fill_in is not on page. Just use fill_in:
describe "visit /signup" do
before(:each) do
get signup_path
end
it "has an email input field" do
page.has_field?("#user_email")
end
it "accepts an email address" do
fill_in('#user_email', :with=>Faker::Internet.email)
end
end
This question is about how to best name RSpec example groups and examples in English.
I understand that in RSpec, describe, context (which are functionally equivalent) and it are supposed to give you complete sentences. So for example,
describe "Log-in controller" do
context "with logged-in user" do
it "redirects to root" do
...
end
end
end
reads Log-in controller with logged-in user redirects to root. Awesome.
But in my application, where I need to test all components on an ajaxy page (using Capybara), I tend to have example groups like this:
describe "blog post" do
describe "content" do
it "is displayed"
end
describe "comment" do
it "is displayed"
describe "editing" do
it "works" # successful comment editing
it "requires logged-in user" # error case 1
it "requires non-empty body" # error case 2
end
end
describe "comment form" do
it "works" # successful comment submission
it "requires valid email address" # error case 1
it "requires non-empty body" # error case 2
end
end
I see two anti-patterns here:
The nested describes don't read as sentences. Of course one could put an 's:
describe "blog post" do
describe "'s content" do
it "is displayed"
end
end
Or one could put a colon after "blog post:". Or ideally, I would write
describe "blog post" do
its "content" do
it "is displayed"
end
end
but that's not possible because its is about attribute access, and I just have strings here.
Is there a better way to deal with the "page components" problem?
For the functionality, the successful cases (for functionality like comment submission) are simply marked as it "works". At least this is concise and simple -- I find it slightly preferable to it "can be submitted and causes a comment to be added", because that just forces me to make up verbiage for something that is obvious. But is there a nicer, more "natural" way to do this?
Suggestions for how to restructure the example example-group ;) above would be appreciated!
You shouldn't really think about having examples be grammatically correct. It's fine if your test reads 'blog post content is displayed' without the 's. The test is readable and simple. What you really want is to be able to understand what is failing when a test doesn't work.
With regards to your second point, 'it works' is usually not descriptive enough. It doesn't let someone else know what you mean by 'works'. If you are actually testing many things it's best to split your examples up, for instance:
describe 'blog post' do
context 'creating a comment' do
it 'should require a logged-in user'
it 'should require a non-empty body'
it 'should require a valid email address'
it 'should create a new comment'
it 'should be submittable'
end
end