0
  • hey guys, am trying to build tests for my controllers using rspec , I have 3 models and controllers i.e user, payment and home -These mode;s are associated in search a way that, payment belongs to user and home, home has many payments, and user has many payments.

-I have managed to write tests for these associations for the models , now am stuck forthe controllers.

  • Below is what i have tried to write
require 'rails_helper'

RSpec.describe 'Payments', type: :request do
  let(:valid_attributes) do
    { first_name: 'mutebi', last_name: 'godfrey', phone_number: '+256780140670', address: 'kampala', money_paid: '6000', date: '25/08/2023',
      nin_number: 'KK45896587450P', user_id: '5', home_id: '9' }
  end

  let(:invalid_attributes) do
    skip('Add a hash of attributes invalid for your model')
  end

  describe 'GET /index' do
    it 'renders a successful response' do
      Payment.create! valid_attributes
      get payments_url, headers: valid_headers, as: :json
      expect(response).to be_successful
    end
  end

  describe 'GET /show' do
    it 'renders a successful response' do
      payment = Payment.create! valid_attributes
      get payment_url(payment), as: :json
      expect(response).to be_successful
    end
  end
end
  • and getting this error below in the console. i think the problem could be with the associations
  1) Payments GET /index renders a successful response
     Failure/Error: Payment.create! valid_attributes
     
     ActiveRecord::RecordInvalid:
       Validation failed: User must exist, Home must exist
     # ./spec/requests/payments_spec.rb:17:in `block (3 levels) in <main>'

  2) Payments GET /show renders a successful response
     Failure/Error: payment = Payment.create! valid_attributes
     
     ActiveRecord::RecordInvalid:
       Validation failed: User must exist, Home must exist
     # ./spec/requests/payments_spec.rb:25:in `block (3 levels) in <main>'
  • Below is my controller which am trying to test
# frozen_string_literal: true

module Api
  module V1
    class PaymentsController < ApplicationController
      before_action :authenticate_user!
      before_action :set_payment, only: %i[show edit update destroy]
      # GET /payments or /payments.json
      def index
        if user_signed_in?
          @payments = current_user.payments.order(created_at: :desc)
          render json: @payments.to_json(include: %i[home user])
        else
          render json: {}, status: 401
        end
      end

      # GET /payments/1 or /payments/1.json
      def show
        @payment = current_user.payments.find(params[:id])
        render json: @payment.to_json(include: %i[home user])
      end

      # GET /payments/new
      def new
        @home = Home.find(params[:home_id])
        @payment = @home.payments.new
      end

      # GET /payments/1/edit
      def edit; end

      # POST /payments
      def create
        @home = Home.find(params[:home_id])
        @payment = @home.payments.create(payment_params) do |p|
          p.user = current_user # if user_signed_in?
        end
        if @payment.save
          render json: @payment.to_json(include: %i[home user])
        else
          render json: @payment.errors, status: :unprocessable_entity
        end
      end

      # PATCH/PUT /payments/1 or /payments/1.json
      def update
        @home = Home.find(params[:home_id])
        @payment = @home.payments.find(params[:id])
        if @payment.update
          render json: { notice: 'Payment was successfully updated.' },
                 status: :ok
        else
          render json: { error: 'Unable to update payment' },
                 status: :unprocessable_entity
        end
      end

      # DELETE /payments/1 or /payments/1.json

      def destroy
        @home = Home.find(params[:home_id])
        @payment = @home.payments.find(params[:id])
        @payment.destroy
        render json: { notice: 'Payment succefully removed' }
      end

      private

      # Use callbacks to share common setup or constraints between actions
      def set_payment
        @payment = Payment.find(params[:id])
      end

      # Only allow a list of trusted parameters through.
      def payment_params
        params.require(:payment).permit(:first_name, :last_name, :phone_number, :address, :money_paid, :date,
                                        :nin_number, :user_id, :home_id)
      end
    end
  end
end
  • 1
    You have tagged the question with FactoryBot but you're not actually using it. I think it's pretty clear where you need to do more research. Also they way you're structuring these tests is pretty strange - `Payment.create! valid_attributes` does not belong in the example. Setting up the dependencies of the test should be done via `let!` or `before` so that your don't have to repeat yourself. – max Apr 13 '23 at 12:00
  • i would like to know how i can use factory bot rails. I have used it with model specs which is easy but with requests it has proved to be difficult for me to implement – mutebi ug official Apr 13 '23 at 16:33
  • Well what have you actually tried/researched? Whats the problem that you enounted? There is no shortage of tutorials for this combo. – max Apr 13 '23 at 16:46
  • i have used factory_bot_rails with models like below. FactoryBot.define do factory :payment do first_name { 'Mutebi' } last_name { 'Godfrey' } phone_number { '+256780140670' } address { 'Kampala' } money_paid { '9.99' } nin_number { 'NLJ8589654785P' } home_id { 1 } date { '2023-04-07' } user_id { '7' } status { 'pending' } home user end end . am only stuck with how i can use this request spec – mutebi ug official Apr 13 '23 at 17:22
  • You would use factories for example with `let!(:payments) { FactoryBot.create_list(:payments, 3) }`. But you should not be hardcoding those ids - EVER. Instead follow the instructions for assocations https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md#associations – max Apr 13 '23 at 18:04
  • Max gave a lot of useful advice, but to answer your question directly: yes, you can use FactoryBot for all tests, models, controllers, etc. The error you're getting is because rails expects there to be a User with id 5 and Home with id 9, but you haven't created them. – Mike Szyndel Apr 13 '23 at 19:13
  • Short and sweet FactoryBot can be used for any test, controller, and model. – Rutik Patel Apr 15 '23 at 14:43

1 Answers1

0

You haven't really understood what factories are or how to use them. Factories should generate unique data that you can use in your tests.

So to clean up your factory you could use the ffaker gem to generate psuedo random data instead of your own personal data (which you shouldn't be using like this):

FactoryBot.define do 
  factory :payment do 
    first_name { FFaker::Name.first_name } 
    last_name { FFaker::Name.last_name } 
    phone_number { FFaker::PhoneNumberNL.international_mobile_phone_number } 
    address { FFaker::Address.street_address } 
    money_paid { FFaker::Number.decimal } 
    nin_number { FFaker::Identification.ssn } 
    user # DO NOT HARDCODE ID's!
    home # DO NOT HARDCODE ID's!
  end
end

Using psuedo random data in your tests avoids your tests becoming reliant on information outside the test. This one of the biggest reasons why you would use factories instead of fixtures.

You then use the factory in your specs in the test setup phase:

require 'rails_helper'

RSpec.describe 'Payments', type: :request do
  let(:payment) { FactoryBot.create(:payment) }

  describe 'GET /index' do
    let!(:payments) { FactoryBot.create_list(:payment, 5) }
    it 'renders a successful response' do
      get payments_url, headers: valid_headers, as: :json
      expect(response).to be_successful
    end
  end

  describe 'GET /show' do
    it 'renders a successful response' do
      get payment_url(payment), as: :json
      expect(response).to be_successful
    end

    it 'has the correct JSON' do
      get payment_url(payment), as: :json
      expect(response.parsed_body).to match(a_hash_including(
        payment.attributes.slice(
          "first_name", "last_name", "address" # ...
        )
      ))
    end
  end
end
max
  • 96,212
  • 14
  • 104
  • 165