FAQ
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?



Thanks for the help!
Cheers,
Kate

Search Discussions

  • Jason Galea at Mar 2, 2009 at 2:06 am
    Hi Kate,

    have a look at
    http://search.cpan.org/~jkutej/Catalyst-Plugin-CommandLine-0.05/lib/Catalyst/Plugin/CommandLine.pm

    I've only used it once or twice and I don't know if it's considered
    "best practice" or not but it worked for me.. (I wanted easy access to
    Catalyst app config)

    cheers,

    J

    package MyApp;
    use Catalyst qw/ CommandLine /;


    package main;
    use MyApp;
    my $c = MyApp->commandline;

    my $acc = $c->model('Account')->find(1);



    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?



    Thanks for the help!
    Cheers,
    Kate


    _______________________________________________
    List: Catalyst@lists.scsys.co.uk
    Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
    Searchable archive: http://www.mail-archive.com/catalyst@lists.scsys.co.uk/
    Dev site: http://dev.catalyst.perl.org/
  • Ian Docherty at Mar 2, 2009 at 11:11 am

    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.

    Your Catalyst Model would then look something like.

    package MyApp::Model:MyDB;

    use strict;
    use base 'Catalyst::Model::DBIC::Schema';

    my $dsn = 'dbi:mysql:port406:host=localhost';

    __PACKAGE__->config(
    schema_class => 'MyApp::Storage',
    connect_info => [
    $dsn.';dbname=my_database',
    'username',
    'password',
    { 'mysql_enable_utf8' => 1 },
    { on_connect_do =>[ 'set names utf8' ] },
    ],
    );


    Regards
    Ian
  • David Wright at Mar 2, 2009 at 12:59 pm

    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
  • Jay Shirley at Mar 2, 2009 at 2:30 pm

    On Mon, Mar 2, 2009 at 5:00 AM, David Wright wrote:

    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


    It's good advice to have things standalone and available outside of
    Catalyst, but your assessment of model dependencies stops short.

    What you want is an independent model that accepts a schema object for
    recording, and then a very thin adapter class inside of Catalyst.

    What I would do is create something like MyApp::Payment and
    MyApp::Storage::(Ledger|Order) -- then, in Catalyst have a
    MyApp::Model::Payment adapter class that sets the $schema object on the
    instantiated MyApp::Payment object as necessary.

    This way, you can still dedecouple everything from a Catalyst app. You
    could have the payment stuff actually encapsulate MyApp::Storage, but I
    don't see what that would give you.

    -J
    -------------- next part --------------
    An HTML attachment was scrubbed...
    URL: http://lists.scsys.co.uk/pipermail/catalyst/attachments/20090302/686b93f0/attachment.htm
  • David Wright at Mar 2, 2009 at 10:22 pm

    J. Shirley wrote:
    On Mon, Mar 2, 2009 at 5:00 AM, David Wright wrote:

    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




    It's good advice to have things standalone and available outside of
    Catalyst, but your assessment of model dependencies stops short.
    What you want is an independent model that accepts a schema object for
    recording, and then a very thin adapter class inside of Catalyst.
    And the config object, cache backends, the logger? The code above could
    be made more complex:

    sub pay {
    my ($self, $args) = @_;

    my $order = MyApp->cache('ordercache')->get( $args{id} ) ||
    do {
    my $o = MyApp->model('Order')->find( $args{id} ) ;
    MyApp->cache('ordercache')->set( $o->id, $o,
    MyApp->config->{cache_expiry});
    $o;
    };
    $o->update( { #blah } );
    MyApp->log->debug ("Got order");
    MyApp->model( 'Ledger' )->insert( { # blah } );
    }

    As I said, this 'model' class is very controller-like. Something has to
    deal with the interactions between those components, and I contend that
    it shouldn't be a standard Catalyst controller.

    Using something like this is the suggested better practice:

    package MyApp::Model::PaymentProcessor;
    use base 'Catalyst::Model::Adaptor';

    __PACKAGE__->config( class => 'MyApp::PaymentProcessor' );

    sub prepare_arguments {
    my ($self, $c) = @_;
    return { log => $c->log, expiry => $c->config->{cache_expiry}, cache
    => $c->cache, schema => $c->model->schema };
    }

    But this doesn't help much: now, when testing the constructors in my
    'decoupled' model, I have to verify that I am initializing the
    components in precisely the same way that Catalyst does. I'm not
    decoupled at all, I've just made things harder. There's a risk when the
    model is plugged in that it won't fit, and I'm back at square one with
    working out how to test.

    So, if I'm willing to swallow the overhead of 'use MyApp' in a script,
    and I'm pretty sure that my model will never be used outwith the app,
    and if it isn't really any easier to test if the model/controller thingy
    relies on other Catalyst components, why bother decoupling? I certainly
    don't want two independent deployments to deal with, neither of which do
    anything useful in isolation.

    David

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupcatalyst @
categoriescatalyst, perl
postedMar 1, '09 at 9:25p
activeMar 2, '09 at 10:22p
posts6
users5
websitecatalystframework.org
irc#catalyst

People

Translate

site design / logo © 2021 Grokbase