Mojolicious's "automatic test" technique

Here are some techniques for doing "automatic testing" of Mojolicious.

How to debug a script with Mojolicious::Lite

Mojolicious::Lite makes it easy to debug your scripts. I am happy to take the test with a very short description. This is a great advantage when creating web apps with Mojolcious::Lite.

First of all, it is a simple web application created with Mojolicious::Lite.

use Mojolicious::Lite;

get'/' => sub {
  my $self = shift;

  $DB::single = 1;

  $self->render(text =>'Hello');
};;

app->start;

Save this file as "myapp.pl". The point to pay attention to is the description "$DB::single = 1". This is the notation for embedding breakpoints in scripts. If the debugger finds this line, it will stop at that position.

Next, create a test directory.

mkdir t

Place the test script in a directory called t. Try creating a script named "basic.t" in this directory.

use Test::More'no_plan';

use strict;
use warnings;
use utf8;

use Test::Mojo;

use FindBin;
$ENV {MOJO_HOME} = "$FindBin::Bin / ..";
require "$ENV {MOJO_HOME} /myapp.pl";

my $t = Test::Mojo->new;
$t->get_ok('/');

(Reference) FindBin

This script is written in Mojolicious::Lite's POD for automated testing, but it can also be used for debugging. This is because the process when actually accessing "/" is executed by the description "$t->get_ok('/')".

Next, let's start this test script using the debugger. It is better to execute the script from the directory where the script is stored, not from the t directory.

perl -d t / basic.t

If the module is placed in the lib directory, add the lib directory to the module search path as follows.

perl -d -Ilib t / basic.t

When the debugger starts, use the "c" command to go to the breakpoint described as "$DB::single = 1".

c

You can reach the position where you want to debug.

Five
6: $DB::single = 1;
7
8 ==> $self->render(text =>'Hello');
9:};
Ten
11: app->start;

Remembering this technique will make developing Mojolicious::Lite even easier.

Write a test to check if a page link is broken in Mojolicious

Define a method that looks like this: This is a test that gets the contents of the page, gets all the hyperlinks in it, and if 200 is returned when the link is accessed, it is OK. Please write after use Test::Mojo. $Test::Builder::Level is incremented by 1 to get information about the caller of the row that failed the test.

{
  package Test::Mojo;
  
  sub link_ok {
    my ($t, $url) = @_;
    
    local $Test::Builder::Level = $Test::Builder::Level + 1;
    
    my @links;
    $t->ua->get($url)->res->dom->find('a [href]')->each(sub {
      my $self = shift;
      
      push @links, $self->attrs->{href};
    });
    
    for my $link (@links) {
      $t->get_ok($link)->status_is(200) unless $link = ~ / logout /;
    }
  }
}

It looks like this when using it.

$t->link_ok('/ user / list');

Creating a test can be a hassle, but it's very resistant to code changes and gives you peace of mind.

Show the lines where the test failed in Mojolicious's automated test

When doing an automatic test with Mojolicious, it is very difficult to see in which line the test failed because the output content is large.

With good use of pipes and grep, you can only see the line numbers.

perl myapp.pl test 2> & 1 | grep line

Since the test result is output to the standard error output, pipe it into grep and extract only the line containing the line line.

Find out where the warning occurred during the automated test

When performing automated tests with Mojolicious, we often get warnings that the following undefined values ​​are being used.

Use of uninitialized value in concatenation (.) or string at (eval 520) line 94.

You have to pinpoint the location and find the location of the problem, but doing this on your own is difficult. In such cases, it's a good idea to convert the warning to an exception to get the caller's information. To convert a warning to an exception:

#Convert warning to exeption
use Carp'confess';
$SIG {__WARN__} = sub {confess $_ [0]};

If you write as above, you can write a callback when a warning occurs. confess can raise an exception to include the stack and race containing the caller in the message. It's a good idea to write this at the top of the automated test script.

Now that you know the line number in the script and what you're doing, you can often find the cause by dumping the data output by that process.

What to do if you get stuck in an infinite loop during a Mojolicious test

When developing with Mojolicious, I sometimes get stuck in an infinite loop because I accidentally called a method with the same name. I can't stop the server because I can't hear Ctrl + c.

In this case, press Ctrl + z once to switch the server to background execution. Then use the ps command to identify the process ID of the server and the -KILL signal to kill the process.

ps -ef | grep myapp.pl

kill -KILL process ID

Addition

I added the following opinions.

The reason that mojolicious often has to do ctrl + c is that the controller and the view are doing extra things respectively.

This is not due to Mojolicious, but when some functions of the controller class are separated into the base class, I forgot to call the method of the superclass and called the method with the same name as myself, which is a general infinite loop. It's object-oriented and makes mistakes. In normal programming, when an infinite loop occurs, the program ends with an error, but in the case of the server it can not be stopped, so I am writing a solution, Mojolicious is not doing strange things am.

How to know the exceptions that occurred during the automated test

When writing an automated test in Mojolicious, it's pretty good to know that the test didn't work and the template displayed Server error and gave an exception. In such a case, I wonder how to know the contents of the exception.

So, let me tell you one way.

Capture and output the contents of the exception

The content of the exception can be obtained from the stash key exception . Let's set up a hook and get this.

my $app = YourApp->new;
my $t = Test::Mojo->new($app);

$app->hook(after_dispatch => sub {
  my $c = shift;
  my $exception = $c->stash('exception');
  warn $exception if $exception;
});

Display test details with the test command

You can use the verbose option to view the details with the test command.

perl myapp.pl test --verbose

Start a server that can be connected via SSL for testing purposes

You may want to test SSL. Mojolicious has a test private key and a server certificate so you can start a server that connects via https. As a prerequisite, a module called IO::Socket::SSL is required, so install it.

cpan IO::Socket::SSL

You can connect via https by starting the server with the following options.

# Embedded standalone server
perl myapp.pl daemon --listen https: // *: 3000

#Test morbo server that automatically reloads
morbo myapp.pl --listen https: // *: 3000

(Reference) morbo

You can connect via https with the following URL.

https: // localhost: 3000
</p </p>re>

Of course, it's normal to listen on both HTTP and HTTPS, so it's a good idea to do the following:

<pre>
morbo myapp.pl --listen http: // *: 3000 --listen https: // *: 3001

SSL is supposed to connect to port 443 by default if no port is specified. But in a test environment, it listens for SSL requests on different ports.

But it's better to set environment variables than to do this. By doing this, you will be able to test using a different port for each user.

export MOJO_LISTEN = http: // *: 3000, https: // *: 3001

However, this is troublesome because you have to write different source code for the URL on the production machine and the development machine. Therefore, it is a good idea to create the following helper. If a port number exists, determine it in operation so that the port number obtained by adding 1 to it is the SSL port number.

Also, in production, do not specify the port, leave it to the default, HTTP is 80, HTTPS is 443.

If you register the following helpers, you can do it universally.

$app->helper(
  url_for_https => sub {
    my $self = shift;
    my $url = $self->url_for(@_)->to_abs->scheme('https');
    my $port = $url->port;
    $url->port($port + 1) if defined $port;
    return $url;
  }
);

Use CSS3 selector to check if HTML element exists

You can use the Test::Mojo element_exists method to test for the existence of HTML elements that match the CSS3 selector.

$t = $t->element_exists('div.foo [x = y]');
$t = $t->element_exists('html head title','has a title');

You can use the Test::Mojo element_exists_not method to test that there are no matching HTML elements in the CSS3 selector.

$t = $t->element_exists_not('div.foo [x = y]');
$t = $t->element_exists_not('html head title','has no title');

Test the text content of HTML elements using CSS3 selectors

You can use the Test::Mojo text_is method to test the text content of an HTML element that matches a CSS3 selector.

$t = $t->text_is('div.foo [x = y]'=>' Hello!');
$t = $t->text_is('html head title' =>'Hello!','Right title');

You can use the Test::Mojo text_isnt method to test the text content of HTML elements that don't match the CSS3 selector.

$t = $t->text_isnt('div.foo [x = y]'=>' Hello!');
$t = $t->text_isnt('html head title' =>'Hello!','Differ title');

You can use the Test::Mojo text_like method to test the text content of an HTML element that matches a CSS3 selector.

$t = $t->text_like('div.foo [x = y]' => qr / Hello /);
$t = $t->text_like('html head title' => qr / Hello /,'right title');

You can also use the Test::Mojo text_unlike method to test the text content of HTML elements that don't match the CSS3 selector.

$t = $t->text_unlike('div.foo [x = y]' => qr / Hello /);
$t = $t->text_unlike('html head title' => qr / Hello /,'different title');

Associated Information