[Catalyst] getting $c in model unit test

2007-05-15 Thread Nathan Gray
I would like to test a model with a unit test.  Catalyst kindly
generates stub unit tests for models, but it does not include a stub
showing how to instantiate the context object.

I have looked at:

  - http://dev.catalystframework.org/wiki/Testing#ModelTests
(Examples of test for your model: STUB)
  - 
http://search.cpan.org/dist/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/Testing.pod
(TIP: For unit tests vs. the "full application tests" approach used
  by Test::WWW::Mechanize::Catalyst, see Catalyst::Test.)
  - http://search.cpan.org/perldoc?Catalyst%3A%3ATest
(METHODS: get request local_request remote_request)
  - http://search.cpan.org/perldoc?Catalyst%3A%3ADevel
(The documentation remains with Catalyst::Runtime.)
  - http://search.cpan.org/perldoc?Catalyst%3A%3ARuntime
($c->prepare( @arguments ) Creates a Catalyst context from an 
engine-specific request (Apache, CGI, etc.).)

Does anyone have an example unit test that has access to $c?

Thanks so much!

-kolibrie



signature.asc
Description: Digital signature
___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-15 Thread Matt Lawrence
Nathan Gray wrote:
> I would like to test a model with a unit test.  Catalyst kindly
> generates stub unit tests for models, but it does not include a stub
> showing how to instantiate the context object.
>
>   
I think I mentioned this on another thread earlier on. Something about
Chuck Norris if memory serves. Anyway:

my $controller = MyApp->controller('MyController');
my $c = MyApp->prepare();

# Monkey with $c to set up a fake context (set req->uri, or params)

my $result = $c->forward($controller, 'method_to_test', [EMAIL PROTECTED]);


Matt


___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-15 Thread John Napiorkowski
--- Nathan Gray <[EMAIL PROTECTED]> wrote:

> I would like to test a model with a unit test. 
> Catalyst kindly
> generates stub unit tests for models, but it does
> not include a stub
> showing how to instantiate the context object.
> 
> I have looked at:
> 
>   -
>
http://dev.catalystframework.org/wiki/Testing#ModelTests
> (Examples of test for your model: STUB)
>   -
>
http://search.cpan.org/dist/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/Testing.pod
> (TIP: For unit tests vs. the "full application
> tests" approach used
>   by Test::WWW::Mechanize::Catalyst, see
> Catalyst::Test.)
>   -
> http://search.cpan.org/perldoc?Catalyst%3A%3ATest
> (METHODS: get request local_request
> remote_request)
>   -
> http://search.cpan.org/perldoc?Catalyst%3A%3ADevel
> (The documentation remains with
> Catalyst::Runtime.)
>   -
> http://search.cpan.org/perldoc?Catalyst%3A%3ARuntime
> ($c->prepare( @arguments ) Creates a Catalyst
> context from an engine-specific request (Apache,
> CGI, etc.).)
> 
> Does anyone have an example unit test that has
> access to $c?
> 
> Thanks so much!
> 
> -kolibrie

Hi,

I'm assuming that what you mean is that you want to
test the model without running catalyst as it's
container.  I'm not sure there is a good way to do
this.   What I usually do is make my model a stand
alone perl class that can be tested individually and
then have a thin Catalyst model that wraps and
provides it to my webapp.

For things that would normally go into the catalyst
context, like say $c->user, I try to not access this
in my model directly.  Instead I wrap it in an adapter
class, that gets generated via a factory interface. 
That way I can create versions of this class that can
live outside of catalyst.

So something like:

MyApp::User  is a base User Class
MyApp::CatalystUser  creates a User from $c->user
MyApp::StandaloneUser  creates a user from params

MyApp::UserFactory  returns a MyApp::User based on
what you have

MyApp::WelcomeEmailUser  Expects a User class and
sends a welcome email

MyApp::Web::Model::User  A catalylst model that wraps
the UserFactory for you.

So MyApp::Web::Model::User might do something like

__PACKAGE__->mk_accessors(qw/user/);

sub ACCEPT_CONTEXT
{
 my ( $self, $c ) = @_;
 my $user = MyApp::UserFactory->create($c->user);

 $self->user($user);
 return $self;
}

and in a catalyst controller you do:

my $user = $c->model('User')->user;
MyApp::WelcomeEmailUser->mail($user);

While in your unit test you can:

my $user = MyApp::UserFactory->create({
  name=>"x",
  email=>"[EMAIL PROTECTED]",
});

my $email_status =
MyApp::WelcomeEmailUser->mail($user);



There's probably an easier and more concise way to do
this but right this minute I'm still on my first
coffee of the day.  Hopefully some smarter people will
chime in and correct me.

--john

__
Do You Yahoo!?
Tired of spam?  Yahoo! Mail has the best spam protection around 
http://mail.yahoo.com 

___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-15 Thread Jonathan T. Rockway
On Tue, May 15, 2007 at 09:48:05AM -0400, Nathan Gray wrote:
> Does anyone have an example unit test that has access to $c?

As others have mentioned, this is probably a sign of a poorly designed
API.  If you decide to continue further, you'll probably want to use
Test::MockObject to setup a fake Catalyst app/context.  I have a class
called Angerwhale::Test::Application (code is on CPAN) that will setup a fake
Angerwhale context:

   sub context {
   my $args = shift;
   my $config = $args->{config};
   
   my $c = Test::MockObject->new;
   $c->set_always( 'stash', {} );
   
   $config = { %{$config||{}},
   %{LoadFile('root/resources.yml')||{}}
 };
   
   $c->set_always( 'config', $config );
   
   # fake logging (doesn't do anything)
   my $log = Test::MockObject->new;
   $log->set_always( 'debug', undef );
   $c->set_always( 'log', $log);
   
   # fake cache (always generates a cache miss)
   my $cache = Test::MockObject->new;
   $cache->set_always( 'get', undef );
   $cache->set_always( 'set', undef );
   $c->set_always( 'cache', $cache );
   
   # TODO: model / etc.

   return $c;
   }

As you can see, I only set up the stuff I use; in this case the stash,
a log, and $c->cache.  If I use something else, the test will die and I
can investigate why.

I also have a model function that returns an instance
of a model (and could be generalized to return any Component):

   sub model {
   my $name = shift;
   croak "need name" unless $name;
   my $args = shift;
   my $context = $args->{context} || context();
   
   $name =~ s/\W//g;
   $name = "Angerwhale::Model::$name";
   
   eval "require $name";
   croak "error loading $name: $@" if $@;
   
   my $model;
   eval {
   $model = $name->COMPONENT($context, $args->{args});
   };
   croak "didn't get a model: $@" if $@ || !$model;
   
   return $model;
   }

There's more than one way to do this, but it works for me.

Regards,
Jonathan Rockway

___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-15 Thread Nathan Gray
On Tue, May 15, 2007 at 03:32:31PM +0100, Matt Lawrence wrote:
> Nathan Gray wrote:
> > I would like to test a model with a unit test.  Catalyst kindly
> > generates stub unit tests for models, but it does not include a stub
> > showing how to instantiate the context object.
> >
> I think I mentioned this on another thread earlier on. Something about
> Chuck Norris if memory serves. Anyway:
> 
> my $controller = MyApp->controller('MyController');
> my $c = MyApp->prepare();
> 
> # Monkey with $c to set up a fake context (set req->uri, or params)
> 
> my $result = $c->forward($controller, 'method_to_test', [EMAIL PROTECTED]);

Thank you.

We should probably add something like this to the autogenerated model
test stub, which appears to work:

  use MyApp;
  my $model = MyApp->model('MyModel');
  my $c = MyApp->prepare();

I tried to use the 'forward' method, but it did not want to cooperate
(Modification of non-creatable array value attempted, subscript -1 at
/usr/share/perl5/Catalyst/Dispatcher.pm line 186, which is: 'my
$namespace = $c->stack->[-1]->namespace;').

So rather than stubbing this example code:

  # Test each method of your model
  #is(
  #$c->forward($model, 'methodname_here', [EMAIL PROTECTED]),
  #$expected_value_here,
  #$test_description_here,
  #);

we may want to stub in an example like this:

  # Test each method of your model
  #is(
  #$model->methodname_here($c, @args_here),
  #$expected_value_here,
  #$test_description_here,
  #);

-kolibrie



signature.asc
Description: Digital signature
___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-16 Thread Matt Lawrence
Nathan Gray wrote:
> On Tue, May 15, 2007 at 03:32:31PM +0100, Matt Lawrence wrote:
>   
>> Nathan Gray wrote:
>> 
>>> I would like to test a model with a unit test.  Catalyst kindly
>>> generates stub unit tests for models, but it does not include a stub
>>> showing how to instantiate the context object.
>>>
>>>   
>> I think I mentioned this on another thread earlier on. Something about
>> Chuck Norris if memory serves. Anyway:
>>
>> my $controller = MyApp->controller('MyController');
>> my $c = MyApp->prepare();
>>
>> # Monkey with $c to set up a fake context (set req->uri, or params)
>>
>> my $result = $c->forward($controller, 'method_to_test', [EMAIL PROTECTED]);
>> 
>
> Thank you.
>
> We should probably add something like this to the autogenerated model
> test stub, which appears to work:
>
>   use MyApp;
>   my $model = MyApp->model('MyModel');
>   my $c = MyApp->prepare();
>
> I tried to use the 'forward' method, but it did not want to cooperate
> (Modification of non-creatable array value attempted, subscript -1 at
> /usr/share/perl5/Catalyst/Dispatcher.pm line 186, which is: 'my
> $namespace = $c->stack->[-1]->namespace;').
>   
Ah yes. You don't normally forward to a Model. If you're testing a
controller (or, I suppose, a view), it's best to forward, otherwise you
get a similar error if the controller method you're calling forwards to
another method itself.
> So rather than stubbing this example code:
>
>   # Test each method of your model
>   #is(
>   #$c->forward($model, 'methodname_here', [EMAIL PROTECTED]),
>   #$expected_value_here,
>   #$test_description_here,
>   #);
>
> we may want to stub in an example like this:
>
>   # Test each method of your model
>   #is(
>   #$model->methodname_here($c, @args_here),
>   #$expected_value_here,
>   #$test_description_here,
>   #);
>   
Seems reasonable to me.

Cheers,

Matt

___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-17 Thread Matt S Trout
On Wed, May 16, 2007 at 10:11:56AM +0100, Matt Lawrence wrote:
> Ah yes. You don't normally forward to a Model. If you're testing a
> controller (or, I suppose, a view), it's best to forward, otherwise you
> get a similar error if the controller method you're calling forwards to
> another method itself.

Maybe that means there's a case for adding a context($uri) export to C::Test

-- 
  Matt S Trout   Need help with your Catalyst or DBIx::Class project?
   Technical DirectorWant a managed development or deployment platform?
 Shadowcat Systems Ltd.  Contact mst (at) shadowcatsystems.co.uk for a quote
http://chainsawblues.vox.com/ http://www.shadowcatsystems.co.uk/ 

___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-17 Thread Matt Lawrence
Matt S Trout wrote:
> On Wed, May 16, 2007 at 10:11:56AM +0100, Matt Lawrence wrote:
>   
>> Ah yes. You don't normally forward to a Model. If you're testing a
>> controller (or, I suppose, a view), it's best to forward, otherwise you
>> get a similar error if the controller method you're calling forwards to
>> another method itself.
>> 
>
> Maybe that means there's a case for adding a context($uri) export to C::Test
>
>   

I suppose that would come in useful in some situations, but the question
is how much stuff would get done before the context gets returned. You
could stop before you call the action, having called the relevant begin
and auto methods, but then what could you usefully test other than the
action itself?

If you return the context after the request has been finalised, it would
be useful for ensuring the internal state is as you expect, as well as
the actual response. But it might not be so useful for unit testing to
have a finalised context.


Matt


___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-17 Thread Matt S Trout
On Thu, May 17, 2007 at 05:42:03PM +0100, Matt Lawrence wrote:
> Matt S Trout wrote:
> > On Wed, May 16, 2007 at 10:11:56AM +0100, Matt Lawrence wrote:
> >   
> >> Ah yes. You don't normally forward to a Model. If you're testing a
> >> controller (or, I suppose, a view), it's best to forward, otherwise you
> >> get a similar error if the controller method you're calling forwards to
> >> another method itself.
> >> 
> >
> > Maybe that means there's a case for adding a context($uri) export to C::Test
> >
> >   
> 
> I suppose that would come in useful in some situations, but the question
> is how much stuff would get done before the context gets returned. You
> could stop before you call the action, having called the relevant begin
> and auto methods, but then what could you usefully test other than the
> action itself?

Erm. I was more thinking returning it after a prepare but with ->req populated.

The idea for me would be for unit testing controllers/views (and any model code
that uses ACCEPT_CONTEXT).

Of course, if you wanted you could then call ->dispatch and then ->finalize
yourself, but once you're getting as far as that you're probably better using
the standard stuff (plus replacing the view with a mock one via
$c->_components->{MyApp::View::Foo} = $test_view to handle testing just the
controller ops and stash contents)

-- 
  Matt S Trout   Need help with your Catalyst or DBIx::Class project?
   Technical DirectorWant a managed development or deployment platform?
 Shadowcat Systems Ltd.  Contact mst (at) shadowcatsystems.co.uk for a quote
http://chainsawblues.vox.com/ http://www.shadowcatsystems.co.uk/ 

___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/


Re: [Catalyst] getting $c in model unit test

2007-05-18 Thread Matt Lawrence
Matt S Trout wrote:
> Erm. I was more thinking returning it after a prepare but with ->req 
> populated.
>
> The idea for me would be for unit testing controllers/views (and any model 
> code
> that uses ACCEPT_CONTEXT).
>
> Of course, if you wanted you could then call ->dispatch and then ->finalize
> yourself, but once you're getting as far as that you're probably better using
> the standard stuff (plus replacing the view with a mock one via
> $c->_components->{MyApp::View::Foo} = $test_view to handle testing just the
> controller ops and stash contents)
>   

Fair enough. Sounds like a plan. Looks like it would be almost the same
as Catalyst::Test::request but calling prepare() instead of handle_request.

I've made a quick patch, minimally tested.

Matt

--- lib/Catalyst/Test.pm	2007/05/18 08:57:16	1.1
+++ lib/Catalyst/Test.pm	2007/05/18 09:18:22
@@ -100,25 +100,51 @@
 my $caller = caller(0);
 *{"$caller\::request"} = $request;
 *{"$caller\::get"} = $get;
+
+# Context is always local
+*{"$caller\::context"} = sub { context($class, @_) };
 }
 
 =head2 local_request
 
 =cut
 
-sub local_request {
-my $class = shift;
-
+# Set up a CGI environment
+sub _setup_env {
 require HTTP::Request::AsCGI;
 
 my $request = Catalyst::Utils::request( shift(@_) );
-my $cgi = HTTP::Request::AsCGI->new( $request, %ENV )->setup;
+return HTTP::Request::AsCGI->new( $request, %ENV )->setup;
+}
+
+sub local_request {
+my $class = shift;
+
+my $cgi = _setup_env(@_);
 
 $class->handle_request;
 
 return $cgi->restore->response;
 }
 
+=head2 context
+
+$c = context($url)
+
+Returns a context object based on the given URL. This is always local.
+
+=cut
+
+sub context {
+my $class = shift;
+
+my $cgi = _setup_env(@_);
+
+return $class->prepare;
+
+# environment restored by $cgi going out of scope here
+}
+
 my $agent;
 
 =head2 remote_request
___
List: Catalyst@lists.rawmode.org
Listinfo: http://lists.rawmode.org/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/catalyst@lists.rawmode.org/
Dev site: http://dev.catalyst.perl.org/