RSpec Test issues: Error prohibited this user from being saved: Username can’t be blank

  Kiến thức lập trình

I’m building a Ruby on Rails application from a Test Driven Development approach. I wrote a RSpec test which tests User authentication & Authorization based on their role. I used gems like Devise, Capybara, Launcy e.t.c In my case, the User when trying to Sign-up gets a default role set to “client”
Here is the RSpec test:

require 'rails_helper'
RSpec.configure do |config|
  config.include EmailHelper, type: :feature
end

RSpec.feature 'UserAuths', type: :feature do

  scenario 'user signs up as a client' do
    visit new_user_registration_path

    fill_in 'First name', with: 'John'
    fill_in 'Last name', with: 'Doe'
    fill_in 'Contact number', with: '1234567890'
    fill_in 'Address', with: '123 Main St'
    fill_in 'Username', with: 'johndoe'
    fill_in 'Email', with: '[email protected]'
    fill_in 'Password', with: 'password'
    fill_in 'Password confirmation', with: 'password'

    click_button 'Sign up'
    expect(page).to have_content 'Welcome! You have signed up successfully.'
    expect(User.count).to eq(1)
  end
end

and here is my User Model:

class User < ApplicationRecord
  ROLES = %w[admin therapist client].freeze
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable


  validates :username, presence: true, uniqueness: true
  validates :email, presence: true, uniqueness: true

  enum role: { admin: 'admin', therapist: 'therapist', client: 'client' }, _suffix: true
  attribute :role, :string, default: 'client'

  ROLES.each do |name|
    define_method "#{name}?" do
      role == name
    end
  end
end

attribute :role, :string, default: 'client' sets any user trying to Sign-up with role “client” and it is saved in the database. In my schema I have the following:

ActiveRecord::Schema[7.1].define(version: 2024_04_05_195810) do
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "role"
    t.string "first_name"
    t.string "last_name"
    t.string "contact_number"
    t.string "address"
    t.string "username"
    t.string "gender"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end
end

When I run the Rspec test I keep getting this error:

UserAuths user signs up as a client
     Failure/Error: expect(page).to have_content 'Welcome! You have signed up successfully.'
       expected to find text "Welcome! You have signed up successfully." in "Sign upn1 error prohibited this user from being saved:nUsername can't be blanknFirst namenLast namenContact numbernAddressnUsernamenEmailnPassword (6 characters minimum)nPassword confirmationnLog innLog in Other users Log in as Admin Log in as Therapist"
     # ./spec/features/user_auth_spec.rb:35:in `block (2 levels) in <top (required)>'

Here is the content of my new.html.erb:

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>

  <div class="field">
    <%= f.label :contact_number %><br />
    <%= f.text_field :contact_number %>
  </div>

  <div class="field">
    <%= f.label :address %><br />
    <%= f.text_field :address %>
  </div>

 <% if resource.client? %>
    <div class="field">
      <%= f.label :username %><br />
      <%= f.text_field :username %>
    </div>
 <% end %>


  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

<%= form_for @user do |f| %>
  <%= link_to 'Log in', new_user_session_path(role: 'client') %>
  <%= f.label :Other_Users %><br />
  <%= link_to 'Log in as Admin', new_user_session_path(role: 'admin') %>
  <%= link_to 'Log in as Therapist', new_user_session_path(role: 'therapist') %>
<% end %>

<%= debug(resource.client?) %>

My Factorybot is setup like this as well:

FactoryBot.define do
  factory :user do
    first_name { Faker::Name.first_name }
    last_name { Faker::Name.last_name }
    contact_number { Faker::PhoneNumber.phone_number }
    address { Faker::Address.full_address }
    sequence(:username) { |n| "#{Faker::Internet.username}#{n}" }
    email { Faker::Internet.email }
    password { 'password' }
    password_confirmation { 'password' }
    gender { Faker::Gender }

    trait :admin do
      role { :admin }
    end

    trait :therapist do
      role { :therapist }
    end

    trait :client do
      role { :client }
    end
  end
end

My User controller is this:

class UsersController < ApplicationController
  load_and_authorize_resource
  before_action :authorize_admin!, only: %i[edit update]

  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create

    @user = User.new(user_params)
    if @user.save
      redirect_to users_path, notice: 'User was successfully created.'
    else
      render :new
    end
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user != current_user && @user.update(user_params)
      flash[:notice] = 'User role updated successfully.'
      redirect_to users_path
    else
      flash[:alert] = 'You are not authorized to perform this action.'
      redirect_to root_path
    end
  end

  def destroy
    @user = User.find(params[:id])
    @user.destroy
    redirect_to users_path, notice: 'User was successfully destroyed.'
  end

  private

  def authorize_admin!
    if current_user.admin? && current_user == @user
      redirect_to root_path, alert: 'Admin cannot change their own role.'
    elsif current_user.admin?
      # Admins can only edit other users' roles
      nil
    else
      redirect_to root_path, alert: 'You are not authorized to perform this action.'
    end
  end

  def user_params
    params.require(:user).permit(:first_name, :last_name, :contact_number, :username, :address, :gender, :email, :password, :password_confirmation, :role)
  end

end

My routes.rb looks like this as well:

Rails.application.routes.draw do
  devise_for :users

  root 'home#index'
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  # get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  
  resources :users, only: [:index, :edit, :update]
  get '/admin_dashboard', to: 'dashboard#admin', as: 'admin_dashboard'
  get '/therapist_dashboard', to: 'dashboard#therapist', as: 'therapist_dashboard'
  get '/client_dashboard', to: 'dashboard#client', as: 'client_dashboard'

end

and my Registration controller is like this as well:

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]


  protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, :contact_number, :username, :address, :gender, :email, :password, :password_confirmation, :role])
  end
end

Here is my gem file as well:

source 'https://rubygems.org'

ruby '3.1.0'

gem 'cancancan'

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem 'rails', '~> 7.1.3', '>= 7.1.3.2'

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem 'sprockets-rails'

# Use postgresql as the database for Active Record
gem 'pg', '~> 1.1'

# Search Functionality
gem 'pg_search'

# Use the Puma web server [https://github.com/puma/puma]
gem 'puma', '>= 5.0'

# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem 'importmap-rails'

# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem 'turbo-rails'

# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem 'stimulus-rails'

# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem 'jbuilder'

# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mswin mswin64 mingw x64_mingw jruby]

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

group :development, :test do
  gem 'capybara'
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem 'debug', platforms: %i[mri mswin mswin64 mingw x64_mingw]
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'rspec-rails'
  gem 'selenium-webdriver'
end

group :development do
  # Use console on exceptions pages [https://github.com/rails/web-console]
  gem 'web-console'

  # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
  # gem "rack-mini-profiler"

  # Speed up commands on slow machines / big apps [https://github.com/rails/spring]
  # gem "spring"

  gem 'error_highlight', '>= 0.4.0', platforms: [:ruby]
end

group :test do
  gem 'database_cleaner'
  gem 'shoulda-matchers', '~> 6.2'
  gem 'warden', '~> 1.2'
end

gem 'hotwire-rails', '~> 0.1.3'

gem 'tailwindcss-rails', '~> 2.3'

gem 'view_component', '~> 3.11'

gem 'devise', '~> 4.9'

gem 'launchy', '~> 3.0'

I have tried debugging from the HTML page of the sign-up to no avail. I have also tried other solutions from StackOverflow speaking of this issue but none seems to be working for me.
The HTML markup shows that the username field exists but it’s not being filled after using all manner of fill_in and this:

if user_role == 'client'
  fill_in 'Username', with: 'johndoe'
end

as well as fill_in_field('Username', with: 'johndoe') and fill_in_field_with_placeholder('Username', with: 'johndoe')

The Username field exists in the HTML markup of the Sign-up page so I don’t know why Capybara is not filling it. Any pointers as to how I can solve this? By the way, I’m using Rails 7 and Ruby version 3.1.0.

LEAVE A COMMENT