In my Order model I include a PORO class "ShipmentHandler". This PORO is located like this: app/models/order/shipment_handler.rb
I invoke this in my Order model like so:
def assign_shipments
ShipmentHandler.new(self).assign_shipments
end
My PORO looks like:
class Order
class ShipmentHandler
def initialize(order)
#set_some_variables
end
def some_methods
end
end
end
I'm trying to create spec to test the methods in my ShipmentHandler class. I'm not sure how to do this as I keep getting errors like uninitialized constant ShipmentHandler
I've tried to add it to my order_spec.rb like so:
describe Order do
describe Order::ShipmentHandler do
end
end
and:
describe Order do
describe ShipmentHandler do
end
end
Neither work. I've also tried creating a spec in spec/models/order/shipment_handler_spec.rb
This also failed.
The following way of writing specs worked for me when I made some assumptions on what your Order class looks like with the nested ShipmentHandler class:
class Order
def assign_shipments
ShipmentHandler.new(self).assign_shipments
end
class ShipmentHandler
def initialize(order)
#order = order
end
def some_methods
end
end
end
RSpec.describe Order do
it { is_expected.to be_a Order }
end
# Method 1
RSpec.describe Order::ShipmentHandler do
subject(:shipment_handler) { described_class.new(Order.new) }
it { is_expected.to be_a Order::ShipmentHandler }
end
# Method 2
class Order
RSpec.describe ShipmentHandler do
subject(:shipment_handler) { described_class.new(Order.new) }
it { is_expected.to be_a Order::ShipmentHandler }
end
end
Related
How can I test if MyModel.all is called ?
The following code doesn't work as I expect it:
class MyClass
def call
cached_records
end
private
def cached_records
Rails.cache.fetch(self.class.name.underscore) do
MyModel.all
end
end
end
RSpec.describe MyClass do
subject { MyClass.new }
describe '#call' do
it 'only queries the database the first time' do
expect(MyModel).to receive(:all).once
MyClass.call
MyClass.call
end
end
end
Apparently I found the answer by tweaking the expectation.
expect(MyModel).to receive(:all).once.and_call_original
Why do I need to specify to call the original method ?
My controllers inherit actions from ApplicationController. My goal is to test the behaviour of any controller that inherits from ApplicationController. I created RandomController in my specs in order to achieve that goal.
Here is my spec so far
require 'rails_helper'
RSpec.configure do |c|
c.infer_base_class_for_anonymous_controllers = false
end
class RandomController < ApplicationController; end
class Random < ApplicationRecord; end
RSpec.describe RandomController, type: :controller do
controller {}
describe '.index' do
context 'when no record exists' do
before { get :index }
specify { should respond_with(200) }
end
end
end
Here is application_controller
class ApplicationController
def index
binding.pry
end
end
The issue is that when the index method runs, self.class returns #<Class:0x00007f8c33b56fc8> instead of RandomController. Is it possible to have my anonymous controller be an instance of a given controller (declared within the specs) ?
According to the docs you can specify the base class for the anonymous controller:
To specify a different base class you can pass the class explicitly to the
controller method:
controller(BaseController)
https://relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller
Thus you can probably call:
controller(RandomController)
in your specs
Consider using shared_context instead of creating a RandomController to test shared code:
shared_context 'an application controller' do
describe '#index' do
context 'when no record exists' do
before { get :index }
expect(response).to have_http_status(:ok)
end
end
end
You would typically put this file under /spec/support. Example:
/spec/support/shared_contexts_for_application_controllers.rb
Then, in each controller that inherits from ApplicationController:
describe RandomController do
include_context 'an application controller'
end
I have the following in my UserTrainingController
def create
#user_training_resource = UserTrainingResource::Create.call(user_training_resource_params)
respond_with(#user_training_resource)
end
Then in Operations > Training Resource > Create
class UserTrainingResource
class Create < Operation
def call(params)
user_training_resource = UserTrainingResource.new(params)
ResourceMailer.requested(user_training_resource).deliver_later if user_training_resource.save
user_training_resource
end
end
end
Then in my test I have
require 'rails_helper'
RSpec.describe UserTrainingResource::Create do
let(:params) { attributes_for(:user_training_resource) }
describe '#call' do
it 'saves the request as pending' do
ut = UserTrainingResource::Create.call(params)
expect(ut.persisted?).to eq(true)
end
it 'queues a mailer' do
expect(ResourceMailer).to send_mail(:requested)
UserTrainingResource::Create.call(params)
end
end
end
The test gives me
NameError: uninitialized constant UserTrainingResource::Create
I've no idea what the issue is. Is it not hitting my operations correctly?
Rails expects that class to be defined in app/models/user_training_resource/create.rb, or you could add the dir to config.autoload_paths.
https://blog.bigbinary.com/2015/11/05/how-constant-lookup-happens-in-rails.html
You should use module, like this:
module UserTrainingResource
class Create < Operation
...
end
end
Or the shorthand way, like this:
class UserTrainingResource::Create < Operation
end
The file structure must match the name of the class. Rename the directory to app/models/user_training_resource/create.rb or lib/user_training_resource/create.rb
Im testing a Module that can be included in a Controller.
Right now the code looks like that:
class GithubAuthorizationController < ApplicationController
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
describe GithubAuthorizationController do
before(:all) do
#page_content = "lorem_ipsum"
end
...........
As you can see I basically create a Test-Controller before the tests are run. Now I would like to add the module and index method in the before(:all)-block. I tried:
class GithubAuthorizationController < ApplicationController
end
describe GithubAuthorizationController do
before(:all) do
#page_content = "lorem_ipsum"
class < #controller
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
end
...........
As I can see in debugger in the before(:all) block the #controller is defined as <GithubAuthorizationController .... So It is a instance. There Is also no error when running the code, but the tests fails, because of The action 'index' could not be found ...
What do I wrong? How can I move the code to the before(:all) block? Thanks
The way to do this in rspec is with a controller block:
describe GithubAuthorizationController, type: :controller do
context "with module" do
controller do
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
# within this block the controller will be the anonymous controller class created above
end
end
If you have set infer_base_class_for_anonymous_controllers to false (this is not the default) then you need to do controller(GithubAuthorizationController) or you'll inherit directly from ApplicationController
Your issue could be down to a missing route - the controller helper creates some routes for you (for the default index, show etc.) actions. You can add extra ones in an example with
it "does something with a custom route" do
routes.draw { get "custom" => "github_authorization#custom" }
get :custom
...
end
In all my ruby on rails app, I try to not use the database in controllers, since they should be independent from persistence classes. I used mocking instead.
Here is the example for rspec and rspec-mock:
class CouponsController < ApplicationController
def index
#coupons = Coupon.all
end
end
require 'spec_helper'
describe CouponsController do
let(:all_coupons) { mock }
it 'should return all coupons' do
Coupon.should_receive(:all).and_return(all_coupons)
get :index
assigns(:coupons).should == all_coupons
response.should be_success
end
end
But what if controller contains more complex scopes, like:
class CouponsController < ApplicationController
def index
#coupons = Coupon.unredeemed.by_shop(shop).by_country(country)
end
end
Do you know any good approach for testing simillar scopes chains?
I think that the following test does not look really good:
require 'spec_helper'
describe CouponsController do
it 'should return all coupons' do
Coupon.should_receive(:unredeemed).and_return(result = mock)
result.should_receive(:by_shop).with(shop).and_return(result)
result.should_receive(:by_country).with(country).and_return(result)
get :index
assigns(:coupons).should == result
response.should be_success
end
end
You could use stub_chain method for that.
Something like:
Coupon.stub_chain(:unredeemed, :by_shop, :by_country).and_return(result)
Just an example.
With rspec > 3 use this syntax:
expect(Converter).to receive_message_chain("new.update_value").with('test').with(no_args)
instead of stub_chain.
Read more about message chains in the documenation.