I'm trying to write a rspec test for a mixin class. I have the following.
module one
module two
def method
method_details = super
if method_details.a && method_details.b
something
elsif method_details.b
another thing
else
last thing
end
end
end
end
Now I have mocked the "method" object that will be passed to the class.
But I'm struggling to access the super method.
I did,
let(:dummy_class) { Class.new { include one::two } }
How to pass the mocked method object to this dummy class?
How do I go about testing this? New to ruby, can someone show me a direction with this.
Thanks in advance.
UPDATE:
I tried,
let(:dummy_class) {
Class.new { |d|
include one::two
d.method = method_details
}
}
let (:method_details){
'different attributes'
}
still doesn't work. I get undefined local variable or method method_details for #<Class:0x007fc9a49cee18>
I personally test mixing with the class. Because the mixing (module) itself has no meaning unless its attached to a class/object.
Ex:
module SayName
def say_name
p 'say name'
end
end
class User
include SayName
end
So I believe you should test your module with attached to the relevant class / object.
How ever this is a different perspective on testing mixings
HTH
I think that in your specs, you'll need to explicitly provide a super class definition for when super is called in #method as "you can't mock super and you shouldn't".
I've attempted to spec out all three of your scenarios with the following minor changes:
Changed your example code slightly to become valid Ruby
Changed #method to #the_method so it doesn't conflict with Object#method
Used OpenStruct to represent the object that super returns, because all I know is that it's an object that has methods #a and #b. You can change that out as appropriate for your real specs
Copy and paste the class and specs below into a file and give them a try:
module One
module Two
def the_method
method_details = super
if method_details.a && method_details.b
'something'
elsif method_details.b
'another thing'
else
'last thing'
end
end
end
end
RSpec.describe One::Two do
require 'ostruct'
let(:one_twoable) { Class.new(super_class) { include One::Two }.new }
describe '#the_method' do
let(:the_method) { one_twoable.the_method }
context 'when method_details#a && method_details#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: true, b: true)
end
end
end
it 'is "something"' do
expect(the_method).to eq('something')
end
end
context 'when just method#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: true)
end
end
end
it 'is "another thing"' do
expect(the_method).to eq('another thing')
end
end
context 'when neither of the above' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: false)
end
end
end
it 'is "last thing"' do
expect(the_method).to eq('last thing')
end
end
end
end
Related
I'm trying to scrub out mailto links while allowing others using Rails::HTML Scrubbers.
See: https://github.com/rails/rails-html-sanitizer
I have a Loofah fragment like this:
Loofah.fragment('tis but a scratch')
And my Scrubber is like so:
class TargetedHtmlScrubber < Rails::Html::TargetScrubber
def initialize
super
self.tags = %w[a]
end
def allowed_node?(node)
...
end
def scrub_attribute?(name)
...
end
end
But when I run scrub! in my specs, I can't see any of the methods allowed_node? or scrub_attribute being called as per the documentation.
My spec is like this. It scrubs everything..,.
require 'rails_helper'
RSpec.describe TargetedHtmlScrubber do
describe 'targeting html tags' do
it 'ignores <a>' do
fragment = html_fragment('Greg! the stop sign!!')
expect(
fragment.scrub!(subject).to_s
).to eq 'Greg! the stop sign!!'
end
it 'targets <a href=mailto:>' do
fragment = html_fragment('tis but a scratch')
expect(
fragment.scrub!(subject).to_s
).to eq 'tis but a scratch'
end
end
end
I am expecting to implement something in one of the two methods in my class.
Ok, I worked it out. To call those methods, you need to set the tags and attributes arrays:
def initialize
super
self.tags = %w[a]
self.attributes = %w[href]
end
It will then call allowed_node?(node) and scrub_attribute?(name). So in this case, I only need the tags.
To scrub out mailto: links:
class TargetedHtmlScrubber < Rails::Html::TargetScrubber
def initialize
super
self.tags = %w[a]
end
def allowed_node?(node)
href(node) !~ /^mailto:/
end
private
def href(node)
node.try(:attributes)['href'].try(:value)
end
end
Can somebody help me with rspec testing method call in Service Object?
class UserEntitiesController < ApplicationController
def create
#result = UserEntities::Create.new(params).call
return render '/422.json.jbuilder', status: :unprocessable_entity unless #result
end
here is the service objects:
module UserEntities
class Create
attr_accessor :params
def initialize(params)
#params = params
end
def call
#user_entity = UserEntity.new(user_entity_params)
set_time
if #user_entity.save
#user_entity
else
error_result
end
end
private
def error_result
false
end
def user_entity_params
#params.require(:user_entity).permit(:information,
:destroy_option,
:reviews)
end
def set_time
if #params[:available_days].present?
#user_entity.termination = Time.now + #params[:available_days].days
end
end
end
end
I tried to find information how to do this, but there are not so many.
Also i read some
You can certainly write a unit test to test the Service Object standalone
In this case, create a file spec/services/user_entities/create_spec.rb
describe UserEntities::Create do
let(:params) { #values go here }
context ".call" do
it "create users" do
UserEntities::Create.new(params).call
# more test code
end
# more tests
end
end
Later in the controller tests, if you are planning to write such, you do not need to test UserEntities::Create instead you can just mock the service object to return the desired result
describe UserEntitiesController do
before do
# to mock service object in controller test
allow(UserEntities::Create).to receive(:new)
.and_return(double(:UserEntities, call: "Some Value"))
end
# controller tests go here
end
As a supplement to #bibin answer.
If you want to mock some instance's method renturn:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_return("some value")
if you want to raise a eror:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_raise("boom")
I am having a hard time figuring out how to test this helper because current_group is not defined in my test case and I am not sure how I can stub it.
module Admin
module EmployeesHelper
def upload_access
policy(current_group).can_bulk_create_employees?
end
def dashboard_params
download_employee_url = upload_access ?
download_admin_group_employees_import_csv_index_path(current_group) : nil
upload_employee_url = upload_access ?
admin_group_employees_import_csv_index_path(current_group) : nil
make_hash(upload_employee_url, download_employee_url)
end
private
def make_hash(upload_url, download_url)
{
employees: #employees,
addEmployeeUrl: new_admin_group_employee_path(current_group),
terminated_employees: #terminated_employees,
new_employees: #new_employees,
test_employees: #test_employees,
group_id: current_group.id,
downloadEmployeeUrl: download_url,
uploadEmployeeUrl: upload_url
}
end
end
end
Here's what my test looks like, but it fails because current_group is not defined.
require 'rails_helper'
describe Admin::EmployeesHelper do
let!(:group) { create(:group) }
before do
# This stub doesn't work because helper doesn't implement current_group
allow(helper).to receive(:current_group).and_return(group)
end
it 'returns correct dashboard props' do
allow(helper).to receive(:upload_access).and_return(true)
props = helper.dashboard_params
expect(props).values_at(:downloadEmployeeUrl, :uploadEmployeeUrl).should_not include(nil)
end
end
If the problem is only with current_group method, you could include this helper in a dummy class. Something like this:
let(:klass) {
Class.new do
include Admin::EmployeesHelper
def current_group
nil
end
end
}
let(:instance) { klass.new }
before do
allow(instance).to receive(:current_group).and_return('whatever') # test-specific group
end
it 'returns correct dashboard props' do
allow(instance).to receive(:upload_access).and_return(true)
props = instance.dashboard_params
expect(props).values_at(:downloadEmployeeUrl, :uploadEmployeeUrl).should_not include(nil)
end
Although I foresee that you'll have to include something for the url helpers too. And set up those instance vars. All in all, it probably isn't worth the trouble.
I ended up refactoring the helper to have current_group as an argument instead.
I try to set some options in a rails helper, but it seems it's got overridden every time.
module MetaTagHelper
def meta_options
#meta_options ||= {}
end
def add_meta_tag_options(opt)
meta_options.deep_merge(opt)
end
end
Here is the test
require 'rspec'
describe MetaTagHelper do
it 'options should be set' do
option = {region: "1"}
option2 = {country: "AT"}
helper.add_meta_tag_options(option)
helper.add_meta_tag_options(option2).should eql(option.merge option2)
end
end
expected: {:region=>"1", :country=>"AT"}
got: {:country=>"AT"}
How can I get the spec pass?
Try using deep_merge!:
def add_meta_tag_options(opt)
meta_options.deep_merge!(opt)
end
Given a controller like this where it creates several instance variables for use by the view, would you generally test that each of those get set properly? It seems like you would want to, but it also seems a little it could be a bit tricky. What's the right approach?
class StaffsController < ApplicationController
def index
set_index_vars
#all_staff = Staff.find_staff_for_business_all_inclusive(current_business_id)
respond_to do |format|
format.html { render :action => "index", :locals => { :all_staff => #all_staff, :all_services => #all_services, :new_vacation => #new_vacation } }
end
end
def set_index_vars
#days_of_week = days_of_week
#first_day_of_week = DefaultsConfig.first_day_of_week
#all_services = Service.services_for_business(current_business_id)
#new_vacation = StaffVacation.new
#has_hit_staff_limit = current_user_plan.has_hit_staff_limit?
end
end
The code is also posted at https://gist.github.com/1018190
If you're going to write a controller spec, then yes, by all means test that the instance variables are assigned. Much of the 'trickiness' can come from dependencies on other models/libraries, so stub out those method calls:
# air code
Staff.stub(:find_staff_for_business_all_inclusive) {array_of_staff}
controller.stub(:days_of_week) {['Monday','Tuesday',....etc...]}
DefaultsConfig.stub(:first_day_of_week) {"Monday"}
Service.stub(:services_for_business).with(some_value_for_the_current_business_id).\
and_return(some_relevant_value)
StaffVacation.stub(:new) {something_meaningful}
controller.stub_chain(:current_user_plan,:has_hit_staff_limit?) {false}
get :index
assigns(:days_of_week).should == ['Monday','Tuesday',....etc...]
# ...etc...
I would split it up as follows: test that the index calls the correct method. Then test whether the method works.
So something like
describe StaffsController do
describe "GET #index" do
it "calls set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
# and your usual tests ...
end
describe "#set_index_vars" do
before(:each) do
# stub out the code not from this controller
controller.stub_chain(:current_user_plan, :has_hit_staff_limit?).and_return(false)
.. etc ..
controller.set_index_vars
end
it { assigns(:days_of_week).should == controller.days_of_week }
it { assigns(:has_hit_staff_limit).should be_false
# etc ..
end
end
Hope this helps.
So long as you have good coverage around your method, you can test that your method is being called at the right times, with the right values etc. Something like:
describe StaffsController do
describe "GET #index" do
it "should call set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
end
describe "#set_index_vars" do
it "should assign instance variables with correct values" do
# or wtv this is supposed to do
get :index
assigns(:days_of_week).should == controller.days_of_week
# etc ..
end
end
end