[Catalyst] getting $c in model unit test
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
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
--- 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
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
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
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
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
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
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
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/