2

Very simple question:

package MyApp::Model::Foo;
use Moose;
use namespace::autoclean;
extends 'Catalyst::Model';
has 'firstname' => ( is => 'ro', isa => 'Str' ); # to be populated in config file

# ...

sub check_name {
  my $self = shift;
  my $firstname = $self->firstname;
  # ...
}

When I call check_name() from a test script, at the "$self->firstname" line I get the error Can't use string ("MyApp::Model::Foo") as a HASH ref while "strict refs" in use at reader MyApp::Model::Foo::firstname. How am I supposed to use this?

I can't reproduce the test stuff as it's too extensive, but by the time I run the test script, I've called a setup script that loads the Catalyst application (and thus reads the Catalyst config file), deploys and populates database tables, etc.

The test script worked fine in the original version (which did not take a value from the config file; that's what I'm trying to do now; originally I passed in a value), and the relevant bit is simply

my $name_check = MyApp::Model::Foo->check_name();
ok(defined $name_check, "Name is OK");
user1235777
  • 609
  • 7
  • 24
  • 1
    Please include the test script. Your `check_name` is correct. Something else must be wrong. – simbabque Oct 25 '16 at 15:17
  • 1
    It looks like you might be calling `check_name()` as a class method rather than an object method. But without seeing the call to `check_name()` we can't be sure. Please [edit your question](http://stackoverflow.com/posts/40243560/edit) to add the code that calls `check_name()`. – Dave Cross Oct 25 '16 at 15:34
  • 1
    I was thinking that too, but I also think it might be related to what's going on with the _config file_ there. Because in a test script, there wouldn't be a ConfigLoader plugin @Dave. – simbabque Oct 25 '16 at 15:35
  • 1
    Agreed. But without seeing the actual code this is all just guesswork :-) – Dave Cross Oct 25 '16 at 15:37
  • I've edited to discuss and show the test stuff. Thanks. – user1235777 Oct 25 '16 at 15:51
  • When you say test script, are we talking about unit tests? – simbabque Oct 25 '16 at 15:51
  • 1
    That's exactly what Dave said. You need to _instantiate_ first. Writing an answer. – simbabque Oct 25 '16 at 15:52

1 Answers1

3

It appears you want to do a kind-of unit-test, or maybe an integration test, and verify if your application is getting the correct data from the configuration file.

Catalyst components (Models, Views and Controller) are Moose objects, you've got that right. In order for them to have Moose magic (which isn't really magic), you need to instantiate them. You can't just call an accessor as a class method.

use MyApp::Model::Foo;
my $name_check = MyApp::Model::Foo->new->check_name();

But that wouldn't work, because now you've got a new instance of the model object, and it doesn't have the name set through the config.

Catalyst internally takes care of creating the objects for you, including their configuration. You said you have a Catalyst running. You can use Catalyst::Test to go in there, get a context object $c and then use the model accessor to get you the right kind of model object that has been given the configuration.

The ctx_request function lets Catalyst handle a request and returns the actual HTTP::Response object as well as the context object. You can then work with that context.

use Catalyst::Test 'MyApp';
use Test::More;

my ( $res, $c ) = ctx_request('/');
ok defined $c->model('Foo')->name, 'Name is defined';

You probably already have Catalyst::Test in your test stack somewhere. If not, you're doing something weird.

Note that this doesn't work if you want the session that's attached to a certain user, so if you have a Test::WWW::Mechanize::Catalyst or another user agent that has a session cookie, you will need to extract the cookie and build your own HTTP::Request object, than use the user agents cookie jar to put the cookie into that request before you pass it to ctx_request.

Also note that the test you're doing is not very useful, unless you're building the code that does the configuration reading. And even then, you can build unit tests that don't require a full running Catalyst.

simbabque
  • 53,749
  • 8
  • 73
  • 136
  • Just fix it @Dave ;) – simbabque Oct 25 '16 at 16:14
  • 1
    Thank you for this extremely detailed response, which allowed me to see what was happening. The test suite was in fact already set up as you suggest, with ctx_request. But most tests were not called through Catalyst, i.e. not using any Catalyst magic, so just gave the module name as `MyApp::Model::Foo`. In this case I did need to use all the Cat internals, so simply changing the call to `my $name_check = $c->model('Foo')->check_name()` was all that I needed. – user1235777 Oct 25 '16 at 16:24
  • @user1235777 if you want real unit tests then creating your own objects is fine. For Models and maybe Views that makes sense. For controllers I feel they rely to much on the Context to work well without responses, unless you have a lot of helpers in there. But then that's a good reason to refactor anyway. Most of the time I have specific test configurations for test suite. Using a local suffix and setting the ENV variable for it in your test runner is pretty neat. That's also why I wouldn't recommend a test just to check if a value got set by Catalyst. It will break alright if hadn't anyway.:) – simbabque Oct 25 '16 at 17:40