TL;DR: if you call Str::createUuidsUsing(…) in a test method, don’t forget to call Str::createUuidsNormally() later in that same method (or the tearDown method), or the rest of your test suite will continue to use that same UUID.
While this is very useful, I expected that it would reset between tests, similar to Queue::fake(), Http::fake(), etc.
However, because of how the Str helper generates UUIDs, whatever you provide will be used for the rest of the test run. If you have other tests or app code that expects a unique UUID each time Str::uuid() is used, you may get unexpected results.
There are a couple of options to work around this:
After running the code that needs a UUID, call Str::createUuidsNormally() to reset the Str helper.
If you don’t actually need the value of the UUID for testing, you can wrap your code in the freezeUuids() method instead. Once your code in the callback finishes running, the framework will call createUuidsNormally() to reset everything for you:
TL;DR: don’t use ignored URL parameters when building signed URLs or the resulting signed URL will be invalid. Instead, manually append them to the resulting URL.
They allow you to generate a URL containing a signature that prevents anybody from modifying the URL to access something you didn’t intend (e.g., you could provide a signed URL for a specific post with ID 123; if somebody changed that ID to 124, then Laravel will display a 403 Signature Invalid error rather than happily displaying post 124).
Occasionally you may wish to ignore certain URL parameters when validating the signature (e.g., a pagination or print parameter).
In this case, you cannot include the ignored parameter when generating the signed URL, or the URL will be invalid.
Here’s an example. This route ignores the print parameter when verifying the signature:
If you generate a signed URL without the print parameter, it will be valid. But if you include print in the URL parameters for the helper method, the resulting signature will be invalid, because Laravel uses all of those parameters to generate the signature. Instead, just add the new parameter to the end of the resulting URL:
Note how examples 1 and 3 have the same signature; that is the signature that Laravel calculates when determining what the correct signature should be to verify that the URL has not been modified. The example 2 use print=true when generating the signature, but will remove that parameter when verifying the signature, so they don’t match.
Update: I submitted a PR to the framework to pass ignored parameters to the signed route methods to make this easier.
The Http::assertSent(), Event::assertDispatched(), and Queue::assertPushed()) test methods are perhaps a bit unintuitive.
They seem like they would run just once and check the assertions provided in your callback.
In fact, that callback runs once for each HTTP request (or dispatched event or queued job) and it evaluates the logic inside the callback for each. So if you have 10 requests (or events or jobs) and 3 of them return true it passes. If you have 10 requests and 9 of them return true it passes. It only fails if none of the callbacks return true.
So using dd($request) in one of those assertions is only dumping out the first one and then killing the test. You might not see the one you actually need.
If you’re trying to see what data is in the request, you’re better off either doing something like Log::debug($request->body()) or ray($request->url())1 or putting an xdebug breakpoint on the first line of the callback, running the test, and pressing the “continue” button until you get to the request you’re trying to inspect (possibly the second or fourth or tenth, depending on what happens before the one you want to inspect).
Here is a few code samples that may clarify this a bit more:
I’m in the process of adding Sentry to a Laravel app that uses Laravel Jetstream with Inertia.js and Vue 3, and the Sentry Vue 3 documentation wasn’t working for me because the app setup was wrapped inside a createInertiaApp function.
The key is to add Sentry in the setup method of that function:
Colin DeCarlo presented a talk at Laracon Online where among other useful tips, he demonstrated how to shim MySQL functions in an SQLite database (e.g., add functions that MySQL has but SQLite does not).
Here are two examples that I just needed in a project (FLOOR and DATEDIFF):
We’re using Laravel Socialite with a self-hosted GitLab instance to authenticate users for an internal tool.
Every time the session times out, the OAuth flow redirects the user back to the default dashboard, regardless of what URL the user originally requested.
However, Laravel provides a url.intended session key with the original URL; here’s how I used it, with a fallback to the dashboard URL:
I’ve recently been building an ecommerce app based on Laravel. Partway through development, we added geometry fields to a couple of tables in order to determine distances. I’ve been using this spatial package, so SQLite was not an option for my test suite.
As soon as I switched the testing database driver from SQLite to MariaDB, my tests immediately took an extra 12–13 seconds to run, regardless of whether I ran the entire test suite, a single file, or just one test.
This significantly lengthened the feedback loop when making changes to code and re-running tests.
So when I saw Jack Ellis mention that he uses MySQL for his test suite, it made me curious if he had the same issue.
He said that one of his test files runs 39 tests in < 2 seconds, so apparently it’s not been a problem for him.
Context
I’m using the LazilyRefreshDatabase trait added in Laravel 8.62.0 on my entire test suite
Many of my tables have constrained foreign keys referencing other tables
Comparisons
I decided to do some digging; here are comparisons using four different platforms for the same test in my application.
MariaDB
I’ve been using MariaDB as the main database platform on my development machine for years. Currently I’m on version 10.6.4.
A single test took 14s to run
An entire file with 16 tests took 19s to run
In-Memory SQLite Database
I temporarily disabled the geometry features and tried the in-memory SQLite database (DB_CONNECTION=:memory:); it performed much better for the same tests:
A single test too <1s to run
An entire file with 16 tests took ~7s to run
SQLite File Database
I then tried with an SQLite file (DB_CONNECTION=sqlite), and it performed about the same:
A single test took ~1s to run
An entire file with 16 tests took ~7s to run
MySQL 8
I have an installation of MySQL 8 set up for one app that uses some specific MySQL 8 and I figured why not give that a try too.
Here are the results:
A single test took ~1.5s to run
An entire file with 16 tests took ~6.5s to run
Summary
For some reason, MariaDB takes approximately 12–13 seconds to tear down and recreate the database before starting to run tests, but MySQL is much faster.
While testing MariaDB, I opened the raw data directory for the database, and noticed chunks of files being removed and recreated at a time, so perhaps the foreign key constraints are (part of) the culprit here.
I do have 77 databases with ~3800 tables in my MariaDB installation built up from various projects over the years. It seems unlikely, but theoretically possible, that the server size could be part of the problem too.
I think I’ll experiment with switching back to MySQL as my development platform of choice.
Have you run into this same issue? Have any tips or tricks? Let me know in the comments.
I build an oEmbed provider in a Laravel application the other day and needed to parse an arbitrary URL to determine the route and parameters passed in order to determine the response.
Since I already had the routes built for the possible URLs, I didn’t want to duplicate code and re-parse them.
Here’s how I ended up retrieving the route and parameters: