Help writing DRY test with shoulda

3 views
Skip to first unread message

Marcus Witt

unread,
Nov 11, 2009, 3:07:14 PM11/11/09
to shoulda
Currently I use the following -- Basically same set of assertions on a
bunch of pages.

context "a" do
should "test all pages" do
[post_list_path, post_show_path, post_edit_path].each{|page|
... assertions ...
}
end
end

I want to split that so that I want each test to deal with only one
page. So I tried,

context "a" do

[post_list_path, post_show_path, post_edit_path].each{|page|
define_method "test: this test should test page #{page}. " do
... assertions ...
end
}

I get undefined method post_list_path error. post_list_path is a
route. What am I doing wrong?

On a related note, if I define instance variables in a setup block in
a context, how can I access them to write multiple tests using a
similar idiom? I tried it and the variables are available only inside
the "should" block.

Thanks,

Marcus.



Dan Croak

unread,
Nov 11, 2009, 4:48:11 PM11/11/09
to sho...@googlegroups.com
I think this is a scoping issue. Routes aren't available at the class
level where you're defining the method.

You could change the Array to a Hash of names and string'd routes but
that's ugly. Overall, I'd say there's no real benefit to having a
separate test for each page. If they all share the same requirements,
then putting them in one should statement is fine.

should "respond successfully to all GETable pages" or whatever.
--
Dan Croak
@Croaky

Zach Dennis

unread,
Nov 11, 2009, 7:55:31 PM11/11/09
to sho...@googlegroups.com
On Wed, Nov 11, 2009 at 3:07 PM, Marcus Witt
<marketing...@gmail.com> wrote:
>
> Currently I use the following -- Basically same set of assertions on a
> bunch of pages.
>
> context "a" do
>  should "test all pages" do
>    [post_list_path, post_show_path, post_edit_path].each{|page|
>        ... assertions ...
>    }
>  end
> end
>
> I want to split that so that I want each test to deal with only one
> page. So I tried,
>
> context "a" do
>
>   [post_list_path, post_show_path, post_edit_path].each{|page|
>      define_method "test: this test should test page #{page}. " do
>        ... assertions ...
>      end
>   }
>
> I get undefined method post_list_path error. post_list_path is a
> route. What am I doing wrong?

It's definitely a scoping issue. The block you pass to #context is
evaluated at the class-level of the test, and the block passed to
#should is evaluated at instance level when the test class runs. The
#instance level has access to the route helpers, etc.

What, how, and why are you testing these pages? Example/test
descriptions that read like "this page is tested" raise a giant
warning flag. How is it tested, what are you verifying? etc.

I like what you're thinking in terms of making what you have be
clearer. What about, instead of looping over an array, instead extract
a macro method and declaratively use your macros for each page, ie:

page_should_render :post_list_path

At least then in your macro you can split things up into multiple
"should" calls with highly readable descriptions, ie:

page_should_render generates:
should "render header"
should "render footer"
should "include the title of the post"
etc...

When you had it working for one you now have a choice to update
#page_should_render accept multiple arguments, or you could simply
call #page_should_render more than once in your test.

>
> On a related note, if I define instance variables in a setup block in
> a context, how can I access them to write multiple tests using a
> similar idiom?  I tried it and the variables are available only inside
> the "should" block.

What are you trying to accomplish? A concrete example will surely shed
more light for you looking for an answer and for anyone looking to
provide you with one.




>
> Thanks,
>
> Marcus.
>
>
>
>
> >
>



--
Zach Dennis
http://www.continuousthinking.com (personal)
http://www.mutuallyhuman.com (hire me)
http://ideafoundry.info/behavior-driven-development (first rate BDD training)
@zachdennis (twitter)

Marcus Witt

unread,
Nov 11, 2009, 9:38:35 PM11/11/09
to shoulda
Dan and Zach - Thanks. I will play around with your suggestions.
Here's more background on what's driving this.

Overall, I am trying to move away from one big test that has lots of
assertions to many tests that test (ideally) just one thing. So
certainly looking for "fail early and often" rather than "fail rarely
but spectacularly". This approach can give a near complete list of
failures which are not dependent on the pass/fail result of the
previous *iteration*. So in my example if the test failed on the
second page, I still want the test to run on the third and fourth page
-- which does not happen in the current approach of looping within a
should block.

Zach - You have already answered your question. I am trying to test
for presence/absence of page elements that should be conditionally
present on the page. For example,
* "New Messages" link in the header shows only when you have a new
message
- new message flag is derived from the logged in user, not
controller logic.
* "Home" Tab shows on all pages for all users
* "Admin" Tab is seen only by admins and no other user

I also use this approach to permute through allowable values of a
method argument when the situation demands. For example, if I have an
account type attribute with permissible values = [1, 2, 3] and a
method

def account_status(account_type)
...
end

then I like to write a test that goes through all valid values of
account_types and illegal values of [nil, ""] etc. to make sure the
method behaves as expected. These tests take the form

[1, 2, 3].each do |value|
define_method "test: account_status should correctly handle
account_type of #{value}. " do
... assertions ..
end
end

[nil, ""],each do |value|
define_method "test: account_status gracefully handles illegal value
#{value}" do
... assertions ...
end
end

Doing this for the core classes gives you the peace of mind that every
corner and edge case is covered.

There are a few idioms that have yielded great dividends in improving
code quality and I will extract and publish it as a plugin if there is
interest.

Would certainly welcome your comments if there is a better way to do
this.

/MW
> Zach Dennishttp://www.continuousthinking.com(personal)http://www.mutuallyhuman.com(hire me)http://ideafoundry.info/behavior-driven-development(first rate BDD training)
> @zachdennis (twitter)

Dan Croak

unread,
Nov 12, 2009, 12:11:52 PM11/12/09
to sho...@googlegroups.com
On Wed, Nov 11, 2009 at 9:38 PM, Marcus Witt
<marketing...@gmail.com> wrote:

> Zach - You have already answered your question. I am trying to test
> for presence/absence of page elements that should be conditionally
> present on the page. For example,
>  * "New Messages" link in the header shows only when you have a new
> message
>      - new message flag is derived from the logged in user, not
> controller logic.
>  * "Home" Tab shows on all pages for all users
>  * "Admin" Tab is seen only by admins and no other user

Marcus,

The preferred approach among the people I've talked to is to do this
kind of "view" testing through Cucumber.

http://archive.patmaddox.com/blog/2009/1/15/how-i-test-controllers-2009-remix

Given I am signed in
And I have a new message
Then I should see "New Messages"

Given I am signed out
Then I should see "Home"
And I should not see "Admin"

Given I am signed in as a user
Then I should see "Home"
And I should not see "Admin"

Given I am signed in as an admin
Then I should see "Home"
And I should see "Admin"

--
Dan Croak
@Croaky

Reply all
Reply to author
Forward
0 new messages