Debugging HTTP Client Request Assertions in Laravel Test Suites

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:

My Favorite VS Code Extensions

In a given day, I tend to work primarily on Laravel apps, some using Livewire and some with Inertia.js and a Vue.js frontend, as well as a smattering of WordPress sites and/or custom plugins.

I’ve tried PhpStorm and didn’t care for it, so like what feels like 90% of the rest of the industry, I use VS Code as my primary editor.

Here’s a list of the extensions I use on a daily basis:

General

Sublime Text Keymap and Settings Importer: I used Sublime Text for a year or so and built up muscle memory for the keyboard shortcuts, so these make a lot more intuitive sense to me than the standard VS Code keyboard shortcuts.

Markdown Preview Mermaid Support: I like to document solution architecture using mermaid diagrams, and this is a great extension to preview these in VS code.

Path Intellisense provides autocompletion when typing relative file paths in a project.

EditorConfig for VS Code configures some editor settings for different projects based on the configuration stored in the project.

Prettier – Code formatter is useful for automatically formatting code files.

TODO Highlight v2 provides visual feedback for TODO/FIXME/etc. comments in code.

Encode Decode is extremely useful when dealing with encoded strings. I use it fairly frequently to decode base64-encoded strings.

Remote – SSH / Remote – SSH: Editing Configuration Files / Remote Explorer make it really easy to “cowboy code” on a server 😬 and are useful for occasional debugging in production.

Live Share is amazing for pair-programming: it allows you to open the same codebase your colleague is working on and work with it on your machine just as if it were a local project.

PHP

Composer shows you the actually-installed version of each package in your composer.json file, and gives you a quick link to the packagist.org page for each.

PHP DocBlocker reduces some of the boilerplate necessary when writing docblocks.

PHP Intelephense in my opinion is significantly better than the built-in PHP language support, providing autocompletion for functions, methods, variables, etc., project-wide parameter hints, and even some static analysis features.

PHPUnit Test Explorer is a very useful wrapper for phpunit; you can run a single test, a single file, or the entire test suite, and when combined with Test Explorer UI and Test Adapter Converter, shows a list of passed/failed tests in the sidebar. I use it frequently to run my entire test suite to see a quick list of which job(s) failed.

phpstan runs static analysis on files as I save them, showing my errors as I write code.

PHP Debug might just be the extension I interact with the most; it lets me set breakpoints, step through, and inspect code as it runs. I can’t imagine trying to program without it.

Laravel

DotEnv provides syntax highlighting for .env files used for Laravel configuration.

Laravel Extra Intellisense saves me a lot of time by auto-completing routes names and parameters, configuration keys, views and variables, validation rules, and more.

Laravel Blade Spacer automatically adds spaces when you add a new curly brace pair: just a minor code style convenience.

Livewire Language Support provides autocompletion and other features for Livewire projects.

Laravel goto view provides one-click access to views from controllers.

Laravel Blade Snippets provides Blade snippets and syntax highlighting.

Laravel Pint provides automatic code formatting using Pint.

Laravel Blade formatter provides formatting tools for Blade templates.

Javascript

Alpine.js IntelliSense provides intellisense and snippets for alpine.js.

Inertia.js provides support for linking to vue templates and autocompletes component names.

Other Languages

GraphQL: Syntax Highlighting provides language support for GraphQL files.

SCSS IntelliSense autocompletes mixins, functions, etc. in sass files.

YAML provides YAML language support.

SQL Beautify provides formatting support for SQL files. I don’t always love the output, but it’s better than nothing.

Vue Language Features (Volar) provides language, autocompletion, and other features for vue framework.

Markdown All in One provides keyboard shortcuts, formatting helpers, preview, and more for markdown files.

Tailwind CSS IntelliSense provides suggestions, highlights duplicates, and more for Tailwind class names.

Git

GitLens — Git supercharged provides some great features; my favorite is the code history on the active or hovered line.

GitLab Workflow is a wonderful integration with GitLab; I use the “copy active link to clipboard” feature daily to copy a permalink for specific line(s) when discussing code with my colleagues. It also provides helpful CI features including autocompletion and hints when editing .gitlab-ci.yml files, as well as showing pipeline/job status right in the VS Code status bar.

Adding Sentry to a Laravel/Inertia/Vue 3 app

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:

Shimming MySQL Functions into SQLite for Laravel CI/CD Testing

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):

use Illuminate\Support\Facades\DB;

DB::getPdo()->sqliteCreateFunction('floor', fn ($value) => floor($value));
DB::getPdo()->sqliteCreateFunction('datediff', fn ($date1, $date2) => Carbon::parse($date1)->diff(Carbon::parse($date2))->days);

Redirect to Original URL with Laravel Socialite

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:

return redirect()->to(
    session()->get('url.intended', route('dashboard')
);

Database Platform Comparisons for Laravel Feature Tests

TL;DR: MySQL significantly outperforms MariaDB in my automated test suite.

The Problem

This Twitter thread prompted me to do a bit of research on database platforms for Laravel automated tests.

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
  • I’m using squashed migrations
  • 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.

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:

SQLite File Database

I then tried with an SQLite file (DB_CONNECTION=sqlite), and it performed about the same:

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:

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.

Retrieving Route and Parameters from an Arbitrary URL in Laravel

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:

Migrating sermons from Sermon Manager for WordPress to SermonAudio

I build a Laravel-based command-line utility to import sermons from the Sermon Manager for WordPress plugin and migrate them into SermonAudio.

If it’s useful to you, see this repository for setup and usage details: https://gitlab.com/andrewminion/sermon-manager-to-sermon-audio

Using psysh in Shared Hosting or Limited User Environments

When using psysh or Laravel’s php artisan tinker in a limited user’s environment, you may run into this error:

Unable to create PsySH runtime directory. Make sure PHP is able to write to {some directory path} in order to continue.

This is caused by psysh trying to use a path that’s not accessible to the user.

To fix, add a file at ~/.config/psysh/config.php with this content:

<?php
return [
    'runtimeDir' => '~/tmp'
];

Using Laravel artisan tinker and psysh with Xdebug

I often use Xdebug for troubleshooting and interactively debugging local code as I write it.

Laravel’s artisan command is extremely useful for running code interactively during development. (It’s based on another utility named psysh.)

It can be very useful to set some debug breakpoints and then run code interactively using artisan, but occasionally when I run php artisan tinker, the PHP shell just sits there and doesn’t accept any input until I kill my xdebug listener.

Thanks to this issue, I finally have a solution.

Add this to the psysh config file (~/.config/psysh/config.php on macOS):

<?php
return [
  'usePcntl' => false,
];