• Given, When, Then — how I approach Test-driven development in Laravel

    Given, When, Then — how I approach Test-driven development in Laravel

    Laravel is an incredible PHP framework and the best starting point for pretty much any web-based application (if writing it in PHP, that is).

    Along with it’s many amazing features, comes a beautiful framework from which to test what you are building.

    For the longest time I cowered at the idea of writing automated tests for what I built. It was a way of working that was brought in by a previous workplace of mine and my brain fought against it for ages.

    Since that time a few years ago I slowly came to like the idea of testing. Then over the past year or so I have grown to love it.

    I have met some people that are incredibly talented developers, but for me made the prospect of automated testing both confusing and intimidating.

    That was until I came across Adam Wathan’s excellent Test Driven Laravel course. He made testing immediately approachable and broke it down into three distinct phases (per test): “Given”, “When” and “Then”. Also known as “Arrange”, “Act” and “Assert”. I forget which phrase he used, but either way the idea is like this.

    “Given” this environment

    The first step is to set up the “world” in which the test should happen.

    One example would be if you were building an API that would return PlayStation game data to you. In order to return games, there must be games there to return.

    In Laravel we have factories that we can create for quickly creating test entries for our models. Here is an example of a Game model that uses its factory to create a game for us:

    $game = Game::factory()->create([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);

    “When” this thing I want to test happens

    Here you would do the thing that you are testing.

    Maybe that is sending some data to an API endpoint in your application. Or perhaps you are testing a single utility class can do a specific action, so you call the method on that class.

    Here, let’s continue the idea of return games from an api call. We’ll use the $game variable from the previous example and access its ID to build our GET endpoint:

    $response = $this->json('get', '/api/games/' . $game->id,);

    Here the $response variable gets the response from the json get call, allowing you to later make assertions against it.

    “Then” I should see this particular outcome

    In this last step you would make assertions against what has happened. This could be checking if a record exists in a database with specific values, or asserting that an email got sent.

    Basically anything you need to make sure happened, or didn’t happen, for you to be sure you are getting your desired functionality.

    Let’s finish our game example by asserting that we got json back with the expected data. We do this by calling the appropriate method off of the $response variable from the previous example.

    $response->assertJson([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);

    The full example test code

    $game = Game::factory()->create([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);
    $response = $this->json('get', '/api/games/' . $game->id,);
    $response->assertJson([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);

    Much more to explore

    There is so much to automated testing and I’m still relatively new to it all myself.

    You can “fake” other things in your application in order to not run live things in tests. For example when testing emails are sent you don’t really want to be actually sending emails when you run your tests. Therefore you would “fake” the functionality of sending the mail.

    I hope that this post has been an easy-to-follow intro to how I myself approach testing.

    I have found that even as my tests have gotten more complex in certain situations, I still always stick to the same structural idea:

    1. Given this is the world my code lives in.
    2. When I perform this particular action.
    3. Then I should see this specific outcome.

  • The Internet Archive / Wayback Machine (http://web.archive.org/) is absolutely incredible. It has saved most of my old posts and pages. Been spending a week or so rescuing those posts and bringing them back.

    Fediverse reactions
  • Was nice to finally meet most of the jump24 crew in the flesh last night. Even if I did come second to last in bowling. šŸ˜… @jumptwenty4

    Fediverse reactions
  • Bowling with work mates.

  • What happened to the old library in Birmingham?

  • PHP Psalm warning for RouteServiceProvider configureRateLimiting method

    PHP Psalm warning for RouteServiceProvider configureRateLimiting method

    When running psalm in a Laravel project, I get the following error by default:

    PossiblyNullArgument - app/Providers/RouteServiceProvider.php:45:46 - 
    Argument 1 of Illuminate\Cache\RateLimiting\Limit::by cannot be null, 
    possibly null value provided

    This is the default implementation for configureRateLimiting in the RouteServiceProvider class in Laravel:

    protected function configureRateLimiting()
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });
    }

    I change it to the following to get psalm to pass (I’ve added named parameters and the static keyword before the callback function):

    protected function configureRateLimiting()
    {
        RateLimiter::for(name: 'api', callback: static function (Request $request) {
            $limitIdentifier = $request->user()?->id ?: $request->ip();
            if (!is_null($limitIdentifier)) {
                return Limit::perMinute(maxAttempts: 60)->by(key: $limitIdentifier);
            }
        });
    }
  • Really enjoyed this write-up. I’m trying to approach my own website like a personal commonplace book and digital garden now.

  • Been taking a few shots in Red Dead Redemption 2 recently. Will share them out very soon.

  • The Arch Wiki really is an incredible resource, regardless of what distro you’re running. Just got my video drivers setup correctly (I think) by just following the guide and the pages it took me to.

  • New Thinkpad T470 installed with base Arch Linux and ready to rice it up.

    Fediverse reactions
  • Website under construction

    I’ve always believed that people should own and control their own place on the web. That is, if they want to.

    I have actively kept a personal website for myself for about 10 years now.

    Gosh… 10 years…

    In that time I have moved between about 5 different domain names, different focus topics, and different reasoning behind why I want to publish online.

    I have finally settled now on this domain that you are now on – davidpeach.me.

    I’m also in the process of recovering my old posts that have been thrown by the wayside as I have dicked about changing domain names etc over the years.

    A mixture of Twitter archives, manual database backups I have kept, and some new sources I will be incorporating, mean I am finally going to settle down in this place online I am now calling home.

    I am also re-implementing aspects of the Indieweb movement for content ownership and communicating that to other websites.

    I owe a big thank you to Chris Aldrich too. As it was his website I came across that inspired me to bring my website back to what I have always wanted it to be. Hopefully, thanks to the indieweb helper plugins I have installed, Chris may just get notified on his website and post a reply back — from his website over to mine using the webmention protocol.

  • Sprinklings of Docker for local development

    Sprinklings of Docker for local development

    When I search for docker-related topics online, it almost seems to me that there are two trains of thought for the most part:

    • Those who use a full docker / docker-compose setup for local development.
    • Those who hate and/or fear docker and would rather just install and do everything locally.

    I believe either of these is a valid approach — whatever feels right to you. Of course it does also depend on how your company / team works.

    But I’d like to introduce you to a third way of working on a project — sprinklings of docker, I call it šŸ˜€.

    The idea is essentially to just use docker for certain things in a project as you develop it locally.

    This is how I tend to work, but is by no means what I would call “the right way”; it’s just what works best for me.

    How I work with Docker.

    I am primarily a Laravel developer, and work as such at the excellent company — and Laravel PartnerJump 24.

    As I am a php developer, it stands to reason that I have php installed on my system. I also have nginx installed, so I can run a php application locally and serve it at a local domain without needing docker.

    Historically, when I would need a MySQL database (which is often the case) I would have gotten MySQL installed on my system.

    Which is fine.

    But I’m becoming a bit of a neat freak in my older age and so want to keep my computer as clean as possible within reason.

    So what I do now is start a new docker container for MySQL and connect to that instead:

    # Bash command to start up a docker container with MySQL in it
    # And use port 33061 on my local machine to connect to it.
    docker run \
    --name=mysql \
    --publish 33061:3306 \
    --env MYSQL_DATABASE=my_disposable_db \
    --env MYSQL_ROOT_PASSWORD=password \
    --detach mysql

    Then in my Laravel .env configuration I would add this:

    DB_HOST=0.0.0.0:33061
    DB_DATABASE=my_disposable_db
    DB_USERNAME=root
    DB_PASSWORD=password

    The benefit of working this way is that if anything happens to my MySQL container — any corruptions or just ending up with a whole mess of databases old and new in there, I can just destroy the container and start a new one afresh.

    Not to mention when I want to upgrade the MySQL version im working with… or even test with a lower version.

    docker container stop mysql
    docker container rm mysql
    # And then re-run the "docker run" command above.
    # Or even run it with different variables / ports.

    The same goes for any other database engines too: Postgres; Redis; MariaDB. Any can just be started up on your system as a standalone Docker container and connected to easily from your website / app in development.

    # Start a Postgres container
    docker run \
    --name postgres \
    --publish 5480:5432 \
    --env POSTGRES_PASSWORD=password \
    --detach postgres:11-alpine
    
    # Start a redis container
    docker run \
    --name redis \
    --publish 6379:6379 \
    --detach redis
    
    # Start a Mariadb container
    docker run \
    --name some-mariadb \
    --publish 33062:3306 \
    --env MARIADB_USER=example-user \
    --env MARIADB_PASSWORD=my_cool_secret \
    --env MARIADB_ROOT_PASSWORD=my-secret-pw  \
    --detach mariadb

    And with them all being self-contained and able to be exposed to any port on the host machine, you could have as many as you wanted running at the same time… if you were so inclined.

    I love how this approach keeps my computer clean of extra programs. And how it makes it super easy to have multiple versions of the same thing installed at the same time.

    Docker doesn’t have to be scary when taken in small doses. 😊