I currently expand my test suite to increase the test coverage. I want to test my controller and the html output that it renders, but I found a problem in using delete methods. Let me explain it in an example.
I have a route:
$r->delete('/backups/:id')
->to('backup#delete_backup')
->name('backup_delete');
that points to the following function in the backup
controller:
sub delete_backup {
my $self = shift;
my $id = $self->param('id');
if ( something ) {
$self->flash( msg => "Backup id $id deleted!" );
}
else{
$self->flash( msg => "Cannot delete, backup id $id not found!" );
}
$self->redirect_to($self->url_for('backup_index'));
}
where the method that handles the route backup_index
just displays the $msg
and shows few other irrelevant data.
Next, I want to test this method, so I write a test:
$t_logged_in->ua->max_redirects(3);
my $page = $t_logged_in->app->url_for( 'backup_delete', id => $backup_id );
$t_logged_in->delete_ok($page)
->status_isnt( 404, "Checking: 404 $page" )
->status_isnt( 500, "Checking: 500 $page" );
The test is passed. But now, I want to check if the text is correct on the web page that is shown after redirecting. So I do the following:
$t_logged_in->ua->max_redirects(3);
my $page = $t_logged_in->app->url_for( 'backup_delete', id => $backup_id );
$t_logged_in->delete_ok($page)
->status_isnt( 404, "Checking: 404 $page" )
->status_isnt( 500, "Checking: 500 $page" )
->content_unlike(qr/Cannot delete,/i)
->content_like(qr/deleted/i);
The test fails. It fails because the content is empty, so the matching is done as there were:
'' =~ /deleted/i;
'' !~ /Cannot delete,/i;
and this is of course false in both cases. Of course, in the browser, the redirects work perfectly and I see everything as designed in the test. I can change the method to POST
or GET
but I wanted to make the routing properly in the way an API would be designed.
Question: how to design the test such that the content can be matched after the redirect?
For those who want to dig deeper, I give links to Github.
Sorry no one answered this yet. I used to try to keep a closer eye on stack overflow but I got lazy :-P. This is a very interesting question.
Standard redirects (301/302) use the same verb as the original request UNLESS the original request was a
POST
Interestingly, this behavior was not really intended. A redirect is supposed to use the same request method as the original, but thePOST
->GET
redirect is so commonly intended that most browsers added this behavior even though it was against the original definition. However some people really wanted aPOST
to remain aPOST
and now it depended on the implementation. In fact this got so bad that HTTP added response statuses 307/308 which always redirects as the same http verb (iePOST
->POST
), even thought that was already the official behavior of the old ones but that ship had sailed. This means thatPOST
->GET
, butDELETE
->DELETE
is the defacto behavior of the 301/302.This can be demonstrated in the following one liner (
$_
in these one-liners is the controller):You can see that since my
/
route handles all methods we get a response, however the request was aDELETE
. I see that in your app, you redirect to another named route whose url also handlesDELETE
. This is a source of your confusion as you are actually ending up there in your tests. Your redirect is effectivelyDELETE /backups/<<id>>
->DELETE /backups
, if I'm reading your code correctly.Now as opposed to the people who wanted to keep
POST
asPOST
, what you want here is the opposite, you want to redirect fromDELETE
toGET
because you are trying to show a response that is not the original resource. This is the defined behavior of the little-known 303 response in which any request method is redirected to aGET
. Indeed this is what early browsers should have insisted on rather than breaking the 302.In the following example I modify the response code to 303 explicitly.
But whoops, that's still a
DELETE
! That's because there was a bug in Mojo::UserAgent so that it didn't handle 303s correctly. I'd suggest you still make the change to your code since browsers seem to handle 303s correctly. However since Mojo::UserAgent powers Test::Mojo, your tests can't test that behavior yet. Once fixed you will see it work correctly, as my example does in my local branch:To see more about redirect response types see https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection