2
votes

I'm trying to test the 'destroy' action for my nested comments controller.

User model has_many :comments, dependent: :destroy Movie model has_many :comments, dependent: :destroy Comments model belongs_to :user and :movie Here is my comments controller

  def create
    @comment = @movie.comments.new(comment_params.merge(user: current_user))

    if @comment.save
      flash[:notice] = 'Comment successfully added'
      redirect_to @movie
    else
      flash.now[:alert] = 'You can only have one comment per movie'
      render 'movies/show'
    end
  end

  def destroy
    @comment = @movie.comments.find(params[:id])

    if @comment.destroy
      flash[:notice] = 'Comment successfully deleted'
    else
      flash[:alert] = 'You are not the author of this comment'
    end
    redirect_to @movie
  end

  private

  def comment_params
    params.require(:comment).permit(:body)
  end
  def set_movie
    @movie = Movie.find(params[:movie_id])
  end

Of course there is also before_action :set_movie, only: %i[create destroy] at the top.

Here are my specs, I'm using FactoryBot and all factories works fine in other examples so I think the issue is somewhere else.

  describe "DELETE #destroy" do
    let(:user) { FactoryBot.create(:user) }
    let(:movie) { FactoryBot.create(:movie) }
    before do
      sign_in(user)
    end

    it "deletes comment" do
      FactoryBot.create(:comment, movie: movie, user: user)

      expect do
        delete :destroy, params { movie_id: movie.id }
      end.to change(Comment, :count).by(-1)
      expect(response).to be_successful
      expect(response).to have_http_status(:redirect)
    end
  end

I've got an error ActionController::UrlGenerationError: No route matches {:action=>"destroy", :controller=>"comments", :movie_id=>1} I think my address in specs destroy action is wrong but how to define it in a good way?

3

3 Answers

6
votes

You need to specify id of a comment you want to remove:

it "deletes comment" do
  comment = FactoryBot.create(:comment, movie: movie, user: user)

  expect do
    delete :destroy, params { id: comment.id, movie_id: movie.id }
  end.to change(Comment, :count).by(-1)
  # ...
end
1
votes

I want to contribute here with one more approach. Sometimes you have to be sure that you've deleted the exact instance (comment_1, not comment_2).

it "deletes comment" do
  comment_1 = FactoryBot.create(:comment, movie: movie, user: user)
  comment_2 = FactoryBot.create(:comment, movie: movie, user: user)

  delete :destroy, params { id: comment_1.id, movie_id: movie.id }

  expect { comment_1.reload }.to raise_error(ActiveRecord::RecordNotFound)
  # ...
end
0
votes

Somehow I was never able to pass an ID, either in params {} or as a direct argument following "delete". Instead, I made it work like so:

it "DELETE will cause an exception" do
  delete "/comments/#{comment.id}"
  expect { comment.reload }.to raise_error(ActiveRecord::RecordNotFound)
end