require 'assert' require 'mr/factory/config' require 'mr/factory/model_factory' require 'mr/factory/record_factory' require 'mr/fake_record' require 'mr/model' module MR::Factory::Config class UnitTests < Assert::Context desc "MR::Factory::Config" setup do @config_class = Class.new do include MR::Factory::Config end end subject{ @config_class } end class InitTests < UnitTests desc "when init" setup do @object_class = Factory.boolean ? FakeTwoRecord : TestReadModel @config = @config_class.new(@object_class) end subject{ @config } should have_readers :object_class should have_imeths :apply_args, :set_default_args should "know its object class" do assert_equal @object_class, subject.object_class end should "know its object builder class" do exp = MR::Factory::Config::ObjectBuilder assert_equal exp, subject.instance_eval{ object_builder_class } end should "know how to apply args" do object = @object_class.new call_order = 0 apply_hash_called_at = nil apply_hash_called_with = nil Assert.stub(ObjectBuilder, :apply_hash) do |*args| apply_hash_called_at = call_order += 1 apply_hash_called_with = args end apply_proc_called_at = nil apply_proc_called_with = nil Assert.stub(ObjectBuilder, :apply_proc) do |*args, &block| apply_proc_called_at = call_order += 1 apply_proc_called_with = args + [block] end args_hash = { :name => Factory.string } subject.apply_args(object, args_hash) assert_equal 1, apply_hash_called_at exp = [object, subject, args_hash] assert_equal exp, apply_hash_called_with assert_nil apply_proc_called_at assert_nil apply_proc_called_with call_order = 0 apply_hash_called_at = nil apply_hash_called_with = nil # should use a default args proc if one is set default_args_proc = proc{ set :name, Factory.string } subject.set_default_args(&default_args_proc) subject.apply_args(object, args_hash) assert_equal 1, apply_proc_called_at exp = [object, subject, default_args_proc] assert_equal exp, apply_proc_called_with assert_equal 2, apply_hash_called_at exp = [object, subject, args_hash] assert_equal exp, apply_hash_called_with end end class ObjectBuilderTests < UnitTests desc "ObjectBuilder" setup do @object_class = [FakeTwoRecord, FakeTwoModel].sample @object = @object_class.new @config = @config_class.new(@object_class) @object_builder_class = ObjectBuilder end subject{ @object_builder_class } should have_imeths :apply_hash, :apply_proc should "know how to apply a hash and proc" do set_names_and_values = [] Assert.stub(@object_builder_class, :new).with(@object, @config) do |*args| object_builder = Assert.stub_send(@object_builder_class, :new, @object, @config) Assert.stub(object_builder, :set) do |key, value| set_names_and_values << [key, value] end object_builder end hash = Factory.integer(3).times.inject({}) do |h, _| h.merge!(Factory.string => Factory.string) end subject.apply_hash(@object, @config, hash) exp = hash.map{ |(k, v)| [k, v] }.sort assert_equal exp, set_names_and_values.sort set_names_and_values = [] subject.apply_proc(@object, @config) do hash.each{ |key, value| set(key, value) } end exp = hash.map{ |(k, v)| [k, v] }.sort assert_equal exp, set_names_and_values.sort end end class ObjectBuilderInitTests < ObjectBuilderTests desc "when init" setup do @object_builder = @object_builder_class.new(@object, @config) end subject{ @object_builder } should have_imeths :set should "know how to set an attribute on its object" do @object.name = Factory.string value = [Factory.string, nil].sample subject.set(:name, value) assert_equal value, @object.name end end class WithAssociationsConfigTests < Assert::Context desc "MR::Factory::WithAssociationsConfig" setup do @factory_class, @build_factory_proc = if Factory.boolean [ MR::Factory::RecordFactory, proc{ |rc| MR::Factory::RecordFactory.new(rc) } ] else [ MR::Factory::ModelFactory, proc{ |rc| MR::Factory::ModelFactory.new(rc.model_class, rc) } ] end build_factory_proc = @build_factory_proc @config_class = Class.new do include MR::Factory::WithAssociationsConfig define_method(:build_factory_for_record_class) do |record_class| build_factory_proc.call(record_class) end private :build_factory_for_record_class end end subject{ @config_class } should "be a factory config" do assert_includes MR::Factory::Config, subject end end class WithAssociationsConfigInitTests < WithAssociationsConfigTests desc "when init" setup do @record_class = FakeTwoRecord @config = @config_class.new(@record_class) end subject{ @config } should have_imeths :record_class should have_imeths :force_in_stack_association_names, :force_in_stack_association? should have_imeths :default_factories, :association_factories should have_imeths :factories_for, :factory_for, :factory_config_for should have_imeths :record_classes_for, :build_associated_record should have_imeths :add_association_factory should "know its record class" do assert_equal @record_class, subject.record_class end should "know its force walk association names" do assert_equal [], subject.force_in_stack_association_names end should "know if it should force walk an association" do association_name = Factory.string assert_false subject.force_in_stack_association?(association_name) subject.force_in_stack_association_names << association_name assert_true subject.force_in_stack_association?(association_name) end should "know its object builder class" do exp = MR::Factory::WithAssociationsConfig::ObjectBuilder assert_equal exp, subject.instance_eval{ object_builder_class } end should "know its default factories" do assert_equal({}, subject.default_factories) factory_built_with = [] Assert.stub(@factory_class, :new) do |*args| factory_built_with = args Assert.stub_send(@factory_class, :new, *args) end factory = subject.default_factories[FakeOneRecord] assert_instance_of @factory_class, factory if @factory_class == MR::Factory::RecordFactory assert_equal [FakeOneRecord], factory_built_with else assert_equal [FakeOneModel, FakeOneRecord], factory_built_with end # should cache the factories it builds assert_equal 1, subject.default_factories.size assert_same factory, subject.default_factories[FakeOneRecord] # should raise a no record class error if given a `nil` record class assert_raises(MR::Factory::NoRecordClassError) do subject.default_factories[nil] end end should "know its association factories" do assert_equal({}, subject.association_factories) end should "know how to add an association factory" do fake_one_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(:fake_one, fake_one_factory) assert_true subject.default_factories.key?(FakeOneRecord) assert_equal fake_one_factory, subject.default_factories[FakeOneRecord] key = @config_class::AssociationFactoriesKey.new(:fake_one, FakeOneRecord) assert_true subject.association_factories.key?(key) assert_equal [fake_one_factory], subject.association_factories[key] end should "configure an association to be force walked when adding a factory for it" do fake_one_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(:fake_one, fake_one_factory) assert_not_includes :fake_one, subject.force_in_stack_association_names subject.add_association_factory(:fake_one, fake_one_factory, :force_in_stack => true) assert_includes :fake_one, subject.force_in_stack_association_names end should "not duplicate force walk associations when adding a factory for it" do fake_one_factory = @build_factory_proc.call(FakeOneRecord) assert_empty subject.force_in_stack_association_names subject.add_association_factory(:fake_one, fake_one_factory, :force_in_stack => true) assert_equal 1, subject.force_in_stack_association_names.size subject.add_association_factory(:fake_one, fake_one_factory, :force_in_stack => true) assert_equal 1, subject.force_in_stack_association_names.size end should "not change a default once its set when adding an association factory" do fake_one_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(:fake_one, fake_one_factory) other_fake_one_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(:fake_one, other_fake_one_factory) default_factory = subject.default_factories[FakeOneRecord] assert_not_equal other_fake_one_factory, default_factory assert_equal fake_one_factory, default_factory end should "store all factories added for an association" do fake_one_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(:fake_one, fake_one_factory) other_fake_one_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(:fake_one, other_fake_one_factory) key = @config_class::AssociationFactoriesKey.new(:fake_one, FakeOneRecord) assert_equal 2, subject.association_factories[key].size assert_includes fake_one_factory, subject.association_factories[key] assert_includes other_fake_one_factory, subject.association_factories[key] end should "know how to add a factory for a polymorphic association" do fake_one_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(:fake_poly, fake_one_factory) assert_true subject.default_factories.key?(FakeOneRecord) assert_equal fake_one_factory, subject.default_factories[FakeOneRecord] # should add a `nil` record class association factory key key = @config_class::AssociationFactoriesKey.new(:fake_poly) assert_true subject.association_factories.key?(key) assert_equal [fake_one_factory], subject.association_factories[key] key = @config_class::AssociationFactoriesKey.new(:fake_poly, FakeOneRecord) assert_true subject.association_factories.key?(key) assert_equal [fake_one_factory], subject.association_factories[key] end should "error if adding a factory for an unknown association" do factory = @build_factory_proc.call(FakeOneRecord) assert_raises(MR::Factory::NoAssociationError) do subject.add_association_factory(Factory.string, factory) end end should "know how to get factories for an association and record class" do association_name = Factory.boolean ? :fake_one : :fake_poly factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(association_name, factory) assert_equal [factory], subject.factories_for(association_name, FakeOneRecord) other_factory = @build_factory_proc.call(FakeOneRecord) subject.add_association_factory(association_name, other_factory) factories = subject.factories_for(association_name, FakeOneRecord) assert_equal 2, factories.size assert_includes factory, factories assert_includes other_factory, factories end should "return a default factory for an association if one isn't configured" do association_name = Factory.boolean ? :fake_one : :fake_poly exp = [subject.default_factories[FakeOneRecord]] assert_equal exp, subject.factories_for(association_name, FakeOneRecord) end should "know how to get a factory for an association and record class" do association_name = Factory.boolean ? :fake_one : :fake_poly factories = Factory.integer(3).times.map do @build_factory_proc.call(FakeOneRecord) end Assert.stub(subject, :factories_for).with( association_name, FakeOneRecord ){ factories } factory = factories.sample Assert.stub(factories, :sample){ factory } assert_same factory, subject.factory_for(association_name, FakeOneRecord) end should "know how to get record classes for an association" do association_name = Factory.boolean ? :fake_one : :fake_poly factories = [ @build_factory_proc.call(FakeOneRecord), @build_factory_proc.call(FakeThreeRecord) ] Assert.stub(subject, :factories_for).with(association_name, nil){ factories } exp = factories.map(&:record_class) assert_equal exp, subject.record_classes_for(association_name) end end class WithAssociationsConfigObjectBuilderTests < WithAssociationsConfigTests desc "ObjectBuilder" setup do @object_builder_class = MR::Factory::WithAssociationsConfig::ObjectBuilder end subject{ @object_builder_class } should "be a factory config object builder" do assert subject < MR::Factory::Config::ObjectBuilder end end class RecordObjectBuilderInitTests < WithAssociationsConfigObjectBuilderTests desc "when init with a record" setup do @config_class.class_eval do define_method(:ar_association_for) do |record, name| if (reflection = record.class.reflect_on_association(name)) record.association(reflection.name) end end define_method(:build_factory_for_record_class) do |record_class| MR::Factory::RecordFactory.new(record_class) end end @record_class = FakeTwoRecord @record = @record_class.new @config = @config_class.new(@record_class) @object_builder = @object_builder_class.new(@record, @config) end subject{ @object_builder } should "know how to set an association on its record" do @record.fake_one = FakeOneRecord.new value = [FakeOneRecord.new, nil].sample subject.set(:fake_one, value) assert_equal value, @record.fake_one @record.fake_poly = FakeOneRecord.new value = [FakeOneRecord.new, nil].sample subject.set(:fake_poly, value) assert_equal value, @record.fake_poly end should "know how to set an association when given a hash" do associated_record_class = FakeOneRecord association_name = Factory.boolean ? :fake_one : :fake_poly associated_factory = MR::Factory::RecordFactory.new(associated_record_class) if Factory.boolean @config.add_association_factory(association_name, associated_factory) else @config.default_factories[associated_record_class] = associated_factory end apply_hash_called_with = nil Assert.stub(@object_builder_class, :apply_hash) do |*args| apply_hash_called_with = args Assert.stub_send(@object_builder_class, :apply_hash, *args) end # when the association is already set associated_record = associated_factory.record @record.send("#{association_name}=", associated_record) association_args = { :name => Factory.string } subject.set(association_name, association_args) assert_equal associated_record, @record.send(association_name) assert_equal association_args[:name], associated_record.name exp = [associated_record, associated_factory.config, association_args] assert_equal exp, apply_hash_called_with # when the association isn't already set @record.send("#{association_name}=", nil) if association_name == :fake_poly # set the fake poly type attribute so it can find a factory for it @record.fake_poly_type = associated_record_class.to_s end associated_record = associated_factory.record Assert.stub(associated_factory, :instance){ associated_record } association_args = { :name => Factory.string } subject.set(association_name, association_args) assert_equal associated_record, @record.send(association_name) assert_equal association_args[:name], associated_record.name exp = [associated_record, associated_factory.config, association_args] assert_equal exp, apply_hash_called_with end should "raise a record class error if it can't build an association" do assert_raises(MR::Factory::NoRecordClassError) do subject.set(:fake_poly, {}) end end end class ModelObjectBuilderInitTests < WithAssociationsConfigObjectBuilderTests desc "when init with a model" setup do @config_class.class_eval do define_method(:ar_association_for) do |model, name| if (reflection = model.record_class.reflect_on_association(name)) model.record.association(reflection.name) end end define_method(:build_factory_for_record_class) do |record_class| MR::Factory::ModelFactory.new(record_class.model_class, record_class) end end @model_class = FakeTwoModel @model = @model_class.new @config = @config_class.new(@model_class.record_class) @object_builder = @object_builder_class.new(@model, @config) end subject{ @object_builder } should "know how to set an association on its model" do @model.fake_one = FakeOneModel.new value = [FakeOneModel.new, nil].sample subject.set(:fake_one, value) assert_equal value, @model.fake_one @model.fake_poly = FakeOneModel.new value = [FakeOneModel.new, nil].sample subject.set(:fake_poly, value) assert_equal value, @model.fake_poly end should "know how to set an association when given a hash" do associated_model_class = FakeOneModel association_name = Factory.boolean ? :fake_one : :fake_poly associated_factory = MR::Factory::ModelFactory.new( associated_model_class, associated_model_class.record_class ) if Factory.boolean @config.add_association_factory(association_name, associated_factory) else record_class = associated_model_class.record_class @config.default_factories[record_class] = associated_factory end apply_hash_called_with = nil Assert.stub(@object_builder_class, :apply_hash) do |*args| apply_hash_called_with = args Assert.stub_send(@object_builder_class, :apply_hash, *args) end # when the association is already set associated_model = associated_factory.model @model.send("#{association_name}=", associated_model) association_args = { :name => Factory.string } subject.set(association_name, association_args) assert_equal associated_model, @model.send(association_name) assert_equal association_args[:name], associated_model.name exp = [associated_model, associated_factory.config, association_args] assert_equal exp, apply_hash_called_with # when the association isn't already set @model.send("#{association_name}=", nil) if association_name == :fake_poly # set the fake poly type attribute so it can find a factory for it @model.fake_poly_type = associated_model_class.record_class.to_s end associated_model = associated_factory.model Assert.stub(associated_factory, :instance){ associated_model } association_args = { :name => Factory.string } subject.set(association_name, association_args) assert_equal associated_model, @model.send(association_name) assert_equal association_args[:name], associated_model.name exp = [associated_model, associated_factory.config, association_args] assert_equal exp, apply_hash_called_with end should "raise a record class error if it can't build an association" do assert_raises(MR::Factory::NoRecordClassError) do subject.set(:fake_poly, {}) end end end class FakeOneRecord include MR::FakeRecord attribute :name, :string end class FakeTwoRecord include MR::FakeRecord attribute :name, :string attribute :fake_poly_type, :string attribute :fake_poly_id, :integer attribute :fake_one_id, :integer belongs_to :fake_poly, :polymorphic => true belongs_to :fake_one, :class_name => FakeOneRecord.to_s end class FakeThreeRecord include MR::FakeRecord attribute :fake_one_id, :integer attribute :fake_two_id, :integer belongs_to :fake_one, :class_name => FakeOneRecord.to_s belongs_to :fake_two, :class_name => FakeTwoRecord.to_s end class FakeOneModel include MR::Model record_class FakeOneRecord field_accessor :name end class FakeTwoModel include MR::Model record_class FakeTwoRecord field_accessor :name, :fake_poly_type, :fake_poly_id, :fake_one_id polymorphic_belongs_to :fake_poly belongs_to :fake_one end class TestReadModel include MR::ReadModel field :name, :string end end