Forum: RSpec Specing protected methods

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.
Vaucher B. (Guest)
on 2008-10-23 13:19
(Received via mailing list)
Hi everyone,
Do anyone know if there's a way to spec protected methods ?
Thanks in advance

-- Bastien
Matt W. (Guest)
on 2008-10-23 14:40
(Received via mailing list)
On 23 Oct 2008, at 10:16, Bastien wrote:

> Hi everyone,
> Do anyone know if there's a way to spec protected methods ?
> Thanks in advance

Sorry if this sounds preachy, but I would always advise you to factor
out the code in the method into another class, then test that class.

If the behaviour of the method is so complex you need to test it on
it's own, it deserves to be in a class all by itself.

This is what we call, 'listening to your specs' - if it's hard to
spec, you've probably got the design wrong.

HTH,
Matt
Vaucher B. (Guest)
on 2008-10-23 15:51
(Received via mailing list)
I totally agree with the ''listening to your specs" concepts and
always divide my code into small methods easier to spec.
Now let me get you right, here's my code :

class Survey < ActiveRecord::Base
  has_many :participants
  ...
  def generate_reports
    ...
    sub_total = sub_total(participants.reports)
    ...
  end

protected

 def sub_total(reports)
    sub_total = 0
    reports.collect do |report|
      sub_total = sub_total + report.score
    end
   return sub_total
 end

end

I m already specing the "generate_reports" methods with a mock on
"sub_total", but I would also like to spec that method. I've put this
method into protected because it doesn't make sens to call it from
outside this class. But you're arguing that I should put that method
into a lib ? I m not sure it would make sense as this method is very
specific to that class, and would actually be easy to spec. Except
that it's not possible to call a protected method from a spec as far
as I know, correct me if I m wrong. (I maybe didn't explain my problem
well in the first place)

Regards

-- Bastien
Craig D. (Guest)
on 2008-10-23 17:38
(Received via mailing list)
On Thu, Oct 23, 2008 at 7:49 AM, Bastien <removed_email_address@domain.invalid>
wrote:

> I m already specing the "generate_reports" methods with a mock on
> "sub_total", but I would also like to spec that method. I've put this
> method into protected because it doesn't make sens to call it from
> outside this class. But you're arguing that I should put that method
> into a lib ? I m not sure it would make sense as this method is very
> specific to that class, and would actually be easy to spec. Except
> that it's not possible to call a protected method from a spec as far
> as I know, correct me if I m wrong. (I maybe didn't explain my problem
> well in the first place)


If you're somehow mocking the sub_total method while specing the
generate_reports method, don't do that. Don't stub/mock the thing that
you're specing, only stub/mock its collaborators, i.e., its
dependencies. I
would either verify via generate_reports that the sub totals on the
report
are correct, or I'd factor out something like a ReportGenerator class
and
spec it. Then, I'd stub/mock out the ReportGenerator in the specs for
the
Survey class.

Regards,
Craig
Pat M. (Guest)
on 2008-10-23 17:53
(Received via mailing list)
Bastien <removed_email_address@domain.invalid> writes:

>     ...
>  end
>
> end
>
> I m already specing the "generate_reports" methods with a mock on
> "sub_total"

Why are you mocking #sub_total?

Pat
David C. (Guest)
on 2008-10-23 17:54
(Received via mailing list)
On Thu, Oct 23, 2008 at 6:49 AM, Bastien <removed_email_address@domain.invalid>
wrote:
>    ...
>  end
> as I know, correct me if I m wrong. (I maybe didn't explain my problem
> well in the first place)

This sort of decision is always a balancing act. Leaving it where it
is means you have to either

* unprotect it, which you clearly prefer not to do
* expose it for you code example using clever ruby trickery
* give up testing it directly
* move it to another class, adding conceptual weight to your system

None of these are perfect and all come with some tradeoff.

I'll confess that, in practice, I'd probably not test this directly.
More often than not, for me, the sub_total method is a result of
refactoring the generate_reports method, which by that time would have
at least two code examples in which the only difference is the sum of
scores of the reports.

In this case, however, assuming participants and reports are AR backed
models, you don't really need to introduce a new class:

subtotal = participants.reports.sum('score')

Cheers,
David
Vaucher B. (Guest)
on 2008-10-23 19:22
(Received via mailing list)
Thanks for all your answers.

I was just wondering how about using send in my specs ? It would
bypass the protected limitation without altering my code :

@survey.send( :sub_total, [@mock_report] )

Is that the kind of trickery you were referring to David ? Would that
be bad practice ?

Regards

-- Bastien
David C. (Guest)
on 2008-10-23 19:40
(Received via mailing list)
On Thu, Oct 23, 2008 at 10:00 AM, Bastien <removed_email_address@domain.invalid>
wrote:
> Thanks for all your answers.
>
> I was just wondering how about using send in my specs ? It would
> bypass the protected limitation without altering my code :
>
> @survey.send( :sub_total, [@mock_report] )
>
> Is that the kind of trickery you were referring to David ? Would that
> be bad practice ?

If you're going to do this, use __send__ or your code examples will
break if/when you go to Ruby >= 1.9.

As for good/bad practice, here's the way I see it: there is a lot of
evidence that encapsulation is a good thing in that it constrains
contact points between objects, making them less coupled and easier to
modify internally, swap in and out (polymorphism), etc, etc.

Every time you violate encapsulation by using __send__, even in code
examples, you increase coupling by a little bit, and make your system
that much more brittle. So while one violation might seem harmless,
it's a slippery slope (sorry for the cliché).

With all that - you'll find __send__ in my code, and in my code
examples. But I'm conscious of the risks. FWIW.

Cheers,
David
Michael L. (Guest)
on 2008-10-23 23:01
(Received via mailing list)
It appears we were too clever, but I think this would be a reasonable
use of stories.

We tried to have a story file run with 2 different sets of steps to
use the same story against both the UI using selenium and against the
server API using regular get/put/post.  This way we could spec the
server API and the UI while ensuring they are in sync for the scenario
in question.  But stories get confused when there are stories or
scenarios with the same names.

Does Cucumber deal with this any better than the story runner?  Given
that it searches for steps rather than having them declared, I would
think you can not share story files for different steps?

Michael
Zach D. (Guest)
on 2008-10-24 00:57
(Received via mailing list)
On Thu, Oct 23, 2008 at 1:20 PM, Michael L. 
<removed_email_address@domain.invalid> wrote:
> Does Cucumber deal with this any better than the story runner?  Given that
> it searches for steps rather than having them declared, I would think you
> can not share story files for different steps?

You can do this, you'd just put the steps in different directories and
tell cucumber to require the files in one directory for one run, and
another for another run. ie:

   cucumber --require features/steps/selenium/  features/
AND
   cucumber --require features/steps/normal/     features/

--
Zach D.
http://www.continuousthinking.com
http://www.mutuallyhuman.com
Avdi G. (Guest)
on 2008-10-24 01:03
(Received via mailing list)
On Thu, Oct 23, 2008 at 5:16 AM, Bastien <removed_email_address@domain.invalid>
wrote:
> Hi everyone,
> Do anyone know if there's a way to spec protected methods ?
> Thanks in advance

FWIW, I blogged my answer to this question recently:
http://avdi.org/devblog/2008/10/21/testing-private-methods/

--
Avdi

Home: http://avdi.org
Developer Blog: http://avdi.org/devblog/
Twitter: http://twitter.com/avdi
Journal: http://avdi.livejournal.com
Avdi G. (Guest)
on 2008-10-24 01:11
(Received via mailing list)
On Thu, Oct 23, 2008 at 5:00 PM, Avdi G. <removed_email_address@domain.invalid> 
wrote:
> FWIW, I blogged my answer to this question recently:
> http://avdi.org/devblog/2008/10/21/testing-private-methods/

...and to be a little more concrete, at first glance the way I'd apply
that advice to your code example is to make a class that either
subclasses or wraps (via DelegateClass/SimpleDelegate) Array and adds
a #sub_total method, something like the following:

class Survey < ActiveRecord::Base
 has_many :participants
 ...
 def generate_reports
   ...
   sub_total = ReportCollection.new(participants.reports).sub_total
   ...
 end

  class ReportCollection < SimpleDelegator
    def sub_total
      sub_total = 0
      self.collect do |report|
        sub_total = sub_total + report.score
      end
     return sub_total
    end
  end

end

--
Avdi

Home: http://avdi.org
Developer Blog: http://avdi.org/devblog/
Twitter: http://twitter.com/avdi
Journal: http://avdi.livejournal.com
Michael L. (Guest)
on 2008-10-24 01:22
(Received via mailing list)
That works when running one at a time.  When I want to run all my
specs how do I ensure the requires are applied to the appropriate
features?  It is not all my stories, only a subset that are dual-
purposed.  Do I need to have directories for those that can be run
dual mode and those that are not, and then have a script that runs the
3 cases?

Michael
aslak hellesoy (Guest)
on 2008-10-24 01:55
(Received via mailing list)
On Thu, Oct 23, 2008 at 11:20 PM, Michael L. 
<removed_email_address@domain.invalid> wrote:
> That works when running one at a time.  When I want to run all my specs how
> do I ensure the requires are applied to the appropriate features?  It is not
> all my stories, only a subset that are dual-purposed.  Do I need to have
> directories for those that can be run dual mode and those that are not, and
> then have a script that runs the 3 cases?
>

You have to run once for each "environment". So if you have say
selenium and rails, you'll have to run all features that use selenium
in one run and those that use rails in another. (Of course you can
have overlap - some features that include run in both runs)

Aslak
aslak hellesoy (Guest)
on 2008-10-24 09:58
(Received via mailing list)
On Thu, Oct 23, 2008 at 7:20 PM, Michael L. 
<removed_email_address@domain.invalid> wrote:
> Does Cucumber deal with this any better than the story runner?  Given that
> it searches for steps rather than having them declared, I would think you
> can not share story files for different steps?
>

I've done this on a couple of projects.

steps/common/*_steps.rb
steps/watir/*_steps.rb
steps/rails/*_steps.rb

Set up two tasks and use --require. Or use --profile.

See
http://github.com/aslakhellesoy/cucumber/wikis/mig...
for details.

Aslak
This topic is locked and can not be replied to.