Тестирование Rspe c API Rails 6 - конфликтное поведение - PullRequest
0 голосов
/ 09 апреля 2020

Мне нужна помощь с моим текущим подходом к тестированию. В настоящее время я тестирую свое приложение React-Rails, используя Rspe c, и изначально я настраиваю его в своем контроллере favourite_cocktail:

  def destroy
      @favouritecocktail = FavouriteCocktail.find(params[:id])
      @favouritecocktail.delete
  end

При тестировании запроса DELETE с использованием приведенного ниже кода:

 describe 'DELETE /api/v1/favourite_cocktails/:id' do
    let!(:users) { FactoryBot.create(:user) }
    let!(:cocktails) { FactoryBot.create(:cocktail) }
    let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10, cocktail: cocktails) }
    let(:cocktail_id) { favourite_cocktail.first.id }


    before do
      sign_in users
    end

    before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }


    it 'returns status code 204' do
      expect(response).to have_http_status(204)
    end
  end

проходит, но в моем приложении функция удаления любимого коктейля пользователя не работает. То есть, когда я нажимаю кнопку, чтобы удалить любимый коктейль пользователя, он не работает.

Однако, если я произвожу рефакторинг метода действия destroy в контроллере favourite_cocktail, то это:

   def destroy
      @favouritecocktail = current_user.favourite_cocktails.find_by(cocktail_id: params[:id])
      @favouritecocktail.delete
   end

функция, ответственная за удаление любимого коктейля пользователя, работает в приложении , Но когда я снова запускаю тест:

 describe 'DELETE /api/v1/favourite_cocktails/:id' do
    let!(:users) { FactoryBot.create(:user) }
    let!(:cocktails) { FactoryBot.create(:cocktail) }
    let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10, cocktail: cocktails) }
    let(:cocktail_id) { favourite_cocktail.first.id }


    before do
      sign_in users
    end

    before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }


    it 'returns status code 204' do
      expect(response).to have_http_status(204)
    end
  end

, он терпит неудачу, и это сообщение об ошибке, которое я получаю во время тестирования RSpe c:

Api::V1::FavouriteCocktailsController DELETE /api/v1/favourite_cocktails/:id returns status code 204
     Failure/Error: @favouritecocktail.delete

     NoMethodError:
       undefined method `delete' for nil:NilClass
     # ./app/controllers/api/v1/favourite_cocktails_controller.rb:47:in `destroy'
     # ./spec/requests/favourite_cocktails_spec.rb:80:in `block (3 levels) in <main>'
     # ./spec/rails_helper.rb:112:in `block (3 levels) in <top (required)>'
     # ./spec/rails_helper.rb:111:in `block (2 levels) in <top (required)>'

Теперь предпочтительный подход, который я хочу таков, что мой remove favourite_cocktail должен работать в приложении, а тест Rspe c должен пройти по маршруту DELETE так, чтобы он прошел. Я знаю, что при использовании FactoryBot не было создано ни одной записи favourite_cocktails, и меня интересует, как заставить FactoryBot создать запись, которую нужно удалить. Ниже приведены коды для API:

Gemfile

ruby '2.6.1'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.2', '>= 6.0.2.2'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
gem 'devise'
gem 'react-rails'
gem "font-awesome-rails"
gem 'foreman'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # gem 'rspec-rails', '~> 3.8'
  gem 'rspec-rails', git: 'https://github.com/rspec/rspec-rails', branch: "4-0-maintenance"
end


group :development do
  gem 'guard-rspec', require: false
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'rb-fsevent', '~> 0.10.3'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  gem 'web-console', '>= 3.3.0'
end

group :test do
  gem 'database_cleaner'
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'shoulda-matchers'
end

rout.rb

    Rails.application.routes.draw do
  devise_for :users
   get 'landing/index'
   get '/index', to: 'landing#index', as: 'index'


  namespace :api do
    namespace :v1 do
      resources :cocktails do
        put :favourite, on: :member
      end

      resources :favourite_cocktails, only: %i[create destroy]
      resources :favourites_dashboard, only: %i[index]
    end
  end

  root 'landing#app'
  match '*path', to: 'landing#app', via: :all
end

Favourite_cocktails controller

module Api
  module V1
    class FavouriteCocktailsController < ApplicationController
      skip_before_action :verify_authenticity_token


      def index
        @favouritecocktail = current_user.cocktails

        if user_signed_in? && @favouritecocktail
          render json: {status: 'SUCCESS', message: 'Loading all Favourite Cocktails', data: @favouritecocktail}, status: :ok
        else
          render json: {}, status: 401
        end
      end

      def create

        fav = FavouriteCocktail.new(favourite_params) do |c|
          c.user = current_user
        end

        if fav.save!
          render json: { message: 'created' }, status: :created
        else
          render json: { errors: fav.errors.full_messages },
           status: :unprocessable_entity
        end
      end

      def destroy
        @favouritecocktail = current_user.favourite_cocktails.find_by(cocktail_id: params[:id])
        @favouritecocktail.delete
      end

      private

      def favourite_params
        params.require(:favourite_cocktail).permit(:cocktail_id)
      end
    end
  end
end

Favourite_cocktails factory

FactoryBot.define do
  factory :favourite_cocktail do
    user
    cocktail
  end
end

Пользовательская фабрика

FactoryBot.define do
  factory :user do
    username { Faker::Name.name }
    email { Faker::Internet.safe_email }
    password { 'foobar' }
    password_confirmation { 'foobar' }
  end

  factory :random_user, class: User do
    username { Faker::Name.name }
    email { Faker::Internet.safe_email }
    password { Faker::Password.password }
    password_confirmation { Faker::Password.password_confirmation }
  end
end

Коктейльная фабрика

FactoryBot.define do
  factory :cocktail do
    name { Faker::Restaurant.name }
    description { Faker::Lorem.sentence }
    ingredients { Faker::Lorem.sentence }
    image { Faker::Avatar.image }
  end
end

Ассоциации

Favourite_cocktails

class FavouriteCocktail < ApplicationRecord
  belongs_to :user
  belongs_to :cocktail

  validates :user_id, uniqueness: { scope: :cocktail_id }
end

Пользователь

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :favourite_cocktails
  has_many :favourites, through: :favourite_cocktails, source: :cocktail

  validates :username, presence: true, uniqueness: true, allow_blank: false, length: { minimum: 5 }
  validates :email, presence: true, length: { minimum: 5 }
end

коктейль

class Cocktail < ApplicationRecord
  has_many :favourite_cocktails
  has_many :favourited, through: :favourite_cocktails, source: :user

  validates :name, presence: true, allow_blank: false, length: { minimum: 5 }
  validates :description, presence: true, allow_blank: false, length: { minimum: 10 }
  validates :ingredients, presence: true, allow_blank: false, length: { minimum: 10 }
  validates :image, presence: true
end

RSpe c

Любимый запрос коктейля Spe c

require 'rails_helper'

RSpec.describe Api::V1::FavouriteCocktailsController, type: :request do

  describe 'POST Favourite Cocktails' do
    let!(:users) { FactoryBot.create(:user) }
    let!(:cocktails) { FactoryBot.create_list(:cocktail, 10) }
    let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10) }
    let(:cocktail_id) { cocktails.first.id }

    let(:valid_params) do
      { favourite_cocktail: { cocktail_id: cocktails.first.id } }
    end

    before do
      sign_in users
    end

    context 'when the request is valid' do

      before { post '/api/v1/favourite_cocktails', params: valid_params }

      it 'returns status code 201' do
        expect(response).to have_http_status(201)
      end

      it 'returns a created status' do
        expect(response).to have_http_status(:created)
      end

    end

  end

  describe 'GET all favourite cocktails' do

    let!(:users) { FactoryBot.create(:user) }
    let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10) }
    let(:cocktail_id) { cocktails.first.id }


    before do
      sign_in users
      get '/api/v1/favourite_cocktails'
    end

    it 'returns HTTP status 200' do
      expect(response).to have_http_status 200
    end
  end



  describe 'DELETE /api/v1/favourite_cocktails/:id' do
    let!(:users) { FactoryBot.create(:user) }
    let!(:cocktails) { FactoryBot.create(:cocktail) }

    let!(:favourite_cocktail) { FactoryBot.create_list(:favourite_cocktail, 10, cocktail: cocktails) }
    let(:cocktail_id) { favourite_cocktail.first.id }


    before do
      sign_in users
    end

    before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }

    # thing = create(:thing)
    # delete '/things', :thing => { :id => thing.id'}

    it 'returns status code 204' do
      expect(response).to have_http_status(204)
    end
  end

end

Если есть другие вещи, которые вы хотели бы видеть, чтобы это заработало, пожалуйста, дайте мне знать. Спасибо за вашу помощь.

1 Ответ

0 голосов
/ 09 апреля 2020

Мне кажется, что ваша тестовая настройка RSpe c не воссоздает действительный сценарий "счастливого пути", поскольку коктейль, который вы пытаетесь удалить, на самом деле не принадлежит пользователю, в котором вы вошли как.

Вот небольшой рефакторинг, который, я думаю, должен помочь исправить тест:

describe 'DELETE /api/v1/favourite_cocktails/:id' do
  let!(:user) { FactoryBot.create(:user) }
  let!(:cocktail) { FactoryBot.create(:cocktail) }
  # Adding `user: user` is the important bit here according to your factory
  let!(:favourite_cocktail) { FactoryBot.create(:favourite_cocktail, user: user cocktail: cocktail) }
  let(:cocktail_id) { favourite_cocktail.id }

  before do
    sign_in users
  end

  before { delete "/api/v1/favourite_cocktails/#{cocktail_id}" }

  it 'returns status code 204' do
    expect(response).to have_http_status(204)
  end
end

А вот изменение, которое я внесу в реализацию удаления, чтобы попытаться удалить коктейль, которого у вас нет поскольку избранное не выдает исключение и 500:

def destroy
  @favouritecocktail = current_user.favourite_cocktails.find_by(cocktail_id: params[:id])
  @favouritecocktail.delete if @favouritecocktail
end

Конечно, это вернет успех, даже если не удастся удалить, но вы можете легко отправить ответ на 400 уровне, если это предпочтительнее ваше заявление.

...