0

I've been stuck on this problem for a few days now, please help!

my problem is the following: I'm trying to do an Rspec test in Rails in a posts controller, but when I get to the update, this error happens, I don't know why

#posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: %i[show update destroy]
  before_action :authenticate_user!, except: %i[index show]

  def index
    posts = Posts::List.new(params).execute
    render json: posts, meta: pagination(posts), each_serializer: PostSerializer, status: :ok
  end

  def show
    authorize @post
    render json: @post, serializer: PostSerializer, list_comments: true, status: :ok
  end

  def create
    @post = authorize Posts::Create.new(post_params, current_user).execute

    render json: @post, serializer: PostSerializer, status: :created
  end

  def update
    @post = authorize Posts::Update.new(post_params, @post).execute
    render json: @post, serializer: PostSerializer, status: :ok
  end

  def destroy
    authorize Posts::Destroy.new(@post).execute
    head :ok
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_post
    @post = Post.find(params[:id])
    authorize @post
  end

  # Only allow a list of trusted parameters through.
  def post_params
    params.require(:post).permit(:title, :description, :category_id)
  end
end

I'm using a resource in the action, each one has its own resource, this one is the update action resource

#resource update.rb

class Posts::Update
  attr_accessor :params, :post

  def initialize(params, post)
    @params = params
    @post = post
  end

  def execute
    post.update!(mount_params)
  end

  private

  def mount_params
    {
      title: params[:title] || post.title,
      description: params[:description] || post.description,
      category_id: params[:category_id] || post.category_id
    }
  end
end

the funny thing is that the create action works when tested and for me its operation is very similar compared to the update action

#posts_controller_spec.rb

require 'rails_helper'
require './spec/helpers/authentication_helper'

RSpec.describe PostsController, :focus, type: :controller do
  include AuthenticationHelper
  include Devise::Test::ControllerHelpers
  attr_accessor :post_one, :post_two, :post_three, :user

  before(:all) do
    posts = FactoryBot.create_list(:post, 3, :with_comments)
    @user = FactoryBot.create(:user)
    @post_one = posts.first
    @post_two = posts.second
    @post_three = posts.third
  end
  let(:valid_headers) do
    user.create_new_auth_token
  end
  let(:root_keys) { %w[post] }
  let(:expected_post_keys) { %w[id title description category_id user_id] }
  let(:expected_meta_keys) { %w[current_page per_page total_pages total_count] }
  let(:error_root_keys) { %w[error] }
  let(:expected_error_keys) { %w[message] }
  let(:params) do
    {
      post: {
        title: Faker::Quote.yoda,
        description: Faker::Lorem.characters(number: 15),
        category_id: FactoryBot.create(:category).id
      }
    }
  end

  describe 'GET #index' do
    let(:root_keys) { %w[posts meta] }

    before do
      get :index
      @body = JSON.parse(response.body)
    end

    it 'return status code :ok' do
      expect(response).to have_http_status(:ok)
    end

    it 'match with root keys' do
      expect(@body.keys).to contain_exactly(*root_keys)
    end

    it 'match with posts keys' do
      @body['posts'].map do |post|
        expect(post.keys).to contain_exactly(*expected_post_keys)
      end
    end

    it 'match with meta keys' do
      expect(@body['meta'].keys).to contain_exactly(*expected_meta_keys)
    end
  end

  describe 'GET #show' do
    context 'when post does not exists' do
      let(:root_keys) { %w[error] }
      let(:expected_error_keys) { %w[message] }

      before do
        get :show, params: { id: Faker::Number.number }
        @body = JSON.parse(response.body)
      end

      it 'return status code :not_found' do
        expect(response).to have_http_status(:not_found)
      end

      it 'match with root keys' do
        expect(@body.keys).to contain_exactly(*error_root_keys)
      end

      it 'match with post keys' do
        expect(@body['error'].keys).to contain_exactly(*expected_error_keys)
      end
    end

    context 'when post exists' do
      let(:expected_post_keys) { %w[id title description category_id user_id comments] }
      let(:expected_comment_keys) { %w[id comment post_id created_at] }

      before do
        get :show, params: { id: post_one.id }
        @body = JSON.parse(response.body)
      end

      it 'return status code :ok' do
        expect(response).to have_http_status(:ok)
      end

      it 'match with root keys' do
        expect(@body.keys).to contain_exactly(*root_keys)
      end

      it 'match with post keys' do
        expect(@body['post'].keys).to contain_exactly(*expected_post_keys)
      end

      it 'match with comment keys' do
        @body['post']['comments'].map do |comment|
          expect(comment.keys).to contain_exactly(*expected_comment_keys)
        end
      end
    end
  end

  describe 'POST #create' do
    before do # criar um contexto onde o usuario tenta criar um post sem fazer o login
      set_authentication_headers_for(user)
      post :create, params: params
      @body = JSON.parse(response.body)
    end

    it 'return status code :created' do
      expect(response).to have_http_status(:created)
    end

    it 'match with root keys' do
      expect(@body.keys).to contain_exactly(*root_keys)
    end

    it 'match with post keys' do
      expect(@body['post'].keys).to contain_exactly(*expected_post_keys)
    end
  end

  describe 'PUT #update' do
    context 'when post does not exists' do
      before do
        set_authentication_headers_for(user)
        put :update, params: params.merge({ id: Faker::Number.number })
        @body = JSON.parse(response.body)
      end

      it 'return status code :not_found' do
        expect(response).to have_http_status(:not_found)
      end

      it 'match with root keys' do
        expect(@body.keys).to contain_exactly(*error_root_keys)
      end

      it 'match with post keys' do
        expect(@body['error'].keys).to contain_exactly(*expected_error_keys)
      end
    end

    context 'when post exists' do
      before do
        set_authentication_headers_for(user)
        put :update, params: params.merge({ id: post_one.id })

        @body = JSON.parse([response.body].to_json).first
      end

      it 'return status code :ok' do
        expect(response).to have_http_status(:ok)
      end

      it 'match with root keys' do
        expect(@body.keys).to contain_exactly(*root_keys)
      end

      it 'match with post keys' do
        expect(@body['post'].keys).to contain_exactly(*expected_post_keys)
      end
    end
  end

  describe 'DESTROY #destroy' do
    context 'when post does not exists' do
      before do
        set_authentication_headers_for(user)
        delete :destroy, params: params.merge({ id: Faker::Number.number })
        @body = JSON.parse(response.body)
      end

      it 'return status code :not_found' do
        expect(response).to have_http_status(:not_found)
      end

      it 'match with root keys' do
        expect(@body.keys).to contain_exactly(*error_root_keys)
      end

      it 'match with post keys' do
        expect(@body['error'].keys).to contain_exactly(*expected_error_keys)
      end
    end

    context 'when post exists' do
      before do
        set_authentication_headers_for(user)
        delete :destroy, params: { id: post_one.id }
      end

      it 'return status code :no_content' do
        expect(response).to have_http_status(:no_content)
      end
    end
  end
end
PostsController
  GET #index
    return status code :ok
    match with root keys
    match with posts keys
    match with meta keys
  GET #show
    when post does not exists
      return status code :not_found
      match with root keys
      match with post keys
    when post exists
      return status code :ok
      match with root keys
      match with post keys
      match with comment keys
  POST #create
    return status code :created
    match with root keys
    match with post keys
  PUT #update
    when post does not exists
      return status code :not_found
      match with root keys
      match with post keys
    when post exists
      return status code :ok (FAILED - 1)
      match with root keys (FAILED - 2)
      match with post keys (FAILED - 3)
  DESTROY #destroy
    when post does not exists
      return status code :not_found
      match with root keys
      match with post keys
    when post exists
      return status code :no_content (FAILED - 4)

Failures:

  1) PostsController PUT #update when post exists return status code :ok
     Failure/Error: expect(response).to have_http_status(:ok)
       expected the response to have status code :ok (200) but it was :found (302)
     # ./spec/controllers/posts_controllers_spec.rb:163:in `block (4 levels) in <top (required)>'
     # ./spec/support/database_cleaner.rb:9:in `block (3 levels) in <main>'
     # ./spec/support/database_cleaner.rb:8:in `block (2 levels) in <main>'

  2) PostsController PUT #update when post exists match with root keys
     Failure/Error: expect(@body.keys).to contain_exactly(*root_keys)
     
     NoMethodError:
       undefined method `keys' for #<String:0x0000555e1e849aa0>
     # ./spec/controllers/posts_controllers_spec.rb:167:in `block (4 levels) in <top (required)>'
     # ./spec/support/database_cleaner.rb:9:in `block (3 levels) in <main>'
     # ./spec/support/database_cleaner.rb:8:in `block (2 levels) in <main>'

  3) PostsController PUT #update when post exists match with post keys
     Failure/Error: expect(@body['post'].keys).to contain_exactly(*expected_post_keys)
     
     NoMethodError:
       undefined method `keys' for nil:NilClass
     # ./spec/controllers/posts_controllers_spec.rb:171:in `block (4 levels) in <top (required)>'
     # ./spec/support/database_cleaner.rb:9:in `block (3 levels) in <main>'
     # ./spec/support/database_cleaner.rb:8:in `block (2 levels) in <main>'

  4) PostsController DESTROY #destroy when post exists return status code :no_content
     Failure/Error: expect(response).to have_http_status(:no_content)
       expected the response to have status code :no_content (204) but it was :found (302)
     # ./spec/controllers/posts_controllers_spec.rb:204:in `block (4 levels) in <top (required)>'
     # ./spec/support/database_cleaner.rb:9:in `block (3 levels) in <main>'
     # ./spec/support/database_cleaner.rb:8:in `block (2 levels) in <main>'

Finished in 1.64 seconds (files took 5.32 seconds to load)
24 examples, 4 failures

Failed examples:

rspec ./spec/controllers/posts_controllers_spec.rb:162 # PostsController PUT #update when post exists return status code :ok
rspec ./spec/controllers/posts_controllers_spec.rb:166 # PostsController PUT #update when post exists match with root keys
rspec ./spec/controllers/posts_controllers_spec.rb:170 # PostsController PUT #update when post exists match with post keys
rspec ./spec/controllers/posts_controllers_spec.rb:203 # PostsController DESTROY #destroy when post exists return status code :no_content

I searched for similar errors but I couldn't find one that solves my problem

3
  • 1
    There is a lot of unnecessary information in this post. Please scale it back to the parts that are relevant to the question. Commented May 30, 2023 at 20:29
  • 1
    welcome to stack overflow. You will have much more success if you reduce your code to the minimal reproducible example. See stackoverflow.com/help/minimal-reproducible-example. I doubt anybody's going to read through all the code you've posted. Commented May 30, 2023 at 21:03
  • may be you are right, but without all that information I would not have been able to answer the question Commented May 31, 2023 at 5:55

1 Answer 1

0

You are getting a redirection (probably to the login page)

My guess is that the user is not able to edit the post because it does not belong to him.

I would change the way you are creating the posts to belong to the user.

So changing

    posts = FactoryBot.create_list(:post, 3, :with_comments)
    @user = FactoryBot.create(:user)

into

    @user = FactoryBot.create(:user)
    posts = FactoryBot.create_list(:post, 3, :with_comments, user_id: @user.id)
    

might solve your problem.

Bonus: I would also add tests for exactly the case you are having now: If the user is not authorized to change a post (be it update or delete) it should be redirected, and what is most important, the post does not change (or is not deleted depending on the action being tested)

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much, you were right, I managed to overcome this problem thanks to your help, but now another one has appeared (always like this lol), the error that appears is related to the pundit gem that I am using for authorizations, this is the error that appeared: Failure / Error: @post = authorize Posts::Update.new(post_params, @post).execute Expert::NotDefinedError: unable to find policy TrueClassPolicy for true can you help me with this problem too?
I'm not familiar with pundit. But for what I read, your Posts::Update.new(post_params, @post).execute is returning true, instead of a Post object.
I'm not sure if you are using it right. I'm just guessing based on your implementation of Posts::Update: If the user is not allowed to create a post, the authorize will be too late, as Posts::Create has been already executed. I would also recommend you to read more about how rails updates the records from the params (keyword: Rails Strong Parameters), as the way you are doing it is not "the rails way"
ok, I'll look into what you said, thanks a lot!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.