0
votes

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

1
Can you show us the spec-file(s) as well? From what you've got so far, this looks like your route-spec is expecting the route to be /comments/1 where you have actually nested your routes under posts eg /posts/1/comments/1/ instead.Taryn East
@TarynEast I just have. That's the thing - it is nested in the routes.rb config filei_use_the_internet
Ok, your comments-controller spec looks like it should plausibly be ok. The error you've got in your question is for a route-spec, not a controller-spec... has that been updated to use the new nested routing? Are you getting errors for the comment-controller spec itself? (can you run it by itself to check)?Taryn East
@TarynEast yes I do. all my rspec tests fail. Here is the output. pastebin.com/8jK6vMKYi_use_the_internet
@TarynEast I did not know I had to include that file. My assumption was that since rspec mocks these requests, it just creates said dir and assumes img1.jpg is in that location. I have done so now. As for my spec/routing/comments_routing_spec.rb file. I have it like this now and still all tests fail. The thing now is my expected and return values do not match in the tests because of the extra post_id param. Here is my output: pastebin.com/RnFHRQSti_use_the_internet

1 Answers

1
votes

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.