Spec'ing the :conditions argument of a find


#1

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


#2

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’
)


#3

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


#4

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,


#5

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


#6

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


#7

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)


#8

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


#9

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


#10

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/