This is part of the Chores series of posts
When last we worked on Chores, we had just generated two Models; Identity and Child. The idea behind these is that Child will serve as a many-to-many link between identities via has_many :through. First let’s look over the generated migrations and make a couple changes.
1 2 3 4 5 6 7 8 9 10 11 12 13 | class CreateIdentities < ActiveRecord::Migration def self.up create_table :identities do |t| t.string :identifier, :null => false, :limit => 100 t.timestamps end end def self.down drop_table :identities end end |
and
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class CreateChildren < ActiveRecord::Migration def self.up create_table :children do |t| t.integer :parent_id, :null => false t.integer :child_id, :null => false t.timestamps end end def self.down drop_table :children end end |
and let’s migrate.
$ rake db:migrate (in c:/rails/chores) == CreateIdentities: migrating =============================================== -- create_table(:identities) -> 0.0630s == CreateIdentities: migrated(0.0630s) ====================================== == CreateChildren: migrating ================================================= -- create_table(:children) -> 0.0160s == CreateChildren: migrated (0.0160s) ========================================
and update the Blueprints (./test/blueprints.rb).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | require 'machinist' # if you installed the gem Chore.blueprint do description "Put the dirty clothes in the hamper" end Identity.blueprint do identifier "#{Faker::Internet.user_name}.#{Faker::Internet.domain_name}" end Child.blueprint do child { Identity.make } parent { Identity.make } end |
And remember to add the reference to the Faker gem to ./test/test_helper
require 'faker'
I added not null constraints to the three columns in the two tables I created so I’m going to make note of that in the test files and start by making a child test (./test/unit/child_test.rb).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | require 'test_helper' class ChildShouldBeValidated < ActiveSupport::TestCase specify "that a parent is required" do child = Child.new :identity => Identity.make invalid child child.parent = Identity.make valid child end specify "that a child is required" do child = Child.new :parent => Identity.make invalid child child.identity = Identity.make valid child end specify "message for a null identity" do child = Child.new :parent => Identity.make validation_message_for child, :identity, "can not be blank." end specify "message for a null parent" do child = Child.new :identity => Identity.make validation_message_for child, :parent, "can not be blank." end protected def invalid model assert !model.valid? end def valid model assert model.valid? end def validation_message_for model, column, message invalid model assert_equal message, model.errors.on(column) end end |
that fails spectacularly..
$ rake test:units (in c:/rails/chores) c:/Ruby/bin/ruby -Ilib;test "c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/ rake_test_loader.rb" "test/unit/child_test.rb" "test/unit/chore_test.rb" "test/u nit/identity_test.rb" Loaded suite c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader Started EEEE...... Finished in 0.766 seconds. 1) Error: test_message_for_a_null_identity(ChildShouldBeValidated): ActiveRecord::UnknownAttributeError: unknown attribute: parent c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize' ./test/unit/child_test.rb:20:in 'new' ./test/unit/child_test.rb:20:in 'test_message_for_a_null_identity' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run' 2) Error: test_message_for_a_null_parent(ChildShouldBeValidated): ActiveRecord::UnknownAttributeError: unknown attribute: identity c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize' ./test/unit/child_test.rb:25:in 'new' ./test/unit/child_test.rb:25:in 'test_message_for_a_null_parent' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run' 3) Error: test_that_a_child_is_required(ChildShouldBeValidated): ActiveRecord::UnknownAttributeError: unknown attribute: parent c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize' ./test/unit/child_test.rb:13:in 'new' ./test/unit/child_test.rb:13:in 'test_that_a_child_is_required' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run' 4) Error: test_that_a_parent_is_required(ChildShouldBeValidated): ActiveRecord::UnknownAttributeError: unknown attribute: identity c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes=' c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize' ./test/unit/child_test.rb:6:in 'new' ./test/unit/child_test.rb:6:in 'test_that_a_parent_is_required' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__' c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run' 10 tests, 9 assertions, 0 failures, 4 errors rake aborted! Command failed with status (1): [c:/Ruby/bin/ruby -Ilib;test c:/Ruby/lib/r...] See full trace by running task with --trace)
Looks like I need to set up some attributes. Let’s try the following class definition
1 2 3 4 5 6 7 | class Child < ActiveRecord::Base belongs_to :identity belongs_to :parent, :class_name => 'Identity' validates_presence_of :identity, :message => 'can not be blank.' validates_presence_of :parent, :message => 'can not be blank.' end |
That gives us green
$ rake test:units (in c:/rails/chores) c:/Ruby/bin/ruby -Ilib;test "c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/ rake_test_loader.rb" "test/unit/child_test.rb" "test/unit/chore_test.rb" "test/u nit/identity_test.rb" Loaded suite c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader Started .......... Finished in 0.781 seconds 10 tests, 17 assertions, 0 failures, 0 errors
But wait!
If you have been following along in this series of posts, you might have noticed that I copied, pasted and then modified the code from the last test. Copy and Pasting is bad and the opposite of DRY right? Well, yeah. That’s right. And it’s also why it’s time to refactor. Here are the results of the refactoring.
./test/test_helper.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ENV["RAILS_ENV"] = "test" require File.expand_path(File.dirname(__FILE__) + "/../config/environment") require File.expand_path(File.dirname(__FILE__) + "/blueprints") require 'test_help' require 'machinist' #if you installed as gem rather than plugin require 'faker' class ActiveSupport::TestCase self.use_transactional_fixtures = true self.use_instantiated_fixtures = false def invalid model assert !model.valid? end def valid model assert model.valid? end def validation_message_for model, column, message invalid model assert_equal message, model.errors.on(column) end end def specify *args, &block test(*args, &block) end |
./test/unit/child_test.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | require 'test_helper' class ChildShouldBeValidated < ActiveSupport::TestCase specify "that a parent is required" do child = Child.new :identity => Identity.make invalid child child.parent = Identity.make valid child end specify "that a child is required" do child = Child.new :parent => Identity.make invalid child child.identity = Identity.make valid child end specify "message for a null identity" do child = Child.new :parent => Identity.make validation_message_for child, :identity, "can not be blank." end specify "message for a null parent" do child = Child.new :identity => Identity.make validation_message_for child, :parent, "can not be blank." end end |
./test/unit/chore_test.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | require 'test_helper' class ChoreShouldBeValidated < ActiveSupport::TestCase specify "that a description is required" do chore = Chore.new invalid chore chore.description = "Clean your room" valid chore end specify "that a description of 255 characters is valid" do chore = Chore.new :description => two_hundred_fifty_character_string valid chore end specify "that a description of 256 characters is invalid" do chore = Chore.new :description => two_hundred_fifty_one_character_string invalid chore end specify "message for a null description" do chore = Chore.new validation_message_for chore, :description, "Description cannot be blank." end specify "message for an overly long description" do chore = Chore.new :description => two_hundred_fifty_one_character_string validation_message_for chore, :description, "Description cannot be longer than 255 characters." end protected def two_hundred_fifty_character_string "This is a really long string that is the description of a chore and it is used to validate that the chore model will accept a string of 255 characters. This string is longer than the longest string ever which also included some numbers 012345678901234567" end def two_hundred_fifty_one_character_string two_hundred_fifty_character_string + "1" end end |
As you can see, I’ve moved the duplicates to test_helper.rb, which clears up the duplicate code. Another option that you can explore to reduce duplication is mixins. That just about wraps up this post, next time I will work on testing the identity model.