Search code examples
rubycucumberruby-on-rails-5bddfactory-bot

Ruby Rails Cucumber Troubleshooting


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.


Solution

  • 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.....