[Catalyst] testing catalyst app - need context

David Wright dave-catalyst at dexy.org
Mon Mar 2 13:00:35 GMT 2009


Ian Docherty wrote:
> Kate Yoak wrote:
>> Hi there,
>>
>>
>> Here is a newbie question:
>>
>> I like to test my functionality in bits and pieces as I write it.  How
>> do I go about getting myself the context object in a test script?
>>
>> For example, one of the tests catalyst installs is t/model_App.t where
>> it loads the model.  I'd love to then be able to use the model the same
>> way a controller would:
>>
>> my $acc = $c->model('Account')->find(1);
>>
>> Instead, I am doing
>> my $model = MyApp::Model::App->new();
>> my $acc = $model->recordset('Account')->find(1);
>>
>> In addition to being less than ideal because I am doing something
>> different that I expect real application code to do, it presents
>> configuration problems.  Like, turns out, in order for config to take
>> effect, I have to run __PACKAGE__->setup; after configuring it - which
>> won't be necessary, I think, in a real app.
>>
>> Is the practice of unit tests that break up the layers of catalyst
>> frowned upon? Or what should I do to make it work right?
>>   
> Best practice is to keep your model separate from Catalyst so that you 
> can (for example) create batch scripts or cron
> jobs that work on the model without having to load the whole of Catalyst
>
> Your model then would be in something like MyApp::Storage and accessed 
> by your tests as so...
>
> my $schema = MyApp::Storage->connect(
>    'DBI:mysql:host=localhost;database=my_database',
>    'username',
>    'password',
>    { 'mysql_enable_utf8' => 1 },
>    {on_connect_do =>[ 'set names utf8' ] }
> );
> my $acc = $schema->resultset('Account')->find(1);
>
> You would be testing your database layer separately from Catalyst.
Models aren't necessarily database layers.

I'd suggest that with a sufficiently rich Catalyst application, the 
'model' as known by Catalyst could easily become an effective 
'controller' of a further model. That secondary controller is going to 
want to load model classes, and Catalyst provides a nice way of 
controlling instantiation.

e.g.

sub handle_payment : Path {
   my ($self, $c, @args) = @_;

   my $proc = $c->model('PaymentProcessor');
   my $result = $proc->pay( { currency => $args[0], amount => $args[1], 
id => $args[2] } );

   $c->stash->{result}->{message} = $result->message;
   $c->stash->{result}->{code} = $result->code;
}

# then, within PaymentProcessor
 sub pay {
    my ($self, $args) = @_;

    MyApp->model( 'Ledger' )->insert( { # blah } );
    MyApp->model('Order')->update( { # blah } ) ;
 }
 
This definitely does tightly couple the PaymentProcessor to the 
application. However, it also allows for better whitebox testing. If 
your logic is embedded within a Catalyst controller, the only sensible 
way to test it is to make an HTTP request. If it's in the model, the 
individual steps can be tested.

FWIW, the principle here is "Catalyst controllers should do nothing 
except validate and translate HTTP parameters to 'model' parameters. 
They shouldn't manipulate model objects, or build complicated logic by 
combinations of model calls".

Regards,
David
 



More information about the Catalyst mailing list