Basic Router Test

2016 September 5

Request routers are not created equal and some actually fail to do their job, like TreeRoute: a router I discovered because it was advertised as being faster than FastRoute. Find out if the request router you're using works correctly with these simple tests.

Introduction

Before I started the development of my router, I wrote assurance tests as a guideline to ensure that it would work correctly at the end of its development. I compiled some of them as Basic Router Test. You can benefit from them the same way I did if you are developing or have written your own router. Also if you are deciding on which router to use from a selection of routers.

Due to the importance of a request router as the first thing that gets executed in any web application, I believe there should be a standard test for all request routers. Basic Router Test can be the first step in that direction.

I selected several routers to undergo my test and the results are not what I expected. Find out how well they manage below.

Summary of test results

Router name Correctness Test 1 Correctness Test 2 Trailing Slash Test
ReiRouter PASSED PASSED PASSED
FastRoute PASSED PASSED PASSED
Phroute PASSED PASSED FAILED
Pux PASSED UNSUPPORTED PASSED
Symfony Routing PASSED PASSED PASSED
TreeRoute FAILED UNSUPPORTED FAILED

Correctness Test 1

The purpose of this test is to find out if a router can correctly resolve requests to static and dynamic routes, which is the most basic function of a general purpose router. A router that fails this test is unreliable because the failure indicates basic design problems.

This test involves the following routes:

  • Route1: /route/one
  • Route2: /route/{one}/two
  • Route3: /route/{one}/{two}/three
  • Route4: /route/{one}/two/{three}/four

Test subject must be able to resolve the following requests:

  • /route/one to Route1.
  • /route/one/two to Route2.
  • /route/one/two/three to Route3.
  • /route/one/two/three/four to Route4.
// Example ReiRouter code, adjust as needed for other routers
$router->add('/route/one','Route1');
$router->add('/route/:one/two','Route2');
$router->add('/route/:one/:two/three','Route3');
$router->add('/route/:one/two/:three/four','Route4');

$results = [
    $router->find('/route/one'),
    $router->find('/route/one/two'),
    $router->find('/route/one/two/three'),
    $router->find('/route/one/two/three/four'),
];

Test results:

Router nameResultsVerdict
ReiRouterRoute1, Route2, Route3, Route4PASSED
FastRouteRoute1, Route2, Route3, Route4PASSED
PhrouteRoute1, Route2, Route3, Route4PASSED
PuxRoute1, Route2, Route3, Route4PASSED
Symfony RoutingRoute1, Route2, Route3, Route4PASSED
TreeRouteRoute1, (not found), (not found), (not found)FAILED

Remarks

Any regex-based router should be able to pass this test easily and they did. Because this test focuses only on the most basic function, I am surprised that any router failed this test.

I included TreeRoute in my test because I found it advertised as being faster than FastRoute but in my opinion being faster with bugs is pointless. If you are using TreeRoute, I strongly recommend replacing it with another router until the problem is fixed.

Correctness Test 2

This test simply adds a catch-all route to the previous test. The purpose of this test is to find out if a router can correctly resolve requests to catch-all routes. A router that claims to support catch-all routes but fails this test is unreliable because the failure also indicates design problems.

This test involves the same routes as Correctness Test 1 with one addition:

  • Route5: /route/*

Test subject must be able to resolve the following request:

  • /route/one/two/three/four/five to Route5.
// Example ReiRouter code, adjust as needed for other routers
$router->add('/route/one','Route1');
$router->add('/route/:one/two','Route2');
$router->add('/route/:one/:two/three','Route3');
$router->add('/route/:one/two/:three/four','Route4');
$router->add('/route/*','Route5');

$result = $router->find('/route/one/two/three/four/five');

Test results:

Router nameResultVerdict
ReiRouterRoute5PASSED
FastRouteRoute5PASSED
PhrouteRoute5PASSED
Pux(not tested)UNSUPPORTED
Symfony RoutingRoute5PASSED
TreeRoute(not tested)UNSUPPORTED

Remarks

This is also an easy test for any regex-based router and the result shows. For Pux and TreeRoute, maybe I didn't look hard enough but I can't seem to find a catch-all syntax. It seems to be unsupported, so I don't consider it a failure.

Trailing Slash Test

The purpose of this test is to find out if a router can handle a path with trailing slash and a path without trailing slash as distinct. A router that fails this test cannot be used in applications that require distinct handling of trailing slash. Because application developers may have their own requirements regarding trailing slash, a router must not impose its own specific handling. That decision is the right of the application developer, not the router.

This test involves the following routes:

  • Route1: /static/route
  • Route2: /static/route/
  • Route3: /dynamic/{route}
  • Route4: /dynamic/{route}/

Test subject must be able to resolve the following requests:

  • /static/route to Route1
  • /static/route/ to Route2
  • /dynamic/route to Route3
  • /dynamic/route/ to Route4
// Example Cog Router code, adjust as needed for other routers
$router->add('/static/route', 'Route1');
$router->add('/static/route/', 'Route2');
$router->add('/dynamic/:route', 'Route3');
$router->add('/dynamic/:route/', 'Route4');

$results = [
    $router->find('/static/route'),
    $router->find('/static/route/'),
    $router->find('/dynamic/route'),
    $router->find('/dynamic/route/'),
];

Test results:

Router nameResultsVerdict
ReiRouterRoute1, Route2, Route3, Route4PASSED
FastRouteRoute1, Route2, Route3, Route4PASSED
PhrouteRoute1, Route1, Route3, Route3FAILED
PuxRoute1, Route2, Route3, Route4PASSED
Symfony RoutingRoute1, Route2, Route3, Route4PASSED
TreeRouteRoute2, Route2, Route4, Route4FAILED

Remarks

Without dissecting the source code, I can only guess why Phroute and TreeRoute failed this test. Also, it is surprising to see that Phroute failed because according to its documentation its route matching core is based on FastRoute, which handled this test without a problem.

Conclusion

When I started experimenting on other routers, I was merely confirming my assumption about how they work so I did not expect to release the results. Of course, the results surprised me and now I think there are more routers out there that aren't up to standard. Feel free to use Basic Router Test to weed them out.