I am trying to build an app with Cucumber for the first time, using Rails 5.2.6, Rspec, Capybara and Factory bot.
I successfully got through my first feature with scenarios for authentication with devise.
UPDATE
Through a series of troubleshooting steps, detailed below, the problem is that the controller collection @cocktails somehow isn't passing to the view ONLY in the cucumber test.
With the rails server, it passes with no problem.
I checked and @cocktails only appears on the controller and the view. So its not getting overwritten or erased, at least directly.
Its working in the unit RSpec test and also rails server.
How it is not getting passed in the cucumber test? Can anyone see why it wouldn't pass from the controller to the test?
But I hit a snag in my second feature file for CRUD functionality. The very first given step uses FactoryBot to create 2 items, log in the user and go straight to the index for just those two items. But the page shows the view but not the 2 created items as confirmed by using:
puts page.body
in the cucumber file
When I create them on the actual application, it functions correctly, goes straight to the index and displays them.
So I'm trying to figure out how to troubleshoot this. My first thought is to find a way to confirm that FactoryBot created the 2 items. My second thought is to confirm they are actually set to the user. I have tried to use puts to display the two created objects or the user, but I haven't figured out how to call upon them within cucumber.
This is the step file:
Given('I have populated a cocktail list for this User') do
FactoryBot.create(:cocktail,
:user => @registered_user,
:name => "Frank Wallbanger",
:ingredients => "Lots of Booze, a pinch of lime")
FactoryBot.create(:cocktail,
:user => @registered_user,
:name => "Fuzzy Naval Orange",
:ingredients => "Lots of Tequila, a pinch of orange")
end
When('I visit the website and log in') do
expect(page).to have_content("Signed in successfully")
end
Then('I will see the cocktail list') do
puts page.body
expect(page).to have_content("Frank Wallbanger")
expect(page).to have_content("Fuzzy Naval Orange")
end
This is my RSpec unit test file and its green
require "rails_helper"
RSpec.describe CocktailsController do
let(:user) { instance_double(User) }
before { log_in(user) }
describe "GET #index" do
let(:cocktails) { [
instance_double(Cocktail),
instance_double(Cocktail)
] }
before do
allow(user).to receive(:cocktails).and_return(cocktails)
get :index
end
it "looks up all cocktails that belong to the current user" do
expect(assigns(:cocktails)).to eq(cocktails)
end
end
end
This is rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
puts e.to_s.strip
exit 1
end
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# You can uncomment this line to turn off ActiveRecord support entirely.
# config.use_active_record = false
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, type: :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
# Filter lines from Rails gems in backtraces.
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
end
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :active_record
with.library :active_model
with.library :action_controller
with.library :rails
end
end
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
end
require "support/controller_helpers"
RSpec.configure do |config|
config.include Warden::Test::Helpers
config.include Devise::Test::ControllerHelpers, :type => :controller
config.include ControllerHelpers, :type => :controller
end
This is the controller I'm testing:
class CocktailsController < ApplicationController
before_action :set_cocktail, only: %i[ show edit update destroy ]
# GET /cocktails or /cocktails.json
def index
@cocktails = current_user.cocktails
end
# GET /cocktails/1 or /cocktails/1.json
def show
end
# GET /cocktails/new
def new
@cocktail = Cocktail.new
render :new
end
# GET /cocktails/1/edit
def edit
end
# POST /cocktails or /cocktails.json
def create
@cocktail = Cocktail.new cocktail_params.merge(user: current_user)
if @cocktail.save
redirect_to cocktails_path
else
render :new
end
end
# PATCH/PUT /cocktails/1 or /cocktails/1.json
def update
respond_to do |format|
if @cocktail.update(cocktail_params)
format.html { redirect_to @cocktail, notice: "Cocktail was successfully updated." }
format.json { render :show, status: :ok, location: @cocktail }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @cocktail.errors, status: :unprocessable_entity }
end
end
end
# DELETE /cocktails/1 or /cocktails/1.json
def destroy
@cocktail.destroy
respond_to do |format|
format.html { redirect_to cocktails_url, notice: "Cocktail was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_cocktail
@cocktail = Cocktail.find(params[:id])
end
# Only allow a list of trusted parameters through.
def cocktail_params
params.require(:cocktail).permit(:name, :ingredients, :user)
end
end
I'm totally new to this so if there are other files you need to see let me know or if I put a block in a bad area.
At the minimum, I'm just looking for how to display the values. The user login runs with the previous steps file where @registered_user was created and it seems to persist with the second step that confirms "Signed in successfully". So I'm down to the items not created or not assigned to the user as my most likely suspects.
Thanks
Here is the index.html.erb view
<h1>Cocktails</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Ingredients</th>
<th>User</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @cocktails.each do |cocktail| %>
<tr>
<td><%= cocktail.name %></td>
<td><%= cocktail.ingredients %></td>
<td><%= cocktail.user_id %></td>
<td><%= link_to 'Show', cocktail %></td>
<td><%= link_to 'Edit', edit_cocktail_path(cocktail) %></td>
<td><%= link_to 'Destroy', cocktail, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Cocktail', new_cocktail_path %>
<%= button_to 'Log out', destroy_user_session_path, :method => :delete %>
So, after Lam's suggestion below, I tried this attempt to verify.
I went rails console, and tried to run that first FactoryBot.create from the step file. That produced an error that the user @registered_user doesn't exist. Which makes sense in the console. So I can't verify that. BUT, if I created @registered_user from an existing user, that snippet successfully reacted the Frank Wallbanger cocktail. So it seems the FactoryBot is functional. So perhaps is the @registered_user that is the problem?
So I added "puts @registered_user" to the steps file and it did print an active record to the cucumber output.
When I run the rails server and display that view, I see the cocktails listed out. But its still blank in that area of the cucumber output from the "puts page.body" line in the steps file.
UPDATE
Ok, I think I zeroed down to the cause. But not the fix. In the controller index
@cocktails = current_user.cocktails
Is working on the rails server, but it is NOT populated in the cucumber test. I added this code to the view
<% if @cocktails.any? %>
<%= @cocktails.first.name %>
<% else %>
<p> none </p>
<% end %>
So why would it not be passing? I tried changing the controller to:
@cocktails = Cocktail.all
And that also doesn't work.
Ok, I finally got it.
I needed to put add visit root_path on the Then step of the step file. And it finally all cleared up.
Then('I will see the cocktail list') do
visit root_path
expect(page).to have_content("Frank Wallbanger")
expect(page).to have_content("Fuzzy Naval Orange")
end
I need a beer.....