Skip to content

Instantly share code, notes, and snippets.

@weedySeaDragon
Last active February 11, 2022 00:23
Show Gist options
  • Save weedySeaDragon/af839c94ee7de016b02847b1dc1f4795 to your computer and use it in GitHub Desktop.
Save weedySeaDragon/af839c94ee7de016b02847b1dc1f4795 to your computer and use it in GitHub Desktop.
Using shared_context and shared_examples in Rspec with optional arguments. Answer to SO 24872199
#-------------------------- #
#
# @author Ashley Engelund ([email protected] weedySeaDragon @ github)
# @date 1/13/17
#
# @desc This is my answer to the Stack Overflow (SO) question:
# How can I use a default method to create something in a shared_example,
# sometimes overriding it with some other method and parameters for it?
#
# @see http://stackoverflow.com/questions/24872199/rspec-with-reusable-method-for-shared-examples-context
#
# The example in the SO question refers to the classes as
# 'associations'. That's why those terms are used in the code.
# The classes in this example aren't "associations" in any way.
#
#
# Note that I don't think doing that is a good idea. It's a clue that
# your specifications need to be refactored / redesigned.
# It also makes your tests error prone (what if you mistype a method name?)
#
# But I did this as an exersize to show that this is all just Ruby.
# You can check to see if a parameter is nil and work with it just
# as you can in 'regular' Ruby code.
#
# Repetitive code and 'puts' are used to make the results clear
# and things explicit so that novices can understand it.
# No error checking is done; that's not the point of this example.
#
#
# @usage Run this with 'rspec'
# It will use puts statements to show which objects were created and
# used in the shared_examples.
#
#--------------------------
#---------------------------------------------------
# Classes used in the RSpec code below.
#
# The names of the classes are from the StackOverflow (SO) question.
#
# These have different numbers of attributes and so the methods used
# to instantiate (create) them require different numbers of arguments.
# I did this to make it clear that number of arguments can vary.
#
# The methods that create new objects set the attributes such that it's
# clear that we have used the arguments passed to the methods, and so that
# it's clear which method was used to create the object.
#
class AddLink
attr_accessor :user_symbol, :arg1
def initialize(user_symbol = nil, param1 = nil)
@user_symbol = user_symbol
@arg1 = param1
end
def self.create_and_repeat_arg1(user_symbol, param1)
new_link = new
new_link.user_symbol = "created with method '#{__method__}' #{user_symbol}"
new_link.arg1 = "#{param1}-#{param1}"
new_link
end
end
class DeleteLink
attr_accessor :user_symbol, :arg1, :arg2
def self.create_and_repeat_args(user_symbol, param1, param2)
new_link =new
new_link.user_symbol = "created with method '#{__method__}' #{user_symbol}"
new_link.arg1 = "#{param1}-#{param1}"
new_link.arg2 = "#{param2}-#{param2}"
new_link
end
end
#---------------------------------------------------
# Shared context and example
#
RSpec.shared_context 'assoc created with default method, parameters' do |default_class, default_method, parameters|
# you can use 'default_assoc' anywhere you've included this shared context
let(:default_assoc) {
default_class.send(default_method, *parameters)
}
end
RSpec.shared_examples 'update association service' do |optional_class, optional_method, parameters|
# The name of this is from the SO question
# warning: no error checking is done to see if default_assoc.nil? or even defined,
# or if optional_class.respond_to? optional_method
let(:assoc) {
if optional_class.nil? || optional_method.nil?
default_assoc
else
optional_class.send(optional_method, *parameters)
end
}
it 'used this association:' do
puts_example_desc_and_association(assoc)
end
end
#---------------------------------------------------
# helper methods just to show things
def puts_example_desc_and_association(association)
puts "\n #{@__inspect_output}\n association = #{association.inspect}\n"
end
def puts_spec_and_association(association)
puts "\n- - -\n#{self.class.description}"
puts_example_desc_and_association(association) #"\n #{@__inspect_output}\n association = #{default_assoc.inspect}"
end
#---------------------------------------------------
RSpec.describe 'Links' do
describe 'AddLink (use the default assocation, which = use the default method for creating it)' do
# The shared_context 'assoc created with default method, parameters'
# is how we set the default way to create an association within this example group.
# Instead of trying to memoize the class, method, and parameter, we
# go ahead and memoize the assocation created and call it 'default_assoc'.
#
# In other words:
# In this example group, the default method (and class) to create an association
# is DeleteLink.create_and_repeat_args('userSymbol', 'firstArg', 'secondArg', 'thirdArg')
#
# The result of this is stored in 'default_assoc' (default association).
# Any example that comes after this can refer to (use)' default_assoc'
include_context 'assoc created with default method, parameters', AddLink, :create_and_repeat_arg1, ['userSymbol', 'firstArg']
# This example shows what 'default_assoc' is.
# If this is the first time 'default_assoc' is used, then it will
# execute the let(:default_assoc) block that is in the
# shared_context 'assoc created with default method, parameters' that is
# included above.
it 'created default association' do
puts_spec_and_association(default_assoc)
end
# Since no arguments are passed to this shared example,
# it will use 'default_assoc'. (See the code in the shared_example.)
it_behaves_like 'update association service'
end
describe 'DeleteLink (override default association)' do
# This is how 'default_assoc' is created:
include_context 'assoc created with default method, parameters', DeleteLink, :create_and_repeat_args, ['userSymbol', 'firstArg', 'secondArg']
# Show what 'default_assoc' is:
it 'created default association' do
puts_spec_and_association(default_assoc)
end
# This is how you effectively override the default method to create an 'assocation'
# (or whatever class you want).
#
# In this example group, the default method (and class) to create an association
# is DeleteLink.create_and_repeat_args('userSymbol', 'firstArg', 'secondArg')
#
# The result of this is stored in 'default_assoc'
#
# Instead of using 'default_assoc', this will create an association
# with the DeleteLink class, 'new' method, and an empty argument list
it_behaves_like 'update association service', DeleteLink, :new, []
end
describe "BlorfLink (use 'AddLink.new('userSymbol', 'firstArg')' to create the default association)" do
# This just shows that you can use the 'new' method to
# create the default association ('default_assoc').
include_context 'assoc created with default method, parameters', AddLink, :new, ['userSymbol', 'firstArg']
it 'created default association' do
puts_spec_and_association(default_assoc)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment