[Catalyst] Testing controller which require login.

Tomas Doran bobtfish at bobtfish.net
Wed May 13 09:41:00 GMT 2009


Louis Erickson wrote:
 > I'm probably going to have several questions in the next few days about
 > tests.

As usual in these parts, all questions welcome, although you implicitly 
volunteered to document anything not covered in the Manual/documentation 
already. Well volunteered in advance. ;_)

I'm going to number the suggested approaches as we go so I can summarise 
at the end...

> My tests are currently based on the stubs generated by myapp_create,
> and use Catalyst::Test.  I could move them to WWW::Mechanize::Catalyst if 
> it would help.

It generally makes things nicer, however I _do_ tend to just use 
Catalyst::Test to test login / logout / etc, as I run some basic 'does 
the app look sane' tests first up so that it's quick to just run the 
front end sanity checks when developing...

I've then got various Mech & Selenium tests (which are much more 
detailed and cover much more of the application / regressions etc) which 
run after that..

Technique 1:
> I can't tell if Catalyst::Test supports cookies.  Does it?  Or do I need 
> to use WWW::Mechanize::Catalyst for that?

It doesn't support cookies per-se, but it's not hard to handle this 
yourself. For a nice neat example against the tutorial of testing login, 
see:

http://github.com/bobtfish/catalyst-app-tutorial-kiffin-authissues/blob/cbb7f692676ecd51805dd7cc6cf4393ff6c208c5/t/01app.t

> I've read some suggestions on how to go about this, and found a couple.

Technique 2:

> 1> Use WWW::Mechanize::Catalyst to log the user in

Works exactly like you'd expect :)

Technique 3:

> 2> Use the mock user in config to set a user who is logged in.

The second example is also fairly possible. I've never gone the whole 
hog and had an entirely fake user object, although I do have tests which 
apply a custom MYAPP_CONFIG environment variable, and have config which 
overwrites my authentication config

> I can set a mock user, but then that user is always logged in.  Can that 
> mock user be changed by the test scripts, so I can have them log out, log 
> in as an administrator, log in as an unprivileged user, etc, and test the 
> pages perform correctly?  If so, I've missed that - it would work, I 
> think.

Well, sure:

package My::Mock::User;
use strict;
use warnings;
use base qw/Catalyst::Authentication::User/;

our $id;

sub id { $id }

our @roles;

sub { @roles }

etc..

Then, assuming your tests and application are running in the same 
process space, you can just say:

$My::Mock::User::id = 1;
$My::Mock::User::roles = qw/ admin /;

Technique 4:

> The SSO is available to me as a library.  I'm considering adding a login 
> page to the application, so the test scripts can log in to the SSO through 
> the application and solve the problem.  Is duplicating a little code (not 
> much, most of it is in a module) to make tests work a good idea?

That's also viable in my opinion.

I mean - you're not trying to test the SSO itself, just how users who 
have logged in with SSO behave, right?

In fact, if you add t/lib/MyApp/Controller/Login/Testing.pm

then that controller will only be loaded in your tests which have said;

use FindBin ();
use lib "$FindBin::Bin/lib";

> 
> I'd want the application-specific login page to only be available while 
> the test scripts are running.  In production it shouldn't exist or at the 
> worst redirect back to the real SSO login page.  Writing a function which 
> behaves differently in test seems like a source of errors to me.  Does 
> this sound possible?

As noted above, just locate the files inside the test suite, but in your 
application namespace. They'll not be installed with the rest of your 
app (if you install your apps), and won't be loaded except in your 
tests, or if you perl -It/lib script/myapp_server.pl yourself.

Technique 5:

> Can I just generate the cookie and put it in the cookie store directly 
> before the query is made to the application?  I can generate the cookie 
> with the SSO library.  I don't see a way to do that through either test 
> tool.

Erm, yes - this will also work..

Don't think at the Catalyst::Test level here - your cookie is stored as 
a row in DBIC (assuming you are using DBIC), just generate a row with a 
known cookie value before you start testing :)

> I'm trying to learn and do this in a maintainable, sensible way.  Any help 
> you can give is highly appreciated.

I think that number 1 needs better docs in Catalyst::Test, and is a 
viable technique, but painful to write all your testing in Catalyst::Test.

Number (2) just works. I'd note that I generally use Mech for 
walk-through type testing, and either Catalyst::Test to test specific 
regressions, or controller mocking if I _really_ need to to test stuff, 
and everything else is in my Model and can be tested in isolation..

3 + 5 would probably win for maintainability in my opinion, as they'd be 
best implemented generically as additional modules which other people 
can reuse for the relevant bits of Auth and Session respectively, and 
I'll be happy to maintain the patches you supply.

I'm more keen on 3 actually, as I guess that'd be the easiest way to 
avoid testing authentication at all where you want to get on with 
testing the rest of your app.

4 makes sense in some situations - you need testing of login / roles 
etc, but given you've already done that, the rest of your app testing 
not having to mess around with that stuff could be cleaner - although 
I'd be tempted to think you should just factor the 'hand me back a 
logged in user' code out into a test library, or that you should mock 
the login code out, rather than adding extra login code.

Thanks
t0m





More information about the Catalyst mailing list