Even though this post is slightly geared towards StimulusReflex it should help anyone who is looking to implement complete front-to-back testing of their app.
How to Start
The basic shape of a feature spec should look something like this:
require 'rails_helper' RSpec.feature "Creation management", :type => :feature do scenario "User clicks to create something" do visit "/" # go to a page click_link "Create" # do something expect(page).to have_text("Something was Created") end end
It'll vary depending on how RSpec is configured, but the question is how do we turn this into something useful? Where does someone start to create feature specs that allows them to test their application?
The first step is confirm that the page you're visiting is your intended target. For example, it's easy to think that a page is being queried when it's really not and the login page is being served up. To figure this out
binding.pry is going to be helpful. To start it should be added right after the line that visits the endpoint like so
visit "/" # go to a page binding.pry click_link "Create" # do something
Once the test is run again the response can be inspected to verify it is what we think it is.
page.html will spit out what we're visiting. If you want a better display use pretty print,
One possibility is that the application is responding as if there isn't an authenticated user. To get around this we can log in. Depending on the tool used this could take different forms. For devise it can look something like this
scenario "User clicks to create something" do user = create(:user) sign_in user visit "/"
sign_in can be used the appropriate linkages must be made within the rails_helper.rb file. For features the following line under the
Rspec.configure block should work.
config.include Devise::Test::IntegrationHelpers, type: :feature
Once the spec can log in and get a proper response it can then start manipulating the page.
Finding and Clicking a Link
It's most likely that the first error will be something along the lines of
Capybara::ElementNotFound: Unable to find link "#something" Note: It appears you may be passing a CSS selector or XPath expression rather than a locator. Please see the documentation for acceptable locator values.
The trick with this is that the locator can be an id or text of the link. Prepending the
# for ids fouls this up. Remove it to get it to work.
The next problem that might happen is that Capybara will discover that there is more than one relevant element. In the case of ids there should be only one of each id on a page. If that's not the case that should be resolved before continuing.
RSpec.feature "Creation management", :type => :feature, js: true do
- It looks like webpack 4 has dependencies that have vulnerabilities (e.g. glob-parent)
- webpacker 5.4 doesn't support webpack 5
- webpacker 6 does, but at the time of this post it was still in the release candidate phase
- It looks like the Rails community might be moving away from webpack to jsbundling anyway
In the end I think this was the solution that worked for me. I had to drop my
public/packs*/ directories and rebuild them with
RAILS_ENV=test bundle exec rails webpacker:compile.
What to Expect
Now that Capybara is recognizing the link and is able to click it the next step is to set up the checks for the tests. Since I want to make sure StimulusReflex is updating something correctly the test will record the original value of a field, click the button that invokes the reflex, and then check the new value against what it was. This looks something like this.
visit "/" sleep 0.01 original_thing = find("#the_thing").find('a').text click_link "something" new_thing = find("#the_thing").find('a').text expect(new_thing).not_to eq original_thing
That's it. From here more specs can be created, but they should be limited to general operations that need to be functional. Testing specific edge cases should be left to unit tests that will run much faster.
It doesn't take much to create a feature test, and they are a great way to test functionality of an app at a system level. In the end the code looks something like this.
require 'rails_helper' RSpec.feature "Creation management", :type => :feature, js: true do scenario "User clicks to create something" do user = create(:user) sign_in user create(:something, name: 'Widget') # populating the database with something visit "/" # go to a page sleep 0.01 # needed a small delay for the ActionCable channel (see below) original_thing = find("#the_thing").find('a').text click_link "Create" # do something new_thing = find("#the_thing").find('a').text expect(new_thing).not_to eq original_thing end end
Unanswered Questions and Other Thoughts.
With ActionCable I needed to add a small delay so that the channel was ready for the click. if this didn't happen the front end would respond with "The ActionCable connection is not open!
this.isActionCableConnectionOpen() must return true before calling
this.stimulate()". To workaround this I added a
sleep for 1/100th of a second. Ideally I'd like for the spec to wait for the connection to open before it attempts to test the subject of the test.
Another neat functionality would be to have the expect detect if a element changed so that the spec doesn't have to record the original value to compare against the post-action value.