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)
Related
I understand this question has been asked before in various forms.
However I'm struggling with something that doesn't seem to be being solved in these answers. My logged in user is not persisting within the spec.
How are you supposed to replicate authentication/logged in users in a request spec?
Here's what I've tried, and what I'm doing.
I'm using Auth0 as my authentication handler. I have a signin method that's called in the Auth0 Callback, so I've jerry-rigged a mock_auth endpoint for my tests to utilize a resource object.
This is my current set up and what I've done to try and replicate the login flow.
#/spec/requests/api/v1/account_spec.rb
RSpec.describe "API V1 Accounts", type: :request do
# Factories.
...
describe "PATCH update" do
subject(:http_request) { patch endpoint, params: { account: account_params, format: :json } }
# set some defaults
let(:id) { account.id }
let(:endpoint) { "/api/v1/accounts/#{id}" }
let(:account_params) { {} }
# Configure subdomain contstraint
within_subdomain :api do
before do |example|
mock_login(resource) unless example.metadata[:skip_signin]
http_request
end
context "when no resource is logged in", :skip_signin do
# This spec passes fine, as it's skipping login.
it_behaves_like "an unauthenticated private api request"
end
context "when there is no record to be found" do
let(:id) { SecureRandom.uuid }
let(:resource) { create(:user) }
it "fails to access a record" do
expect(response).to have_http_status(:not_found)
end
end
xcontext "when the user has access permission" do
end
end
end
end
-
# config/routes.rb
post "/auth/mock/:id", to: "auth#mock", as: :mock_login if Rails.env.test?
-
# auth_controller.rb
def mock
return unless Rails.env.test?
#resource = User.find_by(params[:id]
signin(#resource)
end
def signin(resource)
reset_session
create_session(resource)
after_signin_redirect_for(resource)
end
and I'm using this helper to call it from my request spec
module Helpers
module Auth
def mock_login(resource)
post mock_login_path(resource.id)
end
end
end
RSpec.configure do |config|
config.include Helpers::Auth, type: :request
end
So. By throwing around a bunch of debuggers and binding.pry I can see that my mock_login(resource) is being called successfully and at the end of the signin method, my helper signed_in? is true. Having successfully set a session.
The issue that I'm having now, is that this is not persisting in the feature spec when it's run in the before block, or in the it block.
before do |example|
mock_login(resource) unless example.metadata[:skip_signin] # signed_in? == true!
http_request # signed_in? == nil
end
module API
module V1
class AccountsController < APIController
before_action :authenticate_resource!
# ^ This is where the spec is failing to recognise the signed in resource from the mock_login method.
before_action :set_account
# PATCH /api/v1/accounts/:id
def patch_update
# Cancancan Authorization
authorize! :update, #account
# handle patch
...
end
private
def set_account
binding.pry # We're never making it here.
#account = Account.find_by(id: params[:id])
end
...
end
end
end
def authenticate_resource!
return true if signed_in?
respond_to do |format|
format.json { head(:unauthorized) }
end
end
EDIT: A couple of changes to make it clearer what I'm asking.
When I am running rspec wit pundit version 1.0 on one of my project spec classes I get multiple errors which I haven't seen before. However, when I'm switching to the previous version of pundit (0.3) everything works correctly.
Up to now what I have noticed is that with newer version of pundit #error in create function is not correctly assigned (instead of error class, I get an error message string from the error class).
class ErrorsController < ApplicationController
before_action :set_execution_environment
def authorize!
authorize(#error || #errors)
end
private :authorize!
def create
#error = Error.new(error_params)
authorize!
end
def error_params
params[:error].permit(:message, :submission_id).merge(execution_environment_id: #execution_environment.id)
end
private :error_params
in spec/factories:
FactoryGirl.define do
factory :error, class: Error do
association :execution_environment, factory: :ruby
message "exercise.rb:4:in `<main>': undefined local variable or method `foo' for main:Object (NameError)"
end
end
in spec/controllers/error_controller.rb:
describe 'POST #create' do
context 'with a valid error' do
let(:request) { proc { post :create, execution_environment_id: FactoryGirl.build(:error).execution_environment.id, error: FactoryGirl.attributes_for(:error), format: :json } }
context 'when a hint can be matched' do
let(:hint) { FactoryGirl.build(:ruby_syntax_error).message }
before(:each) do
expect_any_instance_of(Whistleblower).to receive(:generate_hint).and_return(hint)
request.call
end
expect_assigns(execution_environment: :execution_environment)
it 'does not create the error' do
allow_any_instance_of(Whistleblower).to receive(:generate_hint).and_return(hint)
expect { request.call }.not_to change(Error, :count)
end
it 'returns the hint' do
expect(response.body).to eq({hint: hint}.to_json)
end
expect_json
expect_status(200)
end
context 'when no hint can be matched' do
before(:each) do
expect_any_instance_of(Whistleblower).to receive(:generate_hint).and_return(nil)
request.call
end
expect_assigns(execution_environment: :execution_environment)
it 'creates the error' do
allow_any_instance_of(Whistleblower).to receive(:generate_hint)
expect { request.call }.to change(Error, :count).by(1)
end
expect_json
expect_status(201)
end
end
I get the error message
Pundit::NotDefinedError:
unable to find policy Pundit::ErrorPolicy for #<Pundit::Error: {"message"=>"exercise.rb:4:in': undefined
local variable or method foo' for main:Object (NameError)",
"execution_environment_id"=>1}>
since error class is not correctly created. After that every test in error class fail.
My policies:
class AdminOrAuthorPolicy < ApplicationPolicy
[:create?, :index?, :new?].each do |action|
define_method(action) { #user.internal_user? }
end
[:destroy?, :edit?, :show?, :update?].each do |action|
define_method(action) { admin? || author? }
end
end
class ErrorPolicy < AdminOrAuthorPolicy
def author?
#user == #record.execution_environment.author
end
end
I have no such an problem with any other class.
I've been dealing with the same problem for the last half hour, albeit using minitest, and the solution was to run spring stop and then rerun my tests. Hope this helps.
Okay,
this is driving me nuts, since I don't understand the error in this case.
I have the following class defined:
module Admins
class BasePresenter < ::BasePresenter
def render_customer(id:)
return I18n.t('admin.admin') if id.nil?
::Customer.where(id: id).first.try(:name) || I18n.t('admin.deleted')
end
def percent_of(count, total)
((count.to_f / total.to_f) * 100.0).to_i
end
end
end
Which inherits from the BasePresenter below:
class BasePresenter
def initialize(object, template)
#object = object
#template = template
end
def self.presents(name)
define_method(name) do
#object
end
end
def underscored_class
#object.class.name.underscore
end
protected
def h
#template
end
def handle_none(value, html = true)
if value.present?
if block_given?
yield
else
value
end
else
return h.content_tag(:span, '-', class: 'none') if html
'-'
end
end
def current_customer
#current_customer ||= h.current_customer
end
def current_user
#current_user ||= h.current_user
end
end
However when I try to run my specs, I receive the following error from RSpec:
ArgumentError: wrong number of arguments (0 for 2)
./app/presenters/base_presenter.rb:3:in initialize'
./spec/presenters/admins/base_presenter_spec.rb:24:inblock (3
levels) in '
The class is no different from other presents, where the the inheritance works in the exact same way and those tests are passing.
Just the test for this class is failing with this error, and only when testing the method percent_of.
What am I failing to see?
EDIT
This is my RSpec test:
require 'spec_helper'
describe ::Admins::BasePresenter do
describe '#render_customer' do
let(:customer) { Customer.first }
subject { ::Admins::BasePresenter.new(Object.new, ApplicationController.new.view_context) }
it 'returns the I18n translations for (admin) when no customer is set.' do
expect(subject.render_customer(id: nil)).to eql(I18n.t('admin.admin'))
end
it 'returns the proper name when a valid ID is given' do
expect(subject.render_customer(id: customer.id)).to eql(customer.name)
end
it 'returns the I18n translations for (deleted) when an invalid ID is given' do
expect(subject.render_customer(id: -1)).to eql(I18n.t('admin.deleted'))
end
end
describe '#percent_of' do
it 'calculates the percentage correctly' do
expect(subject.percent_of(0, 1)).to eql(0)
expect(subject.percent_of(1, 1)).to eql(100)
expect(subject.percent_of(1, 2)).to eql(50)
expect(subject.percent_of(1, 3)).to eql(33)
end
end
end
Ugh,
I'm an idiot....
The problem was that my subject was defined inside a Describe block for specific tests and the second one did not have any.
Which means our hooks try to create an instance of the class in the outer describe block...
This was the fix:
require 'spec_helper'
describe ::Admins::BasePresenter do
let(:customer) { Customer.first }
subject { ::Admins::BasePresenter.new(Object.new, ApplicationController.new.view_context) }
describe '#render_customer' do
it 'returns the I18n translations for (admin) when no customer is set.' do
expect(subject.render_customer(id: nil)).to eql(I18n.t('admin.admin'))
end
it 'returns the proper name when a valid ID is given' do
expect(subject.render_customer(id: customer.id)).to eql(customer.name)
end
it 'returns the I18n translations for (deleted) when an invalid ID is given' do
expect(subject.render_customer(id: -1)).to eql(I18n.t('admin.deleted'))
end
end
describe '#percent_of' do
it 'calculates the percentage correctly' do
expect(subject.percent_of(0, 1)).to eql(0)
expect(subject.percent_of(1, 1)).to eql(100)
expect(subject.percent_of(1, 2)).to eql(50)
expect(subject.percent_of(1, 3)).to eql(33)
end
end
end
I want to test the controller method, but I can not find the example of testing method with order and search .
This is my controller:
class Admin::HotelsController < Admin::BaseController
helper_method :sort_column, :sort_direction
def index
#hotels = Hotel.search(params[:search], params[:search_column]).order(sort_column + ' ' + sort_direction)
end
def show
#hotel = Hotel.find(params[:id])
end
def update
#hotel = Hotel.find(params[:id])
if #hotel.update_attributes(hotel_params)
redirect_to admin_hotels_path
else
render(:edit)
end
end
private
def hotel_params
params.require(:hotel).permit(:title, :description, :user_id, :avatar, :price, :breakfast, :status, address_attributes: [:state, :country, :city, :street])
end
def sort_column
Hotel.column_names.include?(params[:sort]) ? params[:sort] : 'created_at'
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
end
end
This is test for this controller.
require 'rails_helper'
describe Admin::HotelsController do
login_admin
describe 'GET index' do
it 'render a list of hotels' do
hotel1, hotel2 = create(:hotel), create(:hotel)
get :index
expect(assigns(:hotels)).to match_array([hotel1, hotel2])
end
end
describe 'GET show' do
it 'should show hotel' do
#hotel = create(:hotel)
get :show, { id: #hotel.to_param, template: 'hotels/show' }
expect(response).to render_template :show
end
end
end
I don't know how testing index method. Please help or give me a link with information about this. Thanks!
If it may help you, I personally prefer to have minimals tests for the controllers for various reasons:
1) as I was beginning in rails testing I read many articles saying it's a good idea
2) it allows you to tests in isolation model methods:
describe 'GET index' do
it 'render a list of hotels' do
hotel1, hotel2 = create(:hotel), create(:hotel)
get :index
expect(assigns(:hotels)).to match_array([hotel1, hotel2])
end
end
here your test matches the result of your query on the model. You can split it like this:
describe 'GET index' do
it 'render a list of hotels' do
hotel1, hotel2 = create(:hotel), create(:hotel)
Hotel.should_receive(:search).with(YOUR PARAMS)
get :index
response.response_code.should == 200
end
end
and then test the result of Hotel.search in a model test.
3) it allows you to test the feature and not some random things that are not really relevant:
describe 'GET show' do
it 'should show hotel' do
#hotel = create(:hotel)
get :show, { id: #hotel.to_param, template: 'hotels/show' }
expect(response).to render_template :show
end
end
here "expect(response).to render_template :show" seems like testing that rails rendering system is properly working. I assume that's not what you want to test, you may prefer (that's what I would do):
describe 'GET show' do
it 'should show hotel' do
#hotel = create(:hotel)
Hotel.should_receive(:find).with(YOUR PARAMS)
get :show, { id: #hotel.to_param, template: 'hotels/show' }
response.response_code.should == 200
end
end
and then test what is supposed to appear on the web page with a feature test using something like capybara gem unless you're rendering some json: in this case match the json values in the controller.
By the way: "#hotel = create(:hotel)" the # is not necessary here as you're in the "it". Moreover you can create such entry like this:
context "" do
before(:each) do
#hotel = create(:hotel) # here the # is necessary for the variable to be
end # accessible in the it
it "" do
end
end
or even like this:
context "" do
let(:hotel) { create(:hotel) } # you can call it in the test by using hotel and it
it "" do # will be insert in you db only when it's in the "it"
end # if you want it to be created in the "it" without
end # calling hotel for nothing, use let!
I would suggest using
describe 'GET index' do
let(:hotel1) { create(:hotel) }
let(:hotel2) { create(:hotel) }
it 'render index template' do
get :index
expect(response).to render_template :index
end
it 'render asc ordered hotels' do
get :index
# if you are using json responses
json = JSON.parse(response.body)
expect(json['hotels'].first).to eq hotel1
expect(json['hotels'].last ).to eq hotel2
# or any similar approach to get test the hotels in response
end
it 'render desc ordered hotels' do
get :index, {direction: 'desc'}
# if you are using json responses
json = JSON.parse(response.body)
expect(json['hotels'].first).to eq hotel2
expect(json['hotels'].last ).to eq hotel1
# or any similar approach to get test the hotels in response
end
# you can complete these tests yourself
it 'render hotels sorted with different_column_than_created_at asc'
it 'render hotels sorted with different_column_than_created_at desc'
end
The following is my search method from the controller
#accounts = []
client = GameAccounts::GameAccountsClient.new
if params[:name]
# Try and find a game account by id using the given name
response = client.get_accounts_by_name(params[:name])
if response.is_a?(Net::HTTPSuccess)
account = client.parse_json(response)
unless account.empty?
session[:account] = account
redirect_to game_account_path(params[:name])
end
end
json = client.get_json(params[:limit],params[:offset],params[:name])
#presenter = GameAccountsPresenter.new(json)
end
end
I am trying to run the following test :
require 'spec_helper'
describe GameAccountsController do
describe 'GET search' do
it 'finds a named system account directly' do
get(:search, name: 'test').to be_redirect_to(game_account_path('test'))
end
end
end
I keep getting a
GameAccountsController GET search finds a named system account directly
Failure/Error: get(:search, name: 'test').to be_redirect_to(game_account_path('test'))
NoMethodError:
undefined method `to' for #<ActionController::TestResponse:0x007f8b0beb3e10>
# ./spec/controllers/game_accounts_controller_spec.rb:6:in `block (3 levels) in <top (required)>'
can anyone please let me know what i am doing wrong ?? .. tried doing .should redirect_to and i still get the same error but with method 'should'.
Client code
module GameAccounts
class GameAccountsClient
OAUTH_CONFIG=YAML.load_file(Rails.root.join('config','oauth.yml' ))
def consumer
#consumer ||= OAuth::Consumer.new(OAUTH_CONFIG['oauth_key'],OAUTH_CONFIG['oauth_secret'],access_token_url: OAUTH_CONFIG['oauth_access_token_url'],signature_method: 'HMAC-SHA1')
end
def get_accounts_by_name(name)
query_account(CGI.escape(name))
end
def get_accounts_by_id(account_id)
response = query_account(CGI.escape(account_id))
parse_json(response)
end
def get_json(limit,offset,name)
limit=set_limit(limit)
offset = set_offset(offset)
query = "?name=#{CGI.escape(name)}&limit=#{limit}#{offset}"
response = query_account(query)
parse_json(response)
end
def parse_json(response)
JSON.parse(response.body)
end
end
Rspec's request specs can be tricky. You should call the 'get' method with you params, this will create a response method where your actual response data is being held:
require 'spec_helper'
describe GameAccountsController do
describe 'GET search' do
it 'finds a named system account directly' do
get(:search, name: 'test')
response.should redirect_to(game_account_path('test'))
end
end
end
Hope it helps :)
Edit:
Here's a link to rspec's documentation, version 2.13 supports the expect syntax. The above example could also be written as:
require 'spec_helper'
describe GameAccountsController do
describe 'GET search' do
it 'finds a named system account directly' do
get(:search, name: 'test')
expect(response).to redirect_to(game_account_path('test'))
end
end
end
Edit 2:
Try the following code and test the return values (and classes) of the get_* methods:
def search
#accounts = []
if params[:name]
if get_response.is_a?(Net::HTTPSuccess)
unless get_account.empty?
session[:account] = get_account
redirect_to game_account_path(params[:name])
end
end
json = get_client.get_json(params[:limit],params[:offset],params[:name])
#presenter = GameAccountsPresenter.new(json)
end
end
def get_client
#client ||= GameAccounts::GameAccountsClient.new
end
# Try and find a game account by id using the given name
def get_response
#response ||= client.get_accounts_by_name(params[:name])
end
def get_account
#account ||= get_client.parse_json(get_response)
end