简体   繁体   English

Rails 6 API Rspec 请求测试 - 冲突行为

[英]Rails 6 API Rspec request test - Conflicting behavior

I need help with my current testing approach.我需要有关当前测试方法的帮助。 I am currently testing my React-Rails app using Rspec and initially I setup this in my favourite_cocktail controller:我目前正在使用 Rspec 测试我的 React-Rails 应用程序,最初我在 favourite_cocktail controller 中进行了设置:

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

On testing the DELETE request using the code below:在使用以下代码测试 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

it passes, but on my app, the function responsible for deleting the user's favourite cocktail does not work.它通过了,但在我的应用程序上,负责删除用户最喜欢的鸡尾酒的 function 不起作用。 That is when I click a button to remove a users' favourite cocktail it does not work.那就是当我单击一个按钮以删除用户最喜欢的鸡尾酒时,它不起作用。

However, if I refactor the destroy action method in the favourite_cocktail controller to this:但是,如果我将 favourite_cocktail controller 中的销毁操作方法重构为:

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

the function responsible for deleting the user's favourite cocktail works on the application. function 负责删除应用程序上用户最喜欢的鸡尾酒作品。 But when I run the test again:但是当我再次运行测试时:

 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

it fails and this is the error message I get during the RSpec testing:它失败了,这是我在 RSpec 测试期间收到的错误消息:

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)>'

Now the preferred approach I want is such that my remove favourite_cocktail should work on the application and the Rspec test should hit the DELETE route such that it passes.现在我想要的首选方法是我的 remove favourite_cocktail 应该在应用程序上运行,并且 Rspec 测试应该命中 DELETE 路由,以便它通过。 I know that there is no record of favourite_cocktails created when using FactoryBot and my concern is how to make FactoryBot create a record to be deleted.我知道使用 FactoryBot 时没有创建 favourite_cocktails 的记录,我关心的是如何让 FactoryBot 创建要删除的记录。 Below are codes for the API:以下是 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

routes.rb路线.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 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

User Factory用户工厂

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

Cocktails factory鸡尾酒厂

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

Associations协会

Favourite_cocktails最喜欢的鸡尾酒

class FavouriteCocktail < ApplicationRecord
  belongs_to :user
  belongs_to :cocktail

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

User用户

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

cocktail鸡尾酒

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

RSpec RSpec

Favourite Cocktail Request Spec最喜欢的鸡尾酒要求规格

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

If there are other things you'd like to see to get this working please let me know.如果您希望看到其他的东西来让这个工作,请告诉我。 Thanks for your help.谢谢你的帮助。

It looks to me like your RSpec test setup isn't recreating a valid "happy path" scenario, as the cocktail you are trying to delete doesn't actually belong to the user you signed in as.在我看来,您的 RSpec 测试设置没有重新创建有效的“快乐路径”场景,因为您尝试删除的鸡尾酒实际上并不属于您登录的用户。

Here's a slight refactoring that I think should help fix the test:这是我认为应该有助于修复测试的轻微重构:

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

And here's a change I'd make to the delete implementation so that trying to delete a cocktail you don't have as a favorite doesn't throw an exception and 500:这是我对删除实现所做的更改,以便尝试删除您不喜欢的鸡尾酒不会引发异常和 500:

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

Of course, this will return success even if it fails to delete, but you could easily send a 400-level response if that is preferable to your application.当然,即使删除失败也会返回成功,但如果这比您的应用程序更可取,您可以轻松发送 400 级响应。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM