Testing routes with Laravel-Localization

I recently added the Laravel-Localization package (with Laravel 5.2) to a project I’m working on. Initial setup went smoothly, and I was impressed with how well things worked essentially out of the box.

Then I tried to test my routes.

I had previously made a test case that ran through all of my public routes and asserted that each one returned HTTP status code 200 OK. Suddenly this was failing on every route I tried, saying that 404 Not Found was being returned instead.

The route worked in the browser. It returned the correct result using curl on the vagrant box. But it refused to work in the test. I had the application’s default locale set to ‘en’, and every indication was that it worked perfectly everywhere except in the tests. I pared the test right down:

The error was the same. The request was correctly updated from / to /en based on the default locale, but apparently Laravel insisted on returning 404 to the test. I would have understood if the status code were 301 or 302, since I’m using the LaravelLocalizationRedirectFilter middleware and expect there to be a redirect. But why would the route simply disappear?

To make a long debugging session short, it turns out that while LaravelLocalizationRedirectFilter middleware correctly leads any browser to make a second request for the new URL, the testing framework tries to follow redirects within the routes available to the same initial request object.

Standard operation of Laravel-Localization is to set up a route group with a prefix value of LaravelLocalization::setLocale(). When the browser visits the homepage initially, this function returns null so Route::get('/', ...) is matched, the middleware is invoked, and the browser is redirected to /en. The browser then invokes a second request, this time to /en as directed, which means setLocale() is called again, this time returning 'en'. So Route::get('/', ...) is now prefixed by 'en',  the requested route is matched, and everything works correctly.

In the test however, there is only ever a single request object, in which the prefix defined by setLocale() is simply ''. This matches the first time, the server runs the middleware and sends a redirect, but instead of making a second request, the testing framework tries to follow the redirect by resolving the new route against the RouteCollection in the original Request object. Since it was created with a prefix of '', any route prefixed by 'en' won’t exist.

So, how do we solve this? I’ve found two interim solutions so far, though I plan to keep looking another day.

Option 1 is to disable the LaravelLocalizationRedirectFilter middleware. This means that requests to un-locale-prefixed routes will work just fine, but won’t be redirected to their prefixed versions, resulting in two different URLs for each resource in the default language.

Option 2 is to set 'hideDefaultLocaleInURL' => true in the laravellocalization.php config file. This results in the redirection of all URLs prefixed with the default locale to unprefixed ones—/en becomes /, /en/users becomes /users, and so on. This is perhaps preferable to Option 1 from an SEO standpoint, but comes with the caveat that the Accept-Language header of any user’s browser will now be ignored.

Likely some other options exist that involve changing the requested routes in the test cases instead.