MEH
MEH

Reputation: 1864

How to fix Twilio::REST::RestError while testing with Rspec?

I'm new to testing with RSpec and don't know how to fix this error while test creating a new Customer where my CustomerController

class Api::V1::Customers::RegistrationsController < DeviseTokenAuth::RegistrationsController

  def create
        customer = Customer.new(email: params[:email],
                            password: params[:password],
                            password_confirmation: params[:password_confirmation],
                            first_name: params[:first_name],
                            last_name: params[:last_name],
                            telephone_number: params[:telephone_number],
                            mobile_phone_number: params[:mobile_phone_number])
        if customer.save
          customer.generate_verification_code
          customer.send_verification_code
          render json: {message: 'A verification code has been sent to your mobile. Please fill it in below.'}, status: :created
        else
          render json: customer.errors
        end
     end
  end
end

where generate_verification_code and send_verification_code in Customer

class Customer < ActiveRecord::Base

  def generate_verification_code
    self.verification_code = rand(0000..9999).to_s.rjust(4, "0")
    save
  end

  def send_verification_code
    client = Twilio::REST::Client.new
    client.messages.create(
      from: Rails.application.secrets.twilio_phone_number,
      to: customer.mobile_phone_number,
      body: "Your verification code is #{verification_code}"
    )
  end

end

and test file for registrations_controller_spec.rb for Customer

require 'rails_helper'

RSpec.describe Api::V1::Customers::RegistrationsController, type: :controller do
    let(:customer) { FactoryBot.create(:customer) }

    before :each do
        request.env['devise.mapping'] = Devise.mappings[:api_v1_customer]
    end

    describe "Post#create" do
        it 'creates a new customer' do
            post :create, params: attributes_for(:customer)
            expect(response).to have_http_status(:created)
        end
    end
end

I got this error, after running the test:

 Failure/Error:
   client.messages.create(
     from: Rails.application.secrets.twilio_phone_number,
     to: customer.mobile_phone_number
     body: "Your verification code is #{verification_code}"
   )

 Twilio::REST::RestError:
   Unable to create record: The requested resource /2010-04-01/Accounts//Messages.json was not found

I got it that this error is happening because in testing it shouldn't call external Api (when Twilio send sms verification code to number)!

But any ideas how to solve this ?

Upvotes: 0

Views: 2507

Answers (1)

Chirantan
Chirantan

Reputation: 15654

The reason why this is happening is that your tests are trying to hit twilio's API and probably don't have correct configuration for test environment. For testing third party calls like Twilio, you need to mock HTTP requests. Someone has suggested VCR in comments. However, in my opinion, you should mock the TwilioClient itself by creating a fake twilio adapter. Something like this –

class TwilioAdapter

  attr_reader :client

  def initialize(client = Twilio::REST::Client.new)
    @client = client
  end

  def send_sms(body:, to:, from: Rails.application.secrets.twilio_phone_number)
    client.messages.create(
      from: from,
      to:   to,
      body: body,
    )
  end
end

Change send_verification_code method in Customer to –

def send_verification_code
  client = TwilioAdapter.new
  client.send_sms(
    to: customer.mobile_phone_number,
    body: "Your verification code is #{verification_code}"
  )
end

Now you in before block of controller test, mock TwilioAdapter's send_sms method.

require 'rails_helper'

RSpec.describe Api::V1::Customers::RegistrationsController, type: :controller do
    let(:customer) { FactoryBot.create(:customer) }

    before :each do
      request.env['devise.mapping'] = Devise.mappings[:api_v1_customer]
      # Here's the expectation
      expect_any_instance_of(TwilioAdapter).to receive(:send_sms).with(hash_including(:body, :to))
    end
    …

That should do the trick.

In any case, I strongly discourage this pattern of making 3rd part calls synchronously via models. I recommend creating an SMS service with a generic interface that uses the above mention twilio adapter for sending sms and do this asynchronously using sidekiq. https://www.twilio.com/blog/2015/10/delay-api-calls-to-twilio-with-rails-active-job-and-sidekiq.html

Upvotes: 2

Related Questions