Forum: RSpec spec'ing the :conditions argument of a find

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.
Rémi G. (Guest)
on 2008-10-28 16:52
Let's see, I want to spec the :conditions args to make sure the right
args is passed to the query.

Product.find(:all,
      :conditions => ["inte_no = ? and vaat_id_type_statut_pcpa = ?",
inte_no, 7],
      :limit => 2,
      :order => "trns_dt_appl_prod desc")


Product.should_receive(:find).with(:conditions =>
'vaat_id_type_statut_pcpa == 7)

I'm pretty sure this is not the right synthax.

Any suggestions?

Rémi
Nick H. (Guest)
on 2008-10-30 17:10
(Received via mailing list)
On 2008-10-28, at 10:52, Rémi Gagnon wrote:
> Product.should_receive(:find).with(:conditions =>
> 'vaat_id_type_statut_pcpa == 7)
>
> I'm pretty sure this is not the right synthax.
>
> Any suggestions?
>
> Rémi

Hi Rémi. You have two options here:
1) Pass each argument to #with that #find receives;
2) Use #any_args to not specify some arguments that #find receives.

I'd do something like this:

find_conditions = []
find_conditions << "inte_no = ? and vaat_id_type_statut_pcpa = ?"
find_conditions << inte_no
find_conditions << 7

Product.should_receive(:find).with(:all,
   :conditions => find_conditions,
   :limit => 2,
   :order => 'trns_dt_appl_prod desc'
   )
Pat M. (Guest)
on 2008-10-30 17:35
(Received via mailing list)
Rémi Gagnon <removed_email_address@domain.invalid> writes:

> Product.should_receive(:find).with(:conditions =>
> 'vaat_id_type_statut_pcpa == 7)
>
> I'm pretty sure this is not the right synthax.
>
> Any suggestions?
>
> Rémi

ewww......brittle, ugly, and not encapsulated.  Why don't you create a
method to wrap this find?  Product.find_thingy(1, 2).  Then mocking
becomes trivial and clean:
Product.should_receive(:find_thingy).with(1,2)

Pat
Rémi G. (Guest)
on 2008-10-30 17:41
I do agree, That's what we're gonna do. it was just an example.
But what if we want to test find_thingy(in model spec) to make sure the
:conditions is set properly?

R

Pat M. wrote:
> Rémi Gagnon <removed_email_address@domain.invalid> writes:
>
>> Product.should_receive(:find).with(:conditions =>
>> 'vaat_id_type_statut_pcpa == 7)
>>
>> I'm pretty sure this is not the right synthax.
>>
>> Any suggestions?
>>
>> Rémi
>
> ewww......brittle, ugly, and not encapsulated.  Why don't you create a
> method to wrap this find?  Product.find_thingy(1, 2).  Then mocking
> becomes trivial and clean:
> Product.should_receive(:find_thingy).with(1,2)
>
> Pat
Zach D. (Guest)
on 2008-10-30 18:00
(Received via mailing list)
On Thu, Oct 30, 2008 at 11:41 AM, Rémi Gagnon 
<removed_email_address@domain.invalid>
wrote:

> I do agree, That's what we're gonna do. it was just an example.
> But what if we want to test find_thingy(in model spec) to make sure the
> :conditions is set properly?


You are probably more interested in the fact that find_thingy works
rather
then it sets a bunch of conditions. Perhaps something like the below
would
work for you:

it "can find things by blank and blank" do
   thing1 = Thing.create! :nte_no => 1, vaat_id_type_statut_pcpa => 2
   thing2 = Thing.create! :nte_no => 2, vaat_id_type_statut_pcpa => 3
   Thing.find_by_thingy(1,2).should == thing1
   Thing.find_by_thingy(2,3).should == thing2
end

If you have a way of generating "things" that are valid, then I'd use
that
mechanism and pass in the attributes you are specifically going to be
testing against (so they stand out in the example that that's what
matters
in the example).

I know the above example breaks the one assertion per test guideline
people
strive to adhere to, but I think it is ok.  If there are more examples
that
should be used to make sure find_thingy works then I'd break out a
separate
describe block and have multiple 'it' examples,
Matt W. (Guest)
on 2008-10-30 18:43
(Received via mailing list)
On 30 Oct 2008, at 15:58, Zach D. wrote:
> I know the above example breaks the one assertion per test guideline
> people strive to adhere to, but I think it is ok.  If there are more
> examples that should be used to make sure find_thingy works then I'd
> break out a separate describe block and have multiple 'it' examples,

It also goes to the database, which will make it a slow unit test. I
personally do pretty much the same thing myself mostly when working
with ActiveRecord, but it doesn't mean I'm comfortable with it. (And
it also doesn't mean our unit test suite is anything other than
shamefully slow)

I did experiment with a QueryApapter for this purpose which has worked
out quite well for us. You end up with code like this in the Controller:

  products = Product.find(:all, Product::QueryAdapter.new(params).adapt)

You then have a QueryAdapter class to test which is solely responsible
for mapping web query params into database query params - so that's
where you put the logic to build your conditions, your limit etc, and
return them as a hash from QueryAdapter#adapt.

The family of QueryAdapters then become the one place where I'm
coupled to ActiveRecord's find method arguments, which is nice, rather
than having it sprinkled all over the controller code.

HTH,
Matt
Pat M. (Guest)
on 2008-10-30 22:13
(Received via mailing list)
Matt W. <removed_email_address@domain.invalid> writes:

> shamefully slow)
>
> I did experiment with a QueryApapter for this purpose which has worked
> out quite well for us. You end up with code like this in the
> Controller:

Scott is working on a SQL parser which would let you write tests that
"hit" the db but keep everything in memory and fast.  Might be worth
checking out for you.

Pat
Zach D. (Guest)
on 2008-10-31 08:53
(Received via mailing list)
On Thu, Oct 30, 2008 at 12:41 PM, Matt W. <removed_email_address@domain.invalid> 
wrote:

> ActiveRecord, but it doesn't mean I'm comfortable with it. (And it also
> doesn't mean our unit test suite is anything other than shamefully slow)


It will make it a *slower* unit test. I am yet to be convinced that when
working with ActiveRecord, it is worthwhile to not hit the database when
you
trying to verify that custom queries are working correctly. I have yet
to
see model examples which were so slow I didn't want to run them. I get
more
confidence and value out of knowing custom queries work at the model
example
level, rather than waiting for an acceptance test to trigger their
failure.

I do get value out of not hitting the database for things that I don't
need
to hit it for, but custom finds aren't one of those things IMO. It's
also
easier to track down the failure and fix it at the model example level,
rather than having to truck through acceptance test backtraces, log
files,
etc to find that you had a typo in your custom finder.

For the record, my definition of custom apples to handcrafted SQL as
well as
handcrafted conditions, scopes, joins, etc that are used when creating
find
methods which encapsulate this logic (such as find_thingy 1, 2)
Matt W. (Guest)
on 2008-10-31 09:57
(Received via mailing list)
On 30 Oct 2008, at 20:11, Pat M. wrote:
>> with ActiveRecord, but it doesn't mean I'm comfortable with it. (And
> checking out for you.
Sounds interesting. I'd still like to see us have a proper ORM for
ruby that lets us play with POROs 90% of the time, and just have a
separate suite of tests for the database-object mappings that we run
when necessary.

cheers,
Matt
Ashley M. (Guest)
on 2008-10-31 10:26
(Received via mailing list)
On Oct 31, 2008, at 7:49 am, Matt W. wrote:

> separate suite of tests for the database-object mappings that we run
> when necessary.

I'm using DataMapper in a (non-web) project I have on now, and for
part of the spec suite where I am concerned with persistence as a
black-box, I create an in-memory SQLite database:

     # story_spec.rb

     describe "Class", Story do

       # ...

       describe ".unposted" do
         include InMemoryDatabase

         before(:each) do
           setup_in_memory_database

           @twitter_client = mock(TwitterAgent::Client, :post_story =>
true)

           @story_1 = Story.create(:title => "Story title 1",
                                   :published_at => DateTime.new(2008,
9, 28))
           @story_2 = Story.create(:title => "Story title 2",
                                   :published_at => DateTime.new(2008,
9, 29))
           @story_3 = Story.create(:title => "Story title 3",
                                   :published_at => DateTime.new(2008,
9, 27))
         end

         it "should find all stories (in ascending published date)
when none have been posted" do
           Story.unposted.should == [ @story_3, @story_1, @story_2 ]
         end

         it "should find only unpublished stories (in ascending
published date)" do
           @story_1.post_to_twitter!(@twitter_client)
           Story.unposted.should == [ @story_3, @story_2 ]
         end

       end
     end

     # spec_helper.rb

     module InMemoryDatabase
       def setup_in_memory_database
         DataMapper.setup(:default, "sqlite3::memory:")
         Database::Migrator.new.reset_database!
       end
     end

Aside from being unable to use DB-lever constraints, that gives me
enough confidence in the persistence.  Other specs that don't need
persistence simply omit the "setup_in_memory_database" call.

It's important not to lose sight of the fact that, even though
ActiveRecord mixes business logic and persistence, *they are still
separate concerns*.

Ashley


--
http://www.patchspace.co.uk/
http://aviewfromafar.net/
This topic is locked and can not be replied to.