Testing multistep form with rspec - ruby-on-rails

I'm not very proficient with Capybara and testing tools alike. I'm building a multistep form or a form wizard.
This is how I had a scenario in my head :
User visit signup path
fills in bunch of invalid data and some validation errors should be displayed
fills in valid data, user should be taken to the next step
repeat this step above for n steps with valid data
check if number of users has increased by 1
This is what I've got so far (I managed to get something working) :
describe "signup" do
before { visit signup_path }
let(:submit) { 'Start creating my account' }
let(:next_btn) { 'Next' }
describe "with invalid information" do
it "should not create a user" do
expect { click_button submit }.not_to change(User, :count)
end
describe "should not move to the next step" do
before { click_button submit }
it { should have_content('error') }
it { should have_title('Start here') }
end
end
describe "with valid information wizard step 1" do
before do
fill_in 'First name', with: "Example"
fill_in 'Last name', with: "User"
fill_in 'email', with: "user#example.com"
find("#user_password").set "foobar"
end
it "should move to the next wizard step 2" do
click_button submit
should have_content('We need some more info')
end
it "should have error on the wizard step 2" do
fill_in 'Date of birth', with: "Example"
click_button next_btn
should have_content('error')
end
end
end
This assertion fails should have error on the wizard step 2, it seems to be stuck at step 1 still, I know this from looking at page content and by errors where Dob element can't be found and next button cant be found as well.
Is this even possible with capybara to test sequentially, keeping information step by step?

You should be able to make this work by nesting the describe blocks when moving on to a subsequent step, eg.
describe "signup" do
...
describe "with valid information wizard step 1" do
before do
fill_in 'First name', with: "Example"
fill_in 'Last name', with: "User"
fill_in 'email', with: "user#example.com"
find("#user_password").set "foobar"
click_button submit
end
it "should move to the next wizard step 2" do
should have_content('We need some more info')
end
describe "and filling out the date of birth incorrectly" do
before do
fill_in 'Date of birth', with: "Example"
click_button next_btn
end
it "should have error on the wizard step 2" do
should have_content('error')
end
end
end
end
Now the button clicks are repeated in the nested blocks, so the first example clicks 'submit', and the second example clicks 'submit' and then clicks 'next_btn', etc. Keep in mind that the sequence is repeated for each example so this may slow down your tests. OTOH it more accurately reflects the user interaction, so the tradeoff is probably worth it.

I think this is not a capybara question so much as it is an rspec question. I've used cucumber more than rspec, but so far as I know in my limited experience, each "describe" is an independent test case. I don't think that you can either a. expect these to execute in any particular order or b. share context / state between tests.
Again, my experience has mainly been with cucumber, but I have written tests for this EXACT scenario before- a two page user registration wizard. In cucumber, one writes "scenarios," which are basically short stories that describe a user's interaction with the the system in terms of "given," "when," and "then" (or in other worse, assumptions, actions, and assertions) and these are written in plain English. One then writes Ruby code that maps these English sentences to Ruby code (which might use capybara) to do / test what each scenario describes... What I did was constructed several distinct scenarios.
User visits registration page; fills it out with a whole bunch of errors; check that the system does display the errors
User visits registration page; fills it out correctly; check that the user makes it to the second page
User has already completed the first page; fills out the second page with a whole bunch of errors; check that the system does display the errors
User has already completed the first page; fills out the second page correctly; check that the user makes it to the confirmation page
You can obviously translate those stories into rspec.
The thing that recurs in those scenarios is the filling out (whether with good or bad data) of the two forms. To keep the code DRY, create some helper methods that let you easily populate the form with values that will help you satisfy each of those scenarios.
Does that help any?

Related

RSpec test queries not working as anticipated but code/console work fine

Here's my scenario. I have a ticketing app that tests adding comments. I have a creating_comments_spec set up that creates a user, creates a project and creates a ticket, has Pundit assign rights to it, then goes and visits. Standard stuff.
Here's where I'm confused.
On my "show" page, I have the following controller actions for this newly created ticket:
class StaticPagesController < ApplicationController
skip_after_action :verify_authorized, only: [:home]
def home
#inactive_projects = Project.includes(:tickets).where(tickets: { project_id: nil} )
#open_projects = Project.includes(tickets: :state).where.not({ states: { name: "Closed" } })
#projects = Project.all
end
end
On my show page I loop through #inactive_projects and display a link to the project.name. I do the same for #open_projects. And for testing, I do it for #projects.
In RSPEC the only thing Capybara shows is the #projects = Project.all collection. The other two come back empty. If I create these in a let! block before everything, or using Project.create! and Ticket.create! before, during whatever, the ONLY thing that shows up is #projects = Projects.all.
This tells me that in some way my queries don't discover a Project that is there, though it should as the first finds all projects that have no tickets and the second finds all that have tickets that aren't closed.
And in my browser it all works.
And in the rails console, I can put all this in and create it JUST like the test and it all works fine. It's only in RSpec that Capybara shows it's not finding anything by either collection I want.
I can put more, but as I said, I've adapted my test to do a million different things. The ONLY thing that shows up in ALL cases is #projects = Project.all and NOTHING else EVER. But in the browswer and console it all works.
Per the comment below, here's my spec:
require "rails_helper"
RSpec.feature "Users can comment on tickets" do
let!(:user) { FactoryGirl.create(:user) }
let!(:project) { FactoryGirl.create(:project) }
let!(:ticket) { FactoryGirl.create(:ticket, project: project, author: user) }
before do
login_as(user)
assign_role!(user, :manager, project)
FactoryGirl.create(:state, name: "Open")
visit "/"
click_link project.name
end
scenario "with valid attributes" do
click_link ticket.title
fill_in "Text", with: "Added a comment!"
click_button "Create Comment"
expect(page).to have_content("Comment has been created.")
within("#comments") do
expect(page).to have_content("Added a comment!")
end
end
scenario "with invalid attributes" do
click_link ticket.title
click_button "Create Comment"
expect(page).to have_content("Comment has not been created.")
end
scenario "when changing a ticket's state" do
click_link ticket.title
fill_in "Text", with: "This is a real issue"
select "Open", from: "State"
click_button "Create Comment"
expect(page).to have_content("Comment has been created.")
within("#ticket .state") do
expect(page).to have_content("Open")
end
expect(page).to have_content("state changed to Open")
end
scenario "but cannot change the state without permission" do
assign_role!(user, :editor, project)
visit project_ticket_path(project, ticket)
expect(page).not_to have_select("State")
end
scenario "when adding a new tag to a ticket" do
visit project_ticket_path(project, ticket)
expect(page).not_to have_content("bug")
fill_in "Text", with: "Adding the bug tag"
fill_in "Tags", with: "bug"
click_button "Create Comment"
expect(page).to have_content("Comment has been created.")
within("#ticket #tags") do
expect(page).to have_content("bug")
end
end
end
The reason you don't see any open projects is probably because you don't have any in your test setup.
Your controller includes the following code:
#open_projects = Project.includes(tickets: :state).where.not({ states: { name: "Closed" } })
So, you should have a project, which is linked to at least one ticket. That ticket should be linked to at least one state, which name is not Closed. I don't see that anywhere in your spec.
You've created a user and project which you've associated with the ticket you've created on line 6. Where's the state though?
You do create a state in your before block:
FactoryGirl.create(:state, name: "Open")
But as far as I can see it's not associated to the ticket you've created previously.
The reason you don't see any inactive projects is (as far as I can see) because you don't have a ticket without a project. So, to fix that you could try this:
let!(:ticket_without_project) { FactoryGirl.create(:ticket, author: user) }
I would recommend using the pry gem and dumping a call to "binding.pry" into your spec, so you can run your spec line by line within the environment that's being set up. I don't see anything wrong from what you've posted.
This was my exact problem, and I assume you've already figured it out, but for me, the issue was that I had just changed the migration files for some of my models. Rails never complained about it, but my problem was solved after I rollback and migrate the test database, which you can do like so:
rake db:rollback test and rake db:migrate test
You may need to rollback x number of steps like so rake db:rollback STEP=15 test

Rspec Capybara test with ajax call not updating record

I have a form where when a user submits an ajax form with a single name field, a record is created that belongs to a Template object. This all works fine manually, but for some reason when I test this via rspec its telling me that the association was not created. Why isn't the template object updated here?
describe "edit page" do
before(:each) do
visit edit_admin_template_path(template)
end
context "form sections", js: true do
it "allows admin to create one" do
find("#btn-add-section").click
within('#new_form_section') do
fill_in 'Name', with: "Personal Info"
click_button "Create Section"
end
expect(template.form_sections.length).to eq(1)
end
end
end
This is the failure that I am getting
Failure/Error: expect(template.form_sections.length).to eq(1)
expected: 1
got: 0
(compared using ==)
UPDATE: just noticed this in the rspec output
An error occurred in an after hook
ActionView::Template::Error: undefined method `form_sections' for nil:NilClass
so its telling me that the template object does not exist, then its able to compare it afterwards?
UPDATE 2: So it appears that the issue is waiting for the ajax call to complete in capybara, before expecting. I added this to the spec and it now works, obviously I need to refacotr this to something better like a helper
it "allows admin to create one" do
find("#btn-add-section").click
within('#new_form_section') do
fill_in 'Name', with: "Personal Info"
click_button "Create Section"
end
sleep(10) #this waits for the request to complete
expect(template.form_sections.length).to eq(1)
end
The key is telling RSpec to wait for the ajax call to complete before doing any expectations. This is a great post on the solution that I used:
Thoughtbot: Automatically Wait for AJAX with Capybara
Just in case others stumble upon this, rather than using sleep you could test based on the UI itself. Capybara will wait for that element to reload, if you use have_css or find matchers, like this:
expect(page).to have_css("#form-page-two", visible: :visible) #this will force test to wait for ajax call
Capybara.automatic_reload must not be set to false. (defaults to true)
This strategy will reduce the run time of your test suite; if the test only took 2 or 3 seconds to load it will only wait that long (not 10).
However, it can be frustrating to write because of intermittent failures. To avoid this you may need to bump up the wait time setting.
Capybara.default_max_wait_time = 10
https://github.com/teamcapybara/capybara#asynchronous-javascript-ajax-and-friends

How can I build on the result of old expectations in a feature spec?

Let's say I'm testing how to create a Widget:
feature "widget management" do
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
end
Okay, great, but let's say I want to create another widget, and test how those two interact with each other. In a unit test I would have no trouble stubbing or using factory girl's create method to set up the hashes I need, but with a feature integration test, I want to test the whole application realistically, just to really, really make absolutely sure there are no bugs. I don't want to stub or use a create method, I want to literally create two different widgets using the form found at the root_url!
But if I do this:
feature "widget management" do
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget"
expect(page).to have_text("Widget_2 was successfully created.")
end
end
Awesome Widget_2 is created in the database, but Awesome_Widget, from the last scenario, is no longer in the database. This is because my config.use_transactional_fixtures is set to true. I want the test database to be cleaned between expectations, though, at least in all of my unit specs, as they use contexts to set up the database in a certain way before each expectation.
But I don't want the database to be cleaned between each scenario in my extensive integration spec! I want to build on what's happened before. Is this the correct way to do it? Or should I keep transitional_fixtures and be stubbing/creating in a before block for all of my feature specs?
Maybe then create one long expectation that creates multiple widgets and makes them interact with each other in one huge it block?
I just want to get to emulate true behaviour! I want to go through the forms, make thousands of widgets (using a loop and a factory girl sequence) and watch it all work for peace of mind, (maybe using a headed server such as Selenium for extra certainty) before it goes live! Surely that's a sensible thing to want to do? It's been surprisingly tricky to do this!
I can understand stubbing in a request spec, because though you're testing a faculty of your app that uses controllers, models, views and active record, you're testing that feature of your app in isolation.
With a feature spec though, you're meant to be telling a story. A user (or whatever) does this, and then he does that, meanwhile, another user is created, he "friends" the first user, the first user "accepts" and so on. Don't really know how I can do this if the database is wiping itself between each expectation!
Basically, how can I turn transactional_fixtures off for certain specs, but have them on for other specs, and is this advisable?
Up for using database cleaner instead of transactional_fixtures!
Update
Okay, this seems to be a good setup for telling a 'story' with a feature spec:
(Note, I've only included the code relevant to setting this up, your spec_helper needs a few more things in it to get rspec, factory girl, guard, whatever working)
Gemfile
(adding the database_cleaner gem for better control when dropping tables)
gem 'database_cleaner'
spec/spec_helper.rb
(configuring the database_cleaner to drop all tables in the test database, also setting transitional fixtures to true so that all tables are dropped between expectations(this is overwritten in the feature spec itself using an instance method, which you'll see in a bit))
RSpec.configure do |config|
config.after(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.use_transactional_fixtures = true
I18n.enforce_available_locales = true
config.order = "random"
end
spec/features/integration.rb
And finally, the feature spec that builds on old expectations to 'tell a story'. Note the instance method that overrides the spec_helper's configuration regarding transactional fixtures:
feature "widget management" do
self.use_transactional_fixtures = false
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget" # Both exist in the database and so they can take part in the story!
expect(page).to have_text("Widget_2 was successfully created.")
end
end
I suppose the question has changed a little!
What are your thoughts on the above as a way to write feature specs?
By the way, if you're attempting this, or anything that uses database cleaner to manually remove information from your database, be aware you can get in a little mess if you're using active record (which you should be in an integration spec) and model validations! Basically, if data from the last spec is lingering in your database for some reason (if you've just turned transactional_fixtures off, for example), your specs could fail any uniqueness validations you have set up, because identical data is already exists in the database.
If you have database cleaner setup like above, it's set to clean the database when the suite finishes. Because your spec is hitting a validation error, it's never finishing, so database cleaner never cleans the database. And because the database hasn't been cleaned, when you run the spec again, your spec hits the validation error again, it still doesn't finish and data base cleaner still doesn't clean the database and so on into infinitum.
To cut a long story short, if you're getting validation errors, manually clean your tables.
You can do this with the sqlite3 shell (A little easier than rails console I think, but you can use that as well if you want. It's similar commands with any shell, postgres, mysql, whatever):
In the command line:
$ cd db
$ sqlite3 test.sqlite3
sqlite > DELETE FROM widgets;
You may need to run that command a few times to empty different tables depending on your spec. The syntax of the command: DELETE FROM [table name you wish to delete from];
sqlite > .exit
What are your thoughts on the above as a way to write feature specs?
I don't think it's a good idea to share data between scenarios. A scenario shouldn't rely on data from another scenario. This will cause problems if you randomize the execution order in your RSpec configuration.
Another problem that I see is that you are not really testing different scenarios.
feature "widget management" do
self.use_transactional_fixtures = false
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget" # Both exist in the database and so they can take part in the story!
expect(page).to have_text("Widget_2 was successfully created.")
end
end
The only difference between the two scenarios is that you are varying the name but not in a relevant way.
You could improve this suite by varying the type of input (e.g. valid then invalid) in order to test more paths in your code. I would refactor that suite to look more like this:
describe "widget management" do
before do
visit root_url
click_link "New Widget"
fill_in "Name", with: name
click_button "Create Widget"
end
context "when creating a widget with a valid name" do
let(:name) { "Awesome Widget" }
it "returns a success message" do
expect(page).to have_text("Widget was successfully created.")
end
end
context "when trying to create a widget with an invalid name" do
let(:name) { "" }
it "returns an error message" do
expect(page).to have_text("Widget_2 must have a valid name.")
end
end
end

How to test Rails belongs_to relationship using rspec integration test and factory girl

Still trying to get my head around relationships and testing in rails/rspec.
I have an app that has a model Quizzes which belongs_to an Icon model, and that has_many quizzes.
I'm trying to test that a new quiz is created on the quiz/new page using rspec/capybara and factory girl to create my data in the tests.
Here is my test as it is..
describe "new quiz page" do
let(:user) { FactoryGirl.create(:user) }
let(:icon) { FactoryGirl.create(:icon) }
before do
sign_in user
visit new_quiz_path
end
....
describe "with valid info" do
before do
fill_in "Title", with: "Example title"
fill_in "Description", with: "Example quiz description"
select icon.title, from: "quiz_icon_id"
end
it "should create new content" do
expect { click_button submit }.to change(Quiz, :count).by(1)
end
end
end
The rspec error I'm getting is...
Failure/Error: select icon.title, from: "quiz_icon_id"
Capybara::ElementNotFound:
cannot select option, no option with text 'Icon 1' in select box 'quiz_icon_id'
So the problem obviously is that there are no icons to select from. One is definitely being created with the factory girl method as it can find the title but is not replicated in the test form. I can't get my head around how to get this to work.
Can anyone offer some pointers here?
Thanks, Mark.
Thanks #Dty for save_and_open_page tip. That helped me debug the problem.
The icon wasn't being created, or at least wasn't there when the test was being run on the page.
To solve I did this instead of the let(:icon) statement.
before(:all) { 1.times { FactoryGirl.create(:icon) } }
after(:all) { Icon.delete_all }
Then when the tests filled in the form I used Icon.first.title.
before do
fill_in "Title", with: "Example title"
fill_in "Description", with: "Example quiz description"
select Icon.first.title, from: "quiz_icon_id"
end
I don't really understand why I had to use a before(:all) statement for the Icons to be created rather than the 'let' statement I had previously though. If anyone can suggest why this is the case that would be appreciated.

How do you find a specific tag based on parents in capybara and rspec?

Currently I have two tests that each individually test for a button_click within a form on the same page with no ids but both have the same text Go. So what is happens is capybara/rspec only tests the first instance of the Go button for both tests. Is there a way to find something based on the parents? I don't want to have to modify my functional code by adding an id just for the sole purpose of testing. I was thinking of something like test_form > input[type='submit'] but that doesn't work.
Current code:
let(:submit) {"Go"}
describe "with valid information" do
before do
fill_in "email", with: "stuff#example.com"
fill_in "password", with: "stuff"
end
it "should create a user" do
expect {click_button submit}.to change(User, :count).by(1)
end
end
Current Gems:
rails 3.2.6
rspec-rails 2.10.1
capybara 1.1.2
You can specify within which selector you want your button, see doc

Resources