Forum: Ruby on Rails unit test fails, but shouldnt have

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
C4635cd7f47b7c581e295f57d5864e0d?d=identicon&s=25 Merrick Johnson (merrick)
on 2008-12-21 05:51
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
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2008-12-21 06:19
(Received via mailing list)
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/dyna...
C4635cd7f47b7c581e295f57d5864e0d?d=identicon&s=25 Merrick Johnson (merrick)
on 2008-12-21 06:21
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
C4635cd7f47b7c581e295f57d5864e0d?d=identicon&s=25 Merrick Johnson (merrick)
on 2008-12-21 06:30
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 )
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2008-12-21 06:32
(Received via mailing list)
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
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2008-12-21 06:47
(Received via mailing list)
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
C4635cd7f47b7c581e295f57d5864e0d?d=identicon&s=25 Merrick Johnson (merrick)
on 2008-12-21 06:50
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
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2008-12-21 07:07
(Received via mailing list)
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
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2008-12-21 07:14
(Received via mailing list)
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
This topic is locked and can not be replied to.