How can I test ActionCable using RSpec? - ruby-on-rails

This is my NotificationChannel
class NotificationChannel < ApplicationCable::Channel
def subscribed
stream_from "notification_user_#{user.id}"
end
def unsubscribed
stop_all_streams
end
end
How can I write test for this ActionCable channels
This is my Rspec
require 'rails_helper'
require_relative 'stubs/test_connection'
RSpec.describe NotificationChannel, type: :channel do
before do
#user = create(:user)
#connection = TestConnection.new(#user)
#channel = NotificationChannel.new #connection, {}
#action_cable = ActionCable.server
end
let(:data) do
{
"category" => "regular",
"region" => "us"
}
end
it 'notify user' do
#error is in below line
expect(#action_cable).to receive(:broadcast).with("notification_user_#{#user.id}")
#channel.perform_action(data)
end
end
when I run this spec it gives error
Wrong number of arguments. Expected 2, got 1
I used this link to write code for stub and this file.
Rails version - 5.0.0.1
Ruby version - 2.3.1

expect(#action_cable).to receive(:broadcast).with("notification_user_#{#user.id}")
Looking closely broadcast needs two parameters so
expect(#action_cable).to receive(:broadcast).with("notification_user_#{#user.id}", data)
I cant guess what is going on however one issue is
let(:data) do
{
"action" => 'action_name',
"category" => "regular",
"region" => "us"
}
end
You need an action for perform_action.
However you dont have any action defined in NotificationsChannel.
Otherwise you can try
NotificationChannel.broadcast_to("notification_user_#{#user.id}", data )

Related

Mock a singleton class In Ruby on Rails using Rspec [SOLVED]

Hi I Try to create a mock for follow class:
module EstablishmentsQueryService
class << self
def find_by_id(id)
Establishment.find_by!(id:)
rescue ActiveRecord::RecordNotFound
raise EstablishmentNotFoundError.new id
end
end
end
to try test my controller
# frozen_string_literal: true
module Api
module V1
# Controllewr to manager Establishments
class EstablishmentsController < Api::V1::ApiController
before_action :validate_id, only: %i[destroy update show]
before_action :load_establishment, only: %i[destroy update show]
def show; end
def create
#establishment = Establishment.new(establishment_params)
#establishment = EstablishmentService.save(#establishment)
render status: :created
end
def destroy
EstablishmentService.delete(#establishment)
end
def update
#establishment.attributes = establishment_params
#establishment = EstablishmentService.save(#establishment)
end
private
def validate_id
message = I18n.t('establishment_controller.id.invalid', id: params[:id])
UuidValidateService.call(params[:id], message)
end
def load_establishment
#establishment = EstablishmentsQueryService.find_by_id(params[:id])
end
def establishment_params
params.require(:establishment).permit(:name, :cnpj, :description)
end
end
end
end
follow my test:
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::Establishments', type: :request do
describe 'GET /api/v1/establishments/:id' do
context 'when has establishment' do
let(:establishment) { build(:establishment, id: p, created_at: DateTime.now, updated_at: DateTime.now) }
before do
allow_any_instance_of(EstablishmentsQueryService).to receive(:find_by_id).and_return(establishment)
get "/api/v1/establishments/#{establishment.id}"
end
it 'then http status is ok' do
expect_status_is_ok
end
it 'has body equal to expected' do
except_field_by_field(establishment, body_to_open_struct, %i[id name cnpj description])
end
end
context 'when has no establishment' do
before do
get "/api/v1/establishments/#{UUID.new.generate}"
end
it 'then http status is not_found' do
expect_status_is_not_found
end
end
context 'when use invalid id' do
before { get "/api/v1/establishments/#{FFaker::Lorem.word}" }
it 'then http status is bad_request' do
expect_status_is_bad_request
end
end
end
describe 'PUT /api/v1/establishments/:id' do
let(:establishments_query_service) { allow(EstablishmentsQueryService) }
let(:establishments_service) { allow(EstablishmentsService) }
context 'when updated with success' do
let(:establishment) { build(:establishment) }
let(:id) { UUID.new.generate }
before do
establishments_query_service.to receive(:find_by_id) { |p| build(:establishment, id: p, created_at: DateTime.now, updated_at: DateTime.now) }
establishments_service.to receive(:save) do |p|
to_return = p
to_return.created_at = DateTime.now
to_return.updated_at = DateTime.now
end
put "/api/v1/establishments/#{id}"
end
it 'then http status is ok' do
expect_status_is_ok
end
it 'has body equal to expected' do
actual = body_to_open_struct
except_field_by_field(establishment, actual, %i[name cnpj description])
expected(actual.id).to eq(id)
end
end
context 'when has no establishment' do
end
context 'when has constraint violation' do
end
end
describe 'DELETE /api/v1/establishments/:id' do
end
describe 'POST /api/v1/establishments' do
end
end
If I work using allow_any_instance_of a test ignore configuration, use a real configuration and fails because has no data stores. If I use double I received a follow error:
Api::V1::Establishments GET /api/v1/establishments/:id when has establishment then http status is ok
Failure/Error: allow_any_instance_of(EstablishmentsQueryService).to receive(:find_by_id).and_return(establishment)
EstablishmentsQueryService does not implement #find_by_id
I think the right away is user allow_any_instance_of because this config is for static methods, but didn't work
how can I mock my class to test my controller? I using Ruby 3.1.2, rails 7.0.3 and rspec-rails 5.1.2
thank you
I found my problem, I forgot to definie expected params in my confi using with()
allow(EstablishmentsQueryService).to receive(:find_by_id).with(establishment.id).and_return(establishment)

How to mock database calls with RSpec

I am trying to test a JSON API endpoint and I am getting an ActiveRecord::RecordNotFound exception because the call to user.find is failing.
How can I mock the active record calls for my test so that it return something and doesn't throw an exception?
(I am using factory_girl also, in case I can use that)
def do_something
success = false
message = ""
user = User.find(params[:user_id])
if user.present?
# ...
if user.save!
success = true
end
end
render json: {
"success": success,
"message": message
}
end
My RSpec looks like:
#RSpec.describe Api::UsersController, type: :controller do
it "should return some JSON" do
payload = {
user_id: "1",
# ...
}.to_json
post :do_something, payload, format: :json
expected = {
success: false,
message: ""
}.to_json
expect(response.body).to eq(expected)
end
I think you need to create a user first by using FactoryGirl, then you can pass id of that user in payload, like this:
let(:user) { create :user } # create user by FactoryGirl
it "should return some JSON" do
payload = {
user_id: user.id,
# ...
}.to_json
...
end
Well in case you want to mock database with ActiveRecord, you can try something like this.
let(:user) { build_mock_class.new() }
before(:all) { create_table }
after(:all) { drop_table }
def build_mock_class
Class.new(ActiveRecord::Base) do
self.table_name = 'mock_table'
end
end
def create_table
ActiveRecord::Base.connection.create_table :mock_table do |t|
t.integer :user_id
t.timestamps
end
end
def drop_table
ActiveRecord::Base.connection.drop_table :mock_table
end
But you have to establish connection before you proceed and you can put your connection adapter in spec_helper
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: ':memory:'
)
You can change your column names with whatever you want and also this will create table when spec is run and will destroy once spec is passed.
Note: Don't forget to require active_record and sqlite3 in your spec_helper or wherever you want to use.
Hope it helps.

Defining fabricator w/ file attachment & testing file upload w/ rspec

I'm trying to figure out the how to test file attachments w/ the Fabrication and Rspec gems. The file upload works fine when testing the site manually, there's just no Rspec coverage. The problem seems to be that I don't know how to include an attachment in a PUT request.
How do I add a file attachment, preferably using a fabricator, to this test
Fabricator:
Fabricator(:application) do
email Faker::Internet.email
name Faker::Name.name
resume_url { File.open(
File.join(
Rails.root,
"spec",
"support",
"files",
"hey_look_a_pdf_pdf_lolz.pdf"
)
)
}
end
Controller:
class ApplicationsController < ApplicationController
def update
#application = Application.find_or_initialize_by(id: params[:id])
if #application.update(application_params)
flash[:success] = "Application has been saved :)"
redirect_to application_path(#application)
else
render :edit
end
end
private
def application_params
params[:application].permit(:email, :job_id, :name, :resume_url)
end
end
Controller test
require "spec_helper"
# this is a sample application attributes being passed into controller
# it should have a file attachment, but haven't figured out how to do that
#
# {
# "id"=>"fa446fdf-b82d-48c0-8979-cbbb2de4fb47",
# "email"=>"old#example.com",
# "name"=>"Dr. Rebeca Dach",
# "resume_url"=>nil,
# "job_id"=>"cf66dbcf-d110-42cc-889b-0b5ceeeed239",
# "created_at"=>nil,
# "updated_at"=>nil
# }
describe ApplicationsController do
context "PUT /applications/:id" do
it "creates an application" do
expect { put(
:update,
application: application.attributes,
id: application.id
)}.to change{Application.count}.from(0).to(1)
end
end
end
Update #1
The following Fabricator seems to work okay. I'm still not able to get file attachments to work in controller tests.
Fabricator(:application) do
email Faker::Internet.email
name Faker::Name.name
resume_url { ActionDispatch::Http::UploadedFile.new(
tempfile: File.new(Rails.root.join(
"./spec/support/files/hey_look_a_pdf_pdf_lolz.pdf"
)),
filename: File.basename(File.new(Rails.root.join(
"./spec/support/files/hey_look_a_pdf_pdf_lolz.pdf"
)))
)}
end
OK, I got it figured out. There were two pieces.
1) I had to fix the Fabricator, which I mentioned in "Update #1" to my question. Here's a simpler format for the Fabricator using Rack::Test::Upload.new
Fabricator(:application) do
email Faker::Internet.email
name Faker::Name.name
resume_url {
Rack::Test::UploadedFile.new(
"./spec/support/files/hey_look_a_pdf_pdf_lolz.pdf",
"application/pdf"
)
}
end
2) I was using Fabricator.build(:application).attributes, which wasn't compatible with a file attachment. Instead, I started using Fabricator.attributes_for(:application), and everything started working great. Here's the passing.
describe ApplicationsController do
context "PUT /applications/:id" do
let(:job) { Fabricate(:job) }
let(:application) do
Fabricate.attributes_for(
:application,
email: "old#example.com",
id: SecureRandom.uuid,
job_id: job.id
)
end
it "creates an application" do
expect { put(
:update,
application: application,
id: application["id"]
)}.to change{Application.count}.from(0).to(1)
end
end
end

ActionMailer::Base.deliveries always empty in Rspec

I'm trying to test some mailers with rspec but deliveries are always empty. Here is my rspec test:
require "spec_helper"
describe "AccountMailer", :type => :helper do
before(:each) do
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
end
it "should send welcome email to account email" do
account = FactoryGirl.create :account
account.send_welcome_email
ActionMailer::Base.deliveries.empty?.should be_false
ActionMailer::Base.deliveries.last.to.should == account.email
end
end
It fails with:
1) AccountMailer should send welcome email to account email
Failure/Error: ActionMailer::Base.deliveries.empty?.should be_false
expected true to be false
My send_welcome_email function looks like this ( that's my model ):
def send_welcome_email
AccountMailer.welcome self
end
And my mailer:
class AccountMailer < ActionMailer::Base
default from: APP_CONFIG['email']['from']
def welcome data
if data.locale == 'es'
template = 'welcome-es'
else
template = 'welcome-en'
end
mail(:to => data.email, :subject => I18n.t('welcome_email_subject'), :template_name => template)
end
end
Any ideas? I'm not sure about how to proceed.
Have you tested that it's working when you're actually running the app? Perhaps your test is correct to be failing.
I noticed that you're never calling deliver when you create the mail, so I suspect that the test is failing because email is, in fact, not getting sent. I would expect your send_welcome_email method to look more like
def send_welcome_email
AccountMailer.welcome(self).deliver
end

Testing mocked model with RSpec2 returning 0 items and no response body

I am attempting to create an API with Rails using BDD with RSpec.
Rails version is 3.1.1, Ruby version is 1.9.2, Devise version is 1.5.3, and rspec version is 2.7.0. I am relatively new to Rails and very new to RSpec.
I have defined a simple RSpec as follows to test a FormsController with essentially no logic.
describe FormsController, " handling GET /forms" do
include Devise::TestHelpers
render_views
before do
user = Factory.create(:user) # Handle Devise authentication
user.confirm!
sign_in user
#form = mock_model(Form)
Form.stub!(:all).and_return([ #form ])
end
it "gets successfully" do
get :index, :format => :json
response.should be_success
end
it "finds all forms" do
Form.should_receive(:all).and_return([#form])
get :index, :format => :json
Rails.logger.info "*** response.body="+response.body
end
end
Form controller code is very simple currently.
class FormsController < ApplicationController
before_filter :authenticate_user!
# GET /forms
# GET /forms.json
def index
#forms = Form.find_all_by_owner_id(current_user.id)
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #forms }
end
end
end
When I run the spec, "finds all forms" always fails with
Failure/Error: Form.should_receive(:all).and_return([#form])
(<Form(id: integer, title: string, owner_id: integer, created_at: datetime, updated_at: datetime) (class)>).all(any args)
expected: 1 time
received: 0 times
The output from log/test.log shows:
*** response.body=[]
Why? I feel that the problem stems from Form.stub!(:all).and_return([ #form ]), but I am not sure how to debug.
Thanks in advance.
It would help to post your controller code (that is being tested). The error says that the declaration Form.should_receive(:all).and_return([#form]) has not been satisfied. The declaration says you should have code like this in your controller's action: Form.all.
find_all_by_owner_id is not the same as Form.all. find_all_by_owner_id ends up doing
Form.where(...).all
which doesn't match the expectations you've set. In your particular case I'd tell should_receive that I'm expecting a call to find_all_by_owner_id rather than all.
After much more trial and error, the following solution worked for me.
I migrated from mocking the Form model to using Factory Girl to create the full model
I then updated the test to use to_json to compare the response against the model.
The spec is as follows.
describe FormsController, " handling GET /forms" do
include Devise::TestHelpers
render_views
before do
user = Factory.create(:user) # Handle Devise authentication
user.confirm!
sign_in user
#form1 = Factory.create(:form)
end
it "gets successfully" do
get :index, :format => :json
response.should be_success
end
it "finds all forms" do
get :index, :format => :json
response.body.should == [ #form1 ].to_json
Rails.logger.info "*** response.body="+response.body
end
end

Resources