[英]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.