Unit test fails, but shouldnt have


#1

Hi,

I have a one to many relationship ( stories & votes ), that i wish to
test via a unit test. In development everything works great, votes ends
up w/ the correct story_id and the relationship is great ( i can
Vote.find(:last).story or Story.find(:last).votes ) But when I run the
unit test, it fails. The story gets an id of 996332877 ( which is 10000
in dev ) and the votes story_id is 0. Not sure what the heck is going on
here. Any help would be greatly appreciated.

Merrick

stories:
±----------------+
| ID | NAME | URL |
±----------------+

votes:
±--------------+
| ID | STORY_ID |
±--------------+

models:

class Story < ActiveRecord::Base
validates_presence_of :name, :link
has_many :votes do
def latest
find :all, :order => ‘id DESC’, :limit => 3
end
end
def to_param
“#{id}-#{name.gsub(/\W/, ‘-’).downcase}”
end
end

class Vote < ActiveRecord::Base
belongs_to :story
end

vote test:

test “story_association” do
assert_equal stories(:two), votes(:two).story #FAILS!
end


#2

Merrick Johnson wrote:

has_many :votes do
def latest
find :all, :order => ‘id DESC’, :limit => 3
end
end

Nice! Could someone hint what that syntax does? (But I don’t think it’s
causing
the problem…

vote test:

Next time, write the test first! You should have developed all those
model-level
features entirely in test, using the tests essentially the same way - to
feel
around and sense changes as you grew the code - as you used your Rails
app in
development mode.

But enough of the lecture…

test “story_association” do
assert_equal stories(:two), votes(:two).story #FAILS!
end

What is in your test/fixtures/stories.yml and votes.yml files?

What does the assert_equal spew forth when it fails?


Phlip
http://www.oreillynet.com/onlamp/blog/2008/05/dynamic_languages_vs_editors.html


#3

Good call Philip, turns out my votes.yml fixture looked like this:

one:
story_id: one

two:
story_id: one

when it should have looked like this:

one:
story: one

two:
story: one

Waalaa
Thanks


#4

Phlip wrote:

Merrick Johnson wrote:

has_many :votes do
def latest
find :all, :order => ‘id DESC’, :limit => 3
end
end

Nice! Could someone hint what that syntax does? (But I don’t think it’s
causing
the problem…

this creates a method for the resource. in this case it returns the
newest 3 records. This is pretty helpful because i can call this from a
view or partial ( @story.votes.latest ) and just get the newest three
votes. This should work in any DB ( im using Oracle11G )


#5

Phlip wrote:

Merrick Johnson wrote:

this creates a method for the resource. in this case it returns the
newest 3 records.

Yup!

This is pretty helpful because i can call this from a
view or partial ( @story.votes.latest ) and just get the newest three
votes. This should work in any DB ( im using Oracle11G )

And you never need to say that about MVC or ActiveRecord. The point is
you write
the minimum code, all in the right places, to support the maximum
features. You
don’t have to do it the old-fashioned way, with code scattered
everywhere, all
very similar, getting in the way of each other!

Now this is why that new aggregation notation is so dang cool. Here’s
the Rails
1 way to do a bunch of links:

class Customer
has_many :accounts
has_many :accounts_cc, :class_name => ‘Account’,
:conditions => { :kind => ‘cc’ }
has_many :accounts_ach, :class_name => ‘Account’,
:conditions => { :kind => ‘ach’ }
end

Now compared to raw SQL, has_many is already an order of magnitude more
DRY
(Don’t Repeat Yourself). Yet the one requirement to distinguish Credit
Card from
Checking accounts forces us to create three very wet has_many calls on
the same
model. They are, once again, irritatingly similar.

Here’s the (apparent!) Rails 2 fix:

class Customer
has_many :accounts do
def kind(k)
find :all, :conditions => { :kind => k }
end
end
end

Boom done. You can write customer.accounts,
customer.accounts.kind(‘cc’), and
customer.accounts.kind(‘ach’), all very similar to what you could write
before,
but with far fewer executable lines.

I’m stoked because we just spent 3 days refactoring a big system which
needed
this exact fix, and I didn’t know about it. Now I get to install it on
Monday.
Merry Xmas to me, huh?!


Phlip

Just rewrote my test to make a little more sense. The first test fails
because the order of the votes, if i reverse it it passes. Since I am
not testing order, the third test ( votes_association_improved ) makes a
little more sense. Is there a way to write that in one statement without
&& ?

test “votes_association_1” do
assert_equal [ votes(:one), votes(:two) ], stories(:one).votes
#FAILS
end
test “votes_association_2” do
assert_equal [ votes(:two), votes(:one) ], stories(:one).votes
#PASSES
end
test “votes_association_improved” do
assert stories(:one).votes.include? votes(:one)
assert stories(:one).votes.include? votes(:two)
end


#6

Merrick Johnson wrote:

this creates a method for the resource. in this case it returns the
newest 3 records.

Yup!

This is pretty helpful because i can call this from a
view or partial ( @story.votes.latest ) and just get the newest three
votes. This should work in any DB ( im using Oracle11G )

And you never need to say that about MVC or ActiveRecord. The point is
you write
the minimum code, all in the right places, to support the maximum
features. You
don’t have to do it the old-fashioned way, with code scattered
everywhere, all
very similar, getting in the way of each other!

Now this is why that new aggregation notation is so dang cool. Here’s
the Rails
1 way to do a bunch of links:

class Customer
has_many :accounts
has_many :accounts_cc, :class_name => ‘Account’,
:conditions => { :kind => ‘cc’ }
has_many :accounts_ach, :class_name => ‘Account’,
:conditions => { :kind => ‘ach’ }
end

Now compared to raw SQL, has_many is already an order of magnitude more
DRY
(Don’t Repeat Yourself). Yet the one requirement to distinguish Credit
Card from
Checking accounts forces us to create three very wet has_many calls on
the same
model. They are, once again, irritatingly similar.

Here’s the (apparent!) Rails 2 fix:

class Customer
has_many :accounts do
def kind(k)
find :all, :conditions => { :kind => k }
end
end
end

Boom done. You can write customer.accounts,
customer.accounts.kind(‘cc’), and
customer.accounts.kind(‘ach’), all very similar to what you could write
before,
but with far fewer executable lines.

I’m stoked because we just spent 3 days refactoring a big system which
needed
this exact fix, and I didn’t know about it. Now I get to install it on
Monday.
Merry Xmas to me, huh?!


Phlip


#7

Merrick Johnson wrote:

one:
story: one

two:
story: one

Ahem. Now for some more lectures!

Your test data fixtures should be literate, so you can document your
business
rules in your test cases. In other words, your fixtures should not just
say “one
two” etc. They should, instead, tell a … story.

(-:

BTW I guessed what this does:

has_many :votes do
def latest
find :all, :order => ‘id DESC’, :limit => 3
end
end

It lets you say a_story.votes.latest and call that find. I can’t wait to
use
that utterly kewl feature!


Phlip


#8

Merrick Johnson wrote:

test “votes_association_improved” do
assert stories(:one).votes.include? votes(:one)
assert stories(:one).votes.include? votes(:two)
end

You need .to_set, and I will recommend a way to unify assert() and
assert_equal() into an assertion that reflects all its variables and
values when
it fails.

gem install assert2

require ‘assert2’

assert{ stories(:one).votes.to_set == votes(:one, :two).to_set }

.to_set converts an ordered array into an unordered group, so == can’t
see the
order and can’t complain.

(But don’t use assert{ 2.0 } if you use Ruby 1.8.7! A fix for Ruby 1.9
is in the
works…)


Phlip


#9

Merrick Johnson wrote:

  find :all, :order => 'id DESC', :limit => 3

Note that, in a normal database - and in a unit test that generates new
records

  • the ‘id’ should increment monotonically as new records get added. So
    ‘latest’
    is always the highest ‘id’. (Note I have no idea if databases must
    eternally
    enforce this rule, but it stands to reason…)

However, a modern Rails fixture file uses a “magic ‘id’ system” that
builds
'id’s out of the hash of each record’s fixture name. So the 'id’s are
not
monotonic, and these problems illustrate you ought to use a timestamp,
such as
‘created_at’, instead of an ‘id’, to find the latest records!

This means, instead of using .to_set in the test, you should actually
pin down
the ‘created_at’ times in your fixture files. And this reminds us,
again, to use
the fixtures to tell a little story. Suggest one record is
“2.hours.ago”, and
another is “5.minutes.ago”, for example!


Phlip