I currently have a test that doesn't seen to have the ability to visit particular paths. This test is a request spec and this is my first one, as far as I can tell request spec are similar to feature test and can user capybara to do things like visit and fill_in. But right now I can't get this request spec to even visit a path. Is there something I should know about request specs? I'll post my code and see if you see anything strange?
SPEC:
require "spec_helper"
describe "Mailchimp" do
describe "Manage list" do
it "adds new subscriber to list" do
VCR.use_cassette "mailchimp/subscriber" do
visit new_subscriber_path
expect {
fill_in "first_name", with: "John"
fill_in "last_name", with: "Mayer"
fill_in "phone_number", with: "61615551233"
fill_in "email", with: "john#rowster.com"
click_button "Sign Up"
}.to change(Subscriber, :count).by(1)
end
end
end
end
Let me know if you need to see anything else. Thank You!
Request specs used to be the same thing as feature specs in earlier versions of RSpec, but things have since changed.
Request specs are designed for you to hit the full stack via an HTTP request and inspect details from the response. You use methods like get, post, patch, and delete to interact with your application.
An example request spec:
get "/users/#{user.id}"
expect(response.body).to include user.full_name
Feature specs are driven by Capybara and allow you to hit the full stack via elements of the interface. If you want to hit a specific URL, that's when you use visit.
Your question includes an example of a feature spec, so I don't really need to echo it in this answer. My piece of advice related to your code would be to change it so that it's inspecting the interface and not how it changes the database.
(So I guess I will echo your code after all. :))
require "rails_helper"
feature "Mailchimp" do
describe "Manage list" do
scenario "adds new subscriber to list" do
VCR.use_cassette "mailchimp/subscriber" do
visit new_subscriber_path
fill_in "first_name", with: "John"
fill_in "last_name", with: "Mayer"
fill_in "phone_number", with: "61615551233"
fill_in "email", with: "john#rowster.com"
click_button "Sign Up"
expect(page).to have_content "You have successfully subscribed to our service"
end
end
end
end
Related
Let's say I have test on new input and test on new input when input is invalid (first test is on case input is valid).
For example (from my code):
scenario "valid input saving" do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
within("#student_0") do
fill_in "Имя", :with => "Name"
fill_in "Фамилия", :with => "Surname"
fill_in "Электронная почта", :with => "randommail#mail.com"
end
print page.html
click_button "Save"
expect(page).to have_current_path program_stream_path(#program, #stream)
#...other code
end
Obviously, test that checks behavior on invalid input repeats this part:
scenario "invalid input leads to correct input page" do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
#other code
How to avoid this copy-paste way?
You can use before blocks for this kind of thing
feature "..." do
before :each do
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
end
scenario "valid input saving"
#unique code for this scenario
end
scenario "invalid input leads to correct input page"
# unique code for this scenario
end
end
The outer feature block could be a describe or a scenario block if wanted/needed since you can nest multiple levels. If you need to use the code across multiple feature files then it makes sense to move it to a method in one of your spec helper files.
You can place it in a method in your spec file like this and reuse it.
scenario "valid input saving" do
your_named_method
...
end
scenario "invalid input leads to correct input page" do
your_named_method
...
end
def your_named_method
visit program_stream_path(#program, #stream)
click_link "#link"
fill_in "#fill_in", :with=>"1"
click_button "Next"
expect(page).to have_current_path new_students_list_stream_path(#stream)
end
A great way to avoid duplication when testing with Capybara is to use Capybara Test Helpers.
RSpec.feature 'Program Stream', test_helpers: [:programs] do
before { visit program_stream_path(#program, #stream) }
scenario 'valid input saving' do
programs.click_to_add_student
programs.should.be_adding_a_student(#stream)
programs.add_student(name: 'Имя', surname: 'Фамилия', email: 'randommail#mail.com')
programs.should.have_new_student('Имя')
end
scenario 'invalid input leads to correct input page' do
programs.click_to_add_student
programs.should.be_adding_a_student(#stream)
programs.add_student(name: nil, surname: nil, email: nil)
programs.should.have_invalid_form("Name can't be blank")
end
end
Besides reducing code duplication, it has the benefit of being a lot more descriptive, which can help to make tests a lot easier to maintain in the long term.
class ProgramsTestHelper < Capybara::TestHelper
# Actions: Encapsulate complex actions to provide a cleaner interface.
def click_to_add_student
click_link '#link'
fill_in '#fill_in', with: '1')
click_button "Next"
end
def add_student(name:, surname:, email:)
fill_in 'Имя', with: name
fill_in 'Фамилия', with: surname
fill_in 'Электронная почта', with: email
click_button 'Save'
end
# Assertions: Allow to check on element properties while keeping it DRY.
def be_adding_a_student(stream)
have_current_path urls.new_students_list_stream_path(stream)
end
def have_new_student(name)
have_content(name)
end
def have_invalid_form(message)
have('form', text: message)
end
end
Have in mind that you could choose to combine click_to_add_student with add_student, running assertions inside the helper methods. It all boils down to how much granularity you need in tests.
Passing blocks to methods is also a nice way to customize interactions or outcomes.
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 developing a sign in/out function on a web app that i'm building for learning.
During the week, i wasn't able to pass the following rspec test:
describe "sucessfull log in" do
let(:user) {FactoryGirl.create(:user)}
before do
visit signin_path
fill_in "Email", with: user.email.upcase
fill_in "Password", with: user.password
click_button 'Sign in'
end
it {should have_title(user.name)}
it {should have_link('Account')}
it {should have_link('Profile', href: user_path(user))}
it {should have_link('Sign out', href: signout_path)}
end
Then yesterday and end up re writing this code again, but forgot to add that .upcase function. I Left just .upcase.email. Suddenly, the test passed.
Ok, i'm a newbie developer... so what happaned? Why the upcase function did not let rspec log in successfully?
thanks in advance!
Why are you upcasing the email? Would you be equally surprised if .reverse made it fail? You're changing the email address.
EXAMPLE#EMAIL.COM is not the same as example#email.com, strictly speaking. It might be equivalent for your application, but you need to make the application know that.
Are you using devise? If so, the config/devise.rb file will have:
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
config.case_insensitive_keys = [ :email ]
Which should make what you're doing work. If you're not using it, whatever framework you're using will probably have a similar option, and if you're doing it yourself, you'll need to handle it yourself.
I testing with rspec, Im still learning, I guess I'm on the right way... but when I test my rspec file I got this error:
Failures:
1) UsersController signup with valid information should create a user
Failure/Error: expect { click_button submit }.to change(User, :count).by(1)
count should have been changed by 1, but was changed by 0
# ./spec/controllers/user_controller_spec.rb:31
Finished in 1.16 seconds
2 examples, 1 failures
I know what this mean, but I don't know how to fix it, can anyone help me with this trouble please...Also I put my rspec file
require 'spec_helper'
describe UsersController do
describe "signup" do
before { visit new_user_registration_path }
let(:submit) { "Sign up" }
describe "with invalid information" do
it "should not create a user" do
expect { click_button submit }.not_to change(User, :count)
end
end
describe "with valid information" do
before do
fill_in "Email", :with=> "user#example.com"
fill_in "Password", :with=> "foobar"
#fill_in "password_confirmation", :with=> "foobar"
end
(here is that the error appears...below line)
it "should create a user" do
expect { click_button submit }.to change(User, :count).by(1)
end
end
end
end
thanks for your attention
So, you have a failing spec. The spec itself looks ok. So I would check on
is the implementation in place? You can try out the scenario in your browser. Does it work there? If not, you have a failing test (which is good) and need to add the implementation.
if the implementation works, check the spec again carefully. Is the provided information
fill_in "Email", :with=> "user#example.com"
fill_in "Password", :with=> "foobar"
sufficient to create a user?
if you still haven't found the cause, you could use capybaras save_and_open_page (install launchy gem for that) and peek at the page during your test.
ADDITION:
ok, as I said, this should be an integration test - move it from the spec/controllers to the spec/requests folder (and do change the first line, as you're not describing the UsersController! but that should not lead to the problem).
In the Rails on Rails Tutorial by Michael Hartl, the Request Examples make assertions on the response. I installed the cabybara and steak gem to create acceptance tests. After installing capybara, the requests examples are configured to use capybara. capybara examples have a different syntax and don't recognize the response.
How do I reset the Request Examples to run as RSpec example?
Test Error:
4) Users signup failure should not make a new user
Failure/Error: click_button
wrong number of arguments (0 for 1)
# ./spec/requests/users_spec.rb:13
# ./spec/requests/users_spec.rb:7
Request Example
describe "failure" do
it "should not make a new user" do
lambda do
visit signup_path
fill_in "Name", :with => ""
fill_in "Email", :with => ""
fill_in "Password", :with => ""
fill_in "Confirmation", :with => ""
click_button
response.should render_template('users/new')
end.should_not change(User, :count)
end
end
This might have something to do with rspec-rails request_example_group.rb.
request_example_group.rb
If the capybara lines are commented out, then the Request Examples are not defaulted to capybara.
capybara do
include Capybara
end
In the Acceptance examples, include capybara.
require File.expand_path(File.dirname(__FILE__) + '/acceptance_helper')
feature "This Is My First Feature", %q{
In order to ...
As a ...
I want to ...
} do
include Capybara
scenario "Scenario name" do
visit signup_path
fill_in "Name", :with => ""
fill_in "Email", :with => ""
fill_in "Password", :with => ""
fill_in "Confirmation", :with => ""
click_button "Sign up"
page.has_content?('welcome to the sample app')
end
end
I ended up forking rspec-rails and commenting out the capybara lines.
In the Gemfile, the gem installed from the fork.
Why bother? We want both white box and black box testing.
As you correctly point out in your answer, rspec-rails will include Capybara if it is installed. I think they're assuming that it never hurts to include Capybara, just in case you need it. Is that a problem for you? In other words, what specifically is the Capybara DSL conflicting with?
(This might be also be worth bringing up on the RSpec mailing list.)
As for the error you posted, it seems that you are simply missing an argument to the click_button method.