Reputation: 614
I used this tutorial to generate an API for my Rails 5 app. This is between Comments and Post tables. Post has a 1:m relationship with Comment.
But rspec fails every test. I have checked my files and hierarchy over and over again, still not seeing where the problem is.
Here is the output of rails routes
:
Prefix Verb URI Pattern Controller#Action
post_comments GET /posts/:post_id/comments(.:format) comments#index
POST /posts/:post_id/comments(.:format) comments#create
post_comment GET /posts/:post_id/comments/:id(.:format) comments#show
PATCH /posts/:post_id/comments/:id(.:format) comments#update
PUT /posts/:post_id/comments/:id(.:format) comments#update
DELETE /posts/:post_id/comments/:id(.:format) comments#destroy
post_like POST /posts/:post_id/like(.:format) posts#like
post_dislike POST /posts/:post_id/dislike(.:format) posts#dislike
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
post GET /posts/:id(.:format) posts#show
PATCH /posts/:id(.:format) posts#update
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
my config/routes.rb
:
Rails.application.routes.draw do
resources :posts do
resources :comments
post :like
post :dislike
end
end
application_controller.rb
: (Even with ActionController::API, it does not work)
class ApplicationController < ActionController::Base
include Response
include ExceptionHandler
protect_from_forgery with: :exception
end
My comments_controller.rb
class CommentsController < ApplicationController
before_action :set_post
before_action :set_post_comment, only: [:show, :update, :destroy]
# GET /comments
# GET /comments.json
def index
#@comments = Comment.all
json_response(@post.comments)
end
# GET /comments/1
# GET /comments/1.json
def show
json_response(@comment)
end
# POST /comments
# POST /comments.json
def create
@comment = Comment.new(comment_params)
@post.comments.create!(comment_params)
json_response(@post, :created)
if @comment.save
render :show, status: :created, location: @comment
else
render json: @comment.errors, status: :unprocessable_entity
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
@comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:commenter, :comment, :description, :post)
end
def set_post
@post = Post.find(params[:post_id])
end
def set_post_comment
@comment = @post.comments.find_by!(id: params[:id]) if @post
end
end
Finally, the posts_controller.rb
:
class PostsController < ApplicationController
before_action :set_post, only: [:show, :update, :destroy]
# GET /posts
# GET /posts.json
def index
@posts = Post.all
json_response(@posts)
end
# GET /posts/1
# GET /posts/1.json
def show
json_response(@post)
end
# POST /posts
# POST /posts.json
def create
@post = Post.new(post_params)
json_response(@post, :created)
if @post.save
render :show, status: :created, location: @post
else
render json: @post.errors, status: :unprocessable_entity
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:poster, :vote, :description, :comment, :user_id, :image_base)
end
end
UPDATE: showing spec files
my spec/requests/comments_spec.rb
:
require 'rails_helper'
RSpec.describe "Comments", type: :request do
# Initialize the test data
let!(:post) { create(:post) }
let!(:comments) { create_list(:comment, 20, post_id: post.id) }
let(:post_id) { post.id }
let(:id) { comments.first.id }
# Test suite for GET /posts/:post_id/comments
describe 'GET /posts/:post_id/comments' do
before { get "/posts/#{post_id}/comments" }
context 'when post exists' do
it 'returns status code 200' do
expect(response).to have_http_status(200)
end
it 'returns all post comments' do
expect(json.size).to eq(20)
end
end
context 'when post does not exist' do
let(:post_id) { 0 }
it 'returns status code 404' do
expect(response).to have_http_status(404)
end
it 'returns a not found message' do
expect(response.body).to match(/Couldn't find Post/)
end
end
end
# Test suite for GET /posts/:post_id/comments/:id
describe 'GET /posts/:post_id/comments/:id' do
before { get "/posts/#{post_id}/comments/#{id}" }
context 'when post item exists' do
it 'returns status code 200' do
expect(response).to have_http_status(200)
end
it 'returns the item' do
expect(json['id']).to eq(id)
end
end
context 'when post item does not exist' do
let(:id) { 0 }
it 'returns status code 404' do
expect(response).to have_http_status(404)
end
it 'returns a not found message' do
expect(response.body).to match(/Couldn't find Comment/)
end
end
end
# Test suite for PUT /posts/:post_id/comments
describe 'POST /posts/:post_id/comments' do
let(:valid_attributes) { { comment: 'A Comment', commenter: 'Luke Shaw' } }
context 'when request attributes are valid' do
before { post "/posts/#{post_id}/comments", params: valid_attributes }
it 'returns status code 201' do
expect(response).to have_http_status(201)
end
end
context 'when an invalid request' do
before { post "/posts/#{post_id}/comments", params: {} }
it 'returns status code 422' do
expect(response).to have_http_status(422)
end
it 'returns a failure message' do
expect(response.body).to match(/Validation failed: Commenter or Comment can't be blank/)
end
end
end
end
The spec/requests/posts_spec.rb
:
require 'rails_helper'
RSpec.describe "Posts", type: :request do
# initialize test data
let!(:posts) { create_list(:post, 10) }
let(:post_id) { posts.first.id }
describe "GET /posts" do
# make HTTP get request before each example
before { get '/posts' }
it 'returns posts' do
# Note `json` is a custom helper to parse JSON responses
expect(json).not_to be_empty
expect(json.size).to eq(10)
end
it 'returns status code 200' do
expect(response).to have_http_status(200)
end
end
# Test suite for GET /posts/:id
describe 'GET /posts/:id' do
before { get "/posts/#{post_id}" }
context 'when the record exists' do
it 'returns the post' do
expect(json).not_to be_empty
expect(json['id']).to eq(post_id)
end
it 'returns status code 200' do
expect(response).to have_http_status(200)
end
end
context 'when the record does not exist' do
let(:post_id) { 100 }
it 'returns status code 404' do
expect(response).to have_http_status(404)
end
it 'returns a not found message' do
expect(response.body).to match(/Couldn't find Post/)
end
end
end
The spec/models/post_spec.rb
:
require 'rails_helper'
RSpec.describe Post, type: :model do
# Association test
# ensure Post model has a 1:m relationship with the Comment model
it { should have_many(:comments).dependent(:destroy) }
# Validation tests
# ensure columns are present before saving
it { should validate_presence_of(:poster) }
it { should validate_presence_of(:description) }
end
ANd spec/models/comment_spec.rb
:
require 'rails_helper'
RSpec.describe Comment, type: :model do
# Association test
# ensure a comment record belongs to a single post record
it { should belong_to(:post) }
# Validation test
# ensure column name is present before saving
it { should validate_presence_of(:comment) }
it { should validate_presence_of(:commenter) }
end
UPDATE 2: Showing spec/factories/*.rb files
Showing spec/factories/posts.rb
:
FactoryBot.define do
factory :post do
image { Rack::Test::UploadedFile.new(Rails.root.join('public', 'system', 'posts', 'images', '000', '000', '001', 'original', 'img1.jpeg'), 'image/jpeg') }
poster { Faker::Lorem.sentence }
vote { Faker::Number.number(10).to_i}
description { Faker::Lorem.paragraph }
end
end
Showing spec/factories/comments.rb
:
FactoryBot.define do
factory :comment do
commenter { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
post_id nil
end
end
My `routes show clear inheritance and I am enforcing that in my controllers. Still, I get errors like:
Failure/Error: expect(:delete => "/comments/1").to route_to("comments#destroy", :id => "1") No route matches "/comments/1" AND RuntimeError: /home/user/projects/app/public/system/posts/images/000/000/001/original/img1.jpeg file does not exist
Upvotes: 0
Views: 1148
Reputation: 27747
Ok, so the error that is causing every spec to fail is that uploaded image. It can't find the image file that you are trying to upload. Where is that image file actually located? Create that file and put it in the right place and they should start working again.
As to the routing-spec - that's the error that you included in your question, and it'll be localised to your routing-spec only (spec/routing/comments_routing_spec.rb
according to your paste-bin), and will be fixed by just adjusting the routes to be the nested ones. They should include everything necessary to make the routes work (including a valid post-id).
Secondly, it should be something like:
expect(:delete => "/posts/1/comments/1").to route_to("comments#destroy", :id => "1", :post_id => "1")
Note: personally I don't bother with route-specs for simple resources, just for complex ones, but you may prefer it as a backup while you're getting used to Rails.
Upvotes: 1