Rails - Minitest + Capybara + Selenium - Test destroy action - ruby-on-rails

I am new to Minitest / Capybara / Selenium. But I want to test my destroy Controller action. I an trying the following and it is failing
test "destroy" do
companies_count = Company.count
visit company_path(#company)
click_on "Delete"
page.driver.browser.switch_to.alert.accept
assert_equal (companies_count - 1), Company.count
end
OUTPUT:
test_destroy FAIL (2.17s)
Expected: 6
Actual: 7
Tried this way also.
test "destroy" do
assert_difference('Company.count', -1) do
delete company_url(#company)
end
end
OUTPUT:
Minitest::UnexpectedError: NoMethodError: undefined method `delete' for #<CompaniesControllerTest:0x000056171e550038>
Can someone help me in testing my destroy action?

Assuming you're using a modern version of Rails (5.2/6) and a standard system test configuration (not running parallel tests in threads) then the concerns in the answer of Gregório Kusowski are irrelevant because the DB connection is shared between your tests and your application, preventing the issue of the tests not being able to see your apps changes.
Also assuming you're using Selenium in these system tests, the main problem you're dealing with is that actions in the browser occur asynchronously from your tests, so just because you've told your test to accept the dialog box doesn't mean the action to delete the company has completed when it returns. The way to verify that is to just sleep for a little bit before checking for the change in count. While that will work it's not a good final solution because it ends up wasting time. Instead, you should be checking for a visual change that indicates the action has completed before verifying the new count
test "destroy" do
companies_count = Company.count
visit company_path(#company)
accept_confirm do
click_on "Delete"
end
assert_text "Company Deleted!" # Check for whatever text is shown to indicate the action has successfully completed
assert_equal (companies_count - 1), Company.count
end
This works because Capybara provided assertions have a waiting/retrying behavior that allows the application up to a specific amount of time to catch up with what the test is expecting.
Note: I've replaced the page.driver... with the correct usage of Capybaras system modal API - If you're using page.driver... it generally indicates you're doing something wrong.

This is very likely to happen because what you execute directly in your test happens in a transaction, and your web-driver is triggering actions that happen on another one. You can read more about how it happens here: https://edgeguides.rubyonrails.org/testing.html#testing-parallel-transactions
Here is a similar issue: Rails integration test with selenium as webdriver - can't sign_in
And as it is stated in the Rails Guides and the similar question, you will probably have to use a solution like http://rubygems.org/gems/database_cleaner
If you don't want to do this, the other option you have is to validate that your action was successful via the web-driver, like for example asserting that there are 6 rows in the table you list all companies.

Related

Rails + RSpec + Set the Order of Test Cases Files while running RSpec Test Suite

For RSpec Capybara Test Case [ Selenium ], I have near about 7 to 8 spec files. Few of the test cases are dependent on each other. For example, before deleting an product, I have to create the product.
But when test cases excution starts, delete product based rspec runs before the create product rspec.
File Name:-
product_delete.rspec
product_listing.rspec
product_newly_added.rspec
Command : rspec
.rspec file in root folder
--require spec_helper
--format html
--out ./log/rspec_results.html
--color
Test case failed while execution for delete product.
Is there any way to define the sequence of file execution while running RSpec.
Test cases should be independent. For your delete test case you can use factory and create a record then delete it in a single test case as shown in example.
just define factory once and use it to create records, in this way DRY wont be violated.
describe 'POST destroy' do
before(:each) do
#obj = build(:factory_name)
#obj.save
end
it 'it has status 200' do
post :destroy, {"id" => #obj.id}
expect(ClassOfObj.count).to eq(0)
end
end
One possible approach is to not separate these actions into their own test cases. With feature specs you test whole features, not single buttons. So, your test might look like this:
Navigate to new item page. Make sure form is displayed
Fill out the form. Click submit. Verify that success message is displayed on screen.
Verify that you have been redirected to item index page. Verify that newly created item is indeed present on the page.
Click "delete" button.
Confirm that you're on index page and that item is no longer displayed.
As mentioned by most/all the other answers, your tests should be independent, and RSpec supports running tests in random order to guarantee that. One of the easiest ways to implement testing in these conditions is to use factories for the creation of your test data (FactorGirl, etc). In this case you would end up with a test along the lines of
feature "deleting of products" do
scenario "removes last product" do
create(:product) # Use factory to create one product
visit products_path
expect(page).to have_css('div.product', count: 1) # verify there is only one product shown on the page
click_link('delete') # click the delete button
expect(page).to have_text("Product deleted!") # check for a visible change that indicates deletion has completed
visit products_path
expect(page).not_to have_css('div.product') # No products shown any more - you may need to expect for something else first if the products are dynamically loaded to the page to ensure that has completed
end
end
You could check the DB contents rather than revisiting the products_path, but direct DB querying in feature tests is generally a bad smell since it's coupling user experience with implementation details.
If using this in Rails < 5.1 with a JS capable driver, you'll probably need to install database_cleaner and turn off transaction mode for JS tests - https://github.com/teamcapybara/capybara#transactions-and-database-setup and https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example. In Rails 5.1+ the DB connection is shared between the app and tests so you can generally leave transactional testing enabled and database_cleaner is unneeded.

Frontend testing with CircleCI and Minitest: Tests don't wait long enough for page to load

So my tests seem to pass about 75% of the time. The other 25% fail because the testing suite isn't waiting long enough for the page to completely load. On our local machines the test takes about 35s, but on CircleCI's logs it's only 5s. (On local I run the tests with BROWSER=chrome m path/to/test.file)
I'm new to this tech stack so any help is greatly appreciated, even if it's just appropriate reference docs.
it 'should use this form' do
assert page.has_content?('#target_form')
within '#target_form' do
# fill in the form and apply payment
end
# it will throw errors here "can't find css..."
# the text/element won't even have loaded yet
# by the time the test is run
assert_equal '24', find_qa('price').text
end
The way you're writing your assertions isn't utilizing Capybara's waiting/retrying behavior so running on slower hardware (CircleCI compared to your local box) can cause your tests to fail. assert_equal evaluates the two parameters compares them, and it's done. This isn't good because Capybara assumes every action could perform asynchronous actions so it doesn't necessarily wait for a button click to submit and load a new page (because it has no way of knowing what action the button click may produce). However if you use the Capybara provided assertions it will wait/retry the comparison up to Capybara.default_max_wait_time seconds for the comparison to be true. I'm not sure how your find_qa method is defined, but if you've declared a custom :qa selector you could do something like
assert_selector :qa, 'price', text: '24'
If find_qa is just doing a CSS selector then you could do
assert_selector :css, "whatever find_qa('price') produces as a css selector", text: '24'
or you could do
find_qa('price').assert_text('24')
Since you're using minitest you probably want to read - https://github.com/teamcapybara/capybara#using-capybara-with-minitest - and configure Capybara's minitest matchers so the counts of assertions run are correct, and to provide a bunch more specific assertions that will utilize Capybara's waiting/retrying behavior. See https://github.com/teamcapybara/capybara/blob/master/lib/capybara/minitest.rb for the matchers added, which will let you write things like
assert_text find_qa('price'), '24'
assert_xpath 'an XPath selector', ...
assert_title ...
assert_current_path ...
etc.

Capybara webkit doesn't pass params from angular

I am trying to port a selenium test suite to capybara-webkit. The Rails app has an angular app embedded in the rails views and is not behaving as expected with webkit.
A test like this:
require 'spec_helper'
feature 'Editing company profiles' do
before do
#user = create(:employee)
#company = Company.find(#user.employer.id)
sign_in_as! #user
end
scenario 'successfully', js: true do
click_link 'Dashboard'
click_link #company.name
click_button 'Edit'
fill_in 'company_name', with: 'new name'
click_button 'Save'
expect(page).to have_content "Your company profile has been updated!"
end
end
Will pass without issue in selenium, but with webkit I get the error
Failure/Error: Unable to find matching line from backtrace
ActionController::ParameterMissing:
param is missing or the value is empty: company
# ./app/controllers/api/v1/companies_controller.rb:23:in `company_params'
# ./app/controllers/api/v1/companies_controller.rb:10:in `update'
The trace is missing, maybe because it's from angular land, but the error is reporting that no params are coming from the client. I've tried the capybara-angular gem, but it has not helped. I've also tried saving the page with capybara and nothing looks out of place there, are there any ways to access the PATCH request inside of webkit that's being generated in this test? I've also gotten similar errors with poltergeist.
Has anyone setup headless rspec testing with angular + rails? Any tips on how to debug why data isn't being sent over from the client?
Without seeing all of your code, this feels like it could be a problem associated with a known issue in the capybara-webkit gem is unable to pass entity bodies to the server.
I suspect that the update request is being sent as a PATCH request (which is appropriate), but the issue with the gem results in failure for your tests.
A workaround to your problem is to change the method of the request to PUT or POST, the issue linked above shows some options. You will be able to get your test to pass, but it's up to you to decide if changing the request type is worth getting your test to pass.
Note: In practice it may not matter if you don't actually use PATCH, as you could technically use (some of) the other http methods interchangeably -- but use caution as there are reasons to use a specific http method for a given situation. See this rubyonrails.org post from a few years ago for some details.

Capybara Selenium webdriver Transactional Rollbacks work arounds

I'm writing a couple of Feature Specs for an app and using the default Selenium webdriver that comes with Capybara. This is the spec I have written.
DatabaseCleaner.cleaning do
find(:css,'.dropdown-toggle').click
click_on "Locations"
find(:css, "#location-8-upgradesub-60").click
value1 = find(:css, "#location-8-review-subscription").text
value1.should be == '(2) Reviews (Paid)'
end
I'm facing 2 issues with this snippet:
1) Capybara isn't waiting for the XHR to get over and is coming out of the test before that. It works if I give a sleep condition for about 10 sec.
UPDATE
Solved 1) by setting Capybara.default_wait_time = 15 and writing a helper to make sure jQuery isn't active. page.evaluate_script('jQuery.active').zero?
2) I'm not able to rollback the DB transaction that takes place when selenim simulates the test. I see an INSERT and COMMIT in the test.log but no ROLLBACK because of which I need to keep changing my specs every time I run the test. If I use,DatabaseCleaner.strategy = :truncation, my entire DB gets wiped out and that is not something I want.
I've done some extensive googling on this issue and haven't been able to find an efficient work around. I've tried using the same transactional thread too, for the test server. Haven't had fruitful results with too! Any heads up or help would be greatly appreciated. Thanks in advance!
UPDATE
I followed this link https://relishapp.com/rspec/rspec-rails/docs/transactions and put my spec inside a before(:each) block and stored the value in an #value1 instance variable to compare it with the desired value within the it block. I haven't had any luck with that too.
before(:each) do
find(:css,'.dropdown-toggle').click
click_on "Locations"
find(:css, "#location-8-upgradesub-60").click
wait_for_ajax #Helper method to wait for ajax call to get over
find(:css, "#location-8-review-subscription").should be_visible
#value1 = find(:css, "#location-8-review-subscription").text
end
it "should open the dropdown, find Location and open it's modal", js:true do
#value1.should be == '(2) Reviews (Paid)'
end
With 1), I think have_content or have_selector will work. These methods will wait for some seconds before checking the content/selector exist. You could config this time via spec_helper.rb. You could put have_content/have_selector BEFORE your find(..).click to make sure it is exist before next tests.
Finally found a work around. I added this code snippet in spec_helper.rb. Not using Database Cleaner anymore.
Reference: http://www.opinionatedprogrammer.com/2011/02/capybara-and-selenium-with-rspec-and-rails-3/#comment-441060846. The entire comment thread is pretty useful.
ActiveRecord::ConnectionAdapters::ConnectionPool.class_eval do
def current_connection_id
# Thread.current.object_id
Thread.main.object_id
end
end

Ruby on Rails - RSpec Javascript Test with Capybara (new to RoR)

New to Ruby, Rails and TDD. I'm using RSpec with Capybara and Capybara webkit.
Trying to test if a div element exists on a page.
Test Code:
require 'spec_helper'
describe "Login module" do
before do
visit root_path
end
it "should have a module container with id mLogin" do
page.should have_css('div#mLogin')
end
it "should have a module container with id mLogin", :js => true do
page.evaluate_script('$("div#mLogin").attr("id")').should eq "mLogin"
end
end
The first test passes but the second test fails with:
Login module should have a module container with id mLogin
Failure/Error: page.evaluate_script('$("div#mLogin").attr("id")').should eq "mLogin"
expected: "mLogin"
got: nil
Ran the JS in browser dev tools and get "mLogin" rather than nil.
Any ideas? Thanks.
find('div#mLogin')[:id].should eq 'mLogin'
See this from doc:
#evaluate_script
Evaluate the given JavaScript and return the result. Be careful when using this with scripts that return complex objects, such as jQuery statements. execute_script might be a better alternative.
evaluate_script always return nil, as far as I remember.
Anyway, your second test seems like is testing if capybara works, because your first test is enough.
One likely problem is that the have_css matcher supports Capybara's synchronization feature. If the selector isn't found right away, it will wait and retry until it is found or a timeout elapses.
There's more documentation about this at http://rubydoc.info/github/jnicklas/capybara#Asynchronous_JavaScript__Ajax_and_friends_
On the other hand, evaluate_script runs immediately. Since this is the first thing you do after visiting the page, there's a race condition: it's possible that it executes this script before the page has finished loading.
You can fix this by trying to find an element on the page that won't appear until the page is loaded before you call evaluate_script.
Alternately, you can wrap your call in a call to synchronize to explicitly retry, but this is not generally recommended. For situations like this, you're much better off using Capybara's built-in matchers. The evaluate_script method should only be used as a last resort when there is no built-in way to accomplish what you need to do, and you need to take a lot of care to avoid race conditions.

Resources