Chores: a test driven website – Part 7 (return of the son of Chores)

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.

Tags: ,

6 Responses to “Chores: a test driven website – Part 7 (return of the son of Chores)”

  1. Wayne Simacek says:

    Jamal,
    I’m getting an error running rake test:units with line 6

    child = Child.new :identity => Identity.make

    it does not like the =&gt

    I’m not sure where I messed up?

    Wayne

  2. Wayne Simacek says:

    I took out that whole first test, and my rake ran with 9 tests, 9 assertions, 0 failures, 3 errors. Could it be I need a capital I in identity?

    Thanks,
    Wayne

  3. Wayne Simacek says:

    Jamal,
    It just didn’t like the extra space before the =. I took it out and I’m back to 10 tests and 4 errors…but at least my tests are running.

    Thanks,
    Wayne

  4. Wayne Simacek says:

    I’m an idiot. just replaced the =%gt with the => and everything is good now. All green.

  5. Wayne Simacek says:

    Jamal,
    In refactoring, when I add the lines
    self.use_transactional_fixtures = true
    self.use_instantiated_fixtures = false

    to test_helper it breaks my tests and I get ‘undefined method’ errors.

    I went ahead and installed the gem ‘faker’ which you had talked about, but that did not seem to help when I tried to put them back in.

    It looks like I’m missing something?

    Wayne

  6. Wayne Simacek says:

    Jamal,
    I found the two self.* statements inside the class ActiveSupport::TestCase already inside my test_helper.rb file and all the tests are still passing.

    I’m just going to continue on and assume all is good until I can’t resolve future tests from the refactoring.

    Thanks again,
    Wayne

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">