-
-
Save joegaudet/fd011c3cc037e28d4d5ffa964721c9c8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module DependencySupport | |
def self.included(base) | |
base.extend(ClassMethods) | |
end | |
# Substitutes a give dependency with a provided value | |
# will raise an exception if a provided dependency is missing | |
# @param [Sumbol] dependency_name | |
# @param [*] value | |
# @param [DependencySupport] instance of dependant to allow for chaining | |
def substitute_dependency(dependency_name, dependency_value) | |
legal_dep = self.class.dependencies.any? {|dep| dep == dependency_name} | |
unless legal_dep | |
raise "'#{dependency_name}' is not a declared dependency of #{self.class.to_s}" | |
end | |
instance_variable_set("@__#{dependency_name}".to_sym, dependency_value) | |
self | |
end | |
# Substitutes a hash of dependencies with the provided dependencies | |
# will raise an exception if a provided dependency is missing | |
# @param [Object] dependencies | |
# @param [DependencySupport] instance of dependant to allow for chaining | |
def substitute(dependencies = {}) | |
dependencies.each do |dependency_name, dependency_value| | |
substitute_dependency(dependency_name, dependency_value) | |
end | |
self | |
end | |
module ClassMethods | |
def substitute(dependencies = {}) | |
self.new.substitute(dependencies) | |
end | |
# Defines a new dependency | |
# @param [Symbol] dependency_name | |
# @param [Class] dependency_class | |
# @param [Object] options | |
# @!macro [attach] dependency | |
# @return [$2] the $1 $0 | |
def dependency(dependency_name, dependency_class, options = {}) | |
dependencies.push(dependency_name) | |
instance = options[:instance] | |
dependency_variable_name = "@__#{dependency_name}".to_sym | |
dependency_initializer = options[:initializer] | |
if options[:static] && instance.present? | |
raise 'You cannot declare a static dependency and then provide an instance' | |
end | |
if options[:static] && dependency_initializer | |
raise 'You cannot declare a static dependency and then provide a custom initializer' | |
end | |
if dependency_initializer && instance.present? | |
raise 'You cannot declare a dependency_initializer and provide an instance' | |
end | |
if dependency_initializer && dependency_initializer.arity != 1 | |
raise 'A custom initializer may only have 1 argument' | |
end | |
define_method(dependency_name) do | |
ret = instance_variable_get(dependency_variable_name) | |
if ret | |
ret | |
else | |
if dependency_initializer.nil? | |
# We are a standard initializer | |
if instance.present? || | |
dependency_class.ancestors.include?(ActiveRecord::Base) || | |
dependency_class.ancestors.include?(ActiveModel::Model) || | |
dependency_class.ancestors.include?(ActionMailer::Base) || | |
options[:static] | |
dependency_instance = instance || dependency_class | |
else | |
dependency_instance = dependency_class.new | |
end | |
else | |
# User overridden initializer | |
begin | |
dependency_instance = dependency_initializer.(self) | |
rescue NoMethodError => e | |
if e.message.include?(self.to_s) | |
raise "There was an error executing your custom initializer for '#{dependency_name}'" | |
else | |
raise e | |
end | |
end | |
end | |
instance_variable_set(dependency_variable_name, dependency_instance) | |
end | |
end | |
end | |
# @return [Array<Object>] | |
def dependencies | |
@dependencies ||= [] | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DependencySupportTest < MiniTest::Test | |
describe DependencySupport do | |
class ARBaz < ActiveRecord::Base | |
end | |
class Bar | |
end | |
class Qux | |
attr_accessor :bar | |
def initialize(bar) | |
@bar = bar | |
end | |
end | |
class Static | |
def self.instance_bar | |
@instance_bar ||= Bar.new | |
end | |
end | |
class Foo | |
include DependencySupport | |
dependency :bar, Bar | |
dependency :static_bar, Bar, static: true | |
dependency :ar_baz_dao, ARBaz | |
dependency :instance_bar, Bar, instance: Static.instance_bar | |
dependency :qux, Qux, initializer: ->(receiver) {Qux.new(receiver.bar)} | |
end | |
it 'includes DependencySupport' do | |
assert Foo.ancestors.include?(DependencySupport) | |
end | |
it 'creates a dependency accessor for a class by initializing it' do | |
foo = Foo.new | |
bar = foo.bar | |
assert bar.is_a?(Bar) | |
end | |
it 'creates a dependency accessor for a AR Class' do | |
foo = Foo.new | |
baz_dao = foo.ar_baz_dao | |
assert_equal baz_dao, ARBaz | |
end | |
it 'creates a dependency accessor for instance references' do | |
foo = Foo.new | |
assert_equal foo.instance_bar, Static.instance_bar | |
end | |
it 'initializes dependencies using a provided custom initializer' do | |
foo = Foo.new | |
assert_equal foo.qux.bar, foo.bar | |
end | |
it 'creates static accessor when the static option is set' do | |
foo = Foo.new | |
assert_equal foo.static_bar, Bar | |
end | |
it 'allows for the substitution of dependencies' do | |
foo = Foo.new | |
foo.substitute(bar: 1) | |
assert_equal foo.bar, 1 | |
foo.substitute_dependency(:bar, 2) | |
assert_equal foo.bar, 2 | |
end | |
it 'raises an exception when you try and substitute a non existent dependency' do | |
foo = Foo.new | |
err = assert_raises do | |
foo.substitute(joe: 1) | |
end | |
assert_equal err.message, "'joe' is not a declared dependency of DependencySupportTest::Foo" | |
end | |
it 'raises an exception if the initializer fails' do | |
err = assert_raises do | |
class FailingFoo | |
include DependencySupport | |
dependency :qux, Qux, initializer: ->(receiver) { | |
Qux.new(receiver.bazo) | |
} | |
dependency :bar, Bar | |
end | |
FailingFoo.new.qux | |
end | |
assert_equal err.message, "There was an error executing your custom initializer for 'qux'" | |
end | |
it 'raises an exception if the initializer has the wrong number of arguments' do | |
err = assert_raises do | |
class FailingFoo2 | |
include DependencySupport | |
dependency :qux, Qux, initializer: ->() {Qux.new(receiver.bar)} | |
end | |
end | |
assert_equal err.message, 'A custom initializer may only have 1 argument' | |
end | |
it 'raises an exception when you declare both a static dependency and provide an instance' do | |
err = assert_raises do | |
class FailingFoo3 | |
include DependencySupport | |
dependency :qux, Qux, static: true, instance: Static.instance_bar | |
end | |
end | |
assert_equal err.message, 'You cannot declare a static dependency and then provide an instance' | |
end | |
it 'raises an exception when you declare both a static dependency and provide a custom initializer' do | |
err = assert_raises do | |
class FailingFoo4 | |
include DependencySupport | |
dependency :qux, Qux, static: true, initializer: ->(foo) {} | |
end | |
end | |
assert_equal err.message, 'You cannot declare a static dependency and then provide a custom initializer' | |
end | |
it 'raises an exception when you declare both a static dependency and provide a custom initializer' do | |
err = assert_raises do | |
class FailingFoo5 | |
include DependencySupport | |
dependency :qux, Qux, instance: Static.instance_bar, initializer: ->(foo) {} | |
end | |
end | |
assert_equal err.message, 'You cannot declare a dependency_initializer and provide an instance' | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'mocha' | |
module DependencySupport | |
# A helper function to configure a mock dao to return the provided model | |
# and mount it to the provided system under test | |
# | |
# @param [ActiveRecord::Base] model | |
# @return [ActiveRecord::Base] | |
def mock_dao(model, save: false, save_exception: nil) | |
if save && save_exception.present? | |
fail 'You cannot configure a dao to both raise an error and save succesfully' | |
end | |
type = model.class.name.underscore | |
model.id = Faker::Number.rand_in_range(1, 1000) | |
mock_dao = Mocha::Mock.new(model.class.name) | |
mock_dao.stubs(:find).with(model.id).returns(model) | |
mock_dao.stubs(:all).returns([model]) | |
mock_dao.stubs(:first).returns(model) | |
mock_dao.stubs(:last).returns(model) | |
# Mount the dependency on the system under test | |
self.substitute_dependency("#{type}_dao".to_sym, mock_dao) | |
if save | |
model.stubs(:save).returns(true) | |
model.stubs(:save!).returns(true) | |
end | |
if save_exception.present? | |
model.stubs(:save).raises(save_exception) | |
model.stubs(:save!).raises(save_exception) | |
end | |
model | |
end | |
# Builds Flipper in Test Mode (in memory mode) and mounts it to the system under test | |
# | |
# @return [Utils::FlipperUtil] | |
def setup_test_flipper | |
flipper = Utils::FlipperUtil.build_in_memory | |
self.substitute_dependency(:flipper, flipper) | |
flipper | |
end | |
# Builds a mock service and mounts it to the system under test | |
# | |
# @param [Symbol] service_name | |
# @param [Symbol[]] with | |
# @param [Mocha::Mock] returns | |
# @param [Symbol] method defaults to call | |
def mock_service(service_name, with: [], returns: nil, method: :call, times: 1) | |
mock_service = Mocha::Mock.new(service_name) | |
mock_service.expects(method).with(*with).returns(returns).times(times) | |
self.substitute_dependency(service_name, mock_service) | |
mock_service | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment