21
Nov '23

by john

In 2023 there really is no excuse to be ignoring unit testing in your applications. When coming in to auditing a development project, my first question is always “What’s the testing strategy?”. More often than not the answer comes in as 10-20% unit test coverage, and that’s about it. Almost as often, when I come to look at the “unit tests” I find a lovely folder full of integration tests!

Now integration tests are very useful, don’t get me wrong, but they should not be the foundation of your testing strategy. Unit tests are there to test the most simple components within your codebase (usually down to individual function level), whereas Integration tests exist to test the interoperability of sections of these units.

Some programming frameworks make it virtually impossible to produce clean unit tests (**cough** Magento **cough**) but if your testing doesn’t being with the smallest practicable components of functionality then the rest of your strategy will be built on seriously unstable foundations.

Unit testing and integration testing are two distinct levels of software testing, each serving specific purposes in the software development life cycle. To clarify this distinction, here is a comparison between unit testing and integration testing:

Scope

  • Unit Testing: Focuses on testing individual units or components of a system in isolation. A unit is the smallest testable part of an application, such as a function or a method.
  • Integration Testing: Involves testing the interactions and interfaces between integrated components or systems. It ensures that the combined units function correctly as a group.

Purpose

  • Unit Testing: Aims to validate that each unit of the software performs as designed. It helps identify and fix bugs at an early stage of development and provides a foundation for higher-level testing.
  • Integration Testing: Verifies the correct interactions between integrated components and detects issues related to the interfaces and data flow between them.

Isolation

  • Unit Testing: Isolates individual units from the rest of the application, often through the use of mocks or stubs to simulate dependencies.
  • Integration Testing: Involves the collaboration of multiple units or components, checking how they work together and share data.

Dependencies

  • Unit Testing: Typically, dependencies are mocked or stubbed to isolate the unit under test. External systems or components are not involved.
  • Integration Testing: Involves real interactions between integrated components, including their dependencies on external systems, databases, or services.

Execution Speed

  • Unit Testing: Tends to be faster since it deals with small, isolated units of code.
  • Integration Testing: May take longer due to the need to set up and coordinate multiple components.

Feedback Loop

  • Unit Testing: Provides fast feedback to developers during the coding phase, enabling them to catch and fix issues early.
  • Integration Testing: Provides feedback on how different components work together, ensuring that the integration is correct and identifying issues that may arise when combining units.

Testing Levels

  • Unit Testing: A lower-level testing activity focused on the smallest parts of the software.
  • Integration Testing: A higher-level testing activity that comes after unit testing and before system testing.

Automation

  • Unit Testing: Highly automated, with tools and frameworks designed specifically for unit testing.
  • Integration Testing: Also automated, but may involve more complex setups and configurations, as it deals with multiple integrated components.

In practice, a comprehensive testing strategy should include both unit testing and integration testing to ensure the reliability and functionality of the entire system. Unit testing catches issues at the individual component level, while integration testing ensures that these components work seamlessly together.

29
Sep '23

by john

Behavior-Driven Development (BDD) and Test-Driven Development (TDD) are two methodologies that emphasize testing throughout the software development process. While they share common principles, they have distinct focuses and approaches. Here’s a comparison between BDD and TDD:

Focus

  • TDD: Primarily focuses on writing tests for individual units of code before the actual implementation. The focus is on validating the correctness of code at a granular level.
  • BDD: Shifts the focus from testing to the behavior of the system as a whole. It emphasizes collaboration between technical and non-technical stakeholders to define and understand the expected behavior of the system.

Language

  • TDD: Tests are written using the programming language of the application, often closely tied to the implementation details. The language is technical and geared towards developers.
  • BDD: Uses a natural language specification, often written in the Given-When-Then format. This language is more accessible to non-technical stakeholders and encourages collaboration between different roles in the team.

Stakeholders Involvement

  • TDD: Primarily involves developers and testers. Tests are written to ensure that the code meets specified requirements and functions correctly.
  • BDD: Involves a broader range of stakeholders, including non-technical team members such as product owners and business analysts. Scenarios are often written collaboratively to capture the expected behavior of the system.

Purpose

  • TDD: Aims to ensure that individual units of code are correct, maintainable, and well-designed. It follows the “Red-Green-Refactor” cycle to incrementally build the application.
  • BDD: Aims to ensure that the software behaves as expected from a business perspective. It helps bridge the communication gap between technical and non-technical stakeholders by using natural language specifications.

Levels of Abstraction

  • TDD: Works at a lower level of abstraction, focusing on the details of individual units or components of code.
  • BDD: Works at a higher level of abstraction, focusing on the overall behavior of the system and its interactions with external components.

Automation

  • TDD: Highly automated, with a strong emphasis on automated unit tests that developers write to validate their code.
  • BDD: Also involves automation, but the emphasis is on automating high-level acceptance tests derived from the natural language specifications.

Integration

  • TDD: Primarily concerned with unit testing, with the possibility of integration testing as well.
  • BDD: Encompasses higher-level acceptance testing, which may involve testing the integration of multiple components to ensure that they collectively deliver the desired behavior.

Tools

  • TDD: Utilizes testing frameworks specific to the programming language being used.
  • BDD: Often uses BDD frameworks and tools that support the Given-When-Then syntax, such as Cucumber, Behave, or SpecFlow.

In practice, these methodologies are not mutually exclusive, and teams often use them in conjunction. TDD can be employed for unit testing within the BDD framework, ensuring that individual units of code are well-tested while BDD scenarios capture and verify the overall behavior of the system.

28
Nov '20

by john

One of the sites I run is a craft business where we want to have a paginated product listing on the front page, but we also want it to be randomised, so that you don’t always get the same products when you first load it.

I wasn’t even sure this was possible when I began looking into it, but thanks to PHP’s ability to seed the randomisation functions, and a bit of careful tapping into Laravel’s core pagination library it is possible.

I’ve split the logic into two functions. One that generates a repeatably randomised set of product records, and another that gives the ability to use Laravel’s paginator on a Collection instance, rather than a query builder instance:

<?php

use Illuminate\Support\Collection;
use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator;

class ProductController extends \Controller
{
    /**
     * Product index page
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index()
    {
        return view('product.index')->with('title', 'Products')
                    ->with('products', $this->randomisedPaginatedProducts());
    }

    /**
     * Get randomised paginated products
     * @return \Collection
     */
    protected function randomisedPaginatedProducts() {

        if(session()->has('seed'))
            $seed = session('seed');
        else {
            $seed = rand(1, 100);
            session()->put('seed', $seed);
        }

        mt_srand($seed);

        $records = \App\Models\Product::get();

        $range = range(0, $records->count() - 1);
        shuffle($range);

        $randomised = collect($range)->map(function($index) use ($records) {
            return $records[$index];
        });

        return $this->paginateCollection($randomised);
    }

    /**
     * Paginate collection
     *
     * @param Collection $items
     * @param int $per_page
     * @param int|null $page
     * @param array $options (path, query, fragment, pageName)
     * @return LengthAwarePaginator
     */
    protected function paginateCollection(Collection $items, int $per_page = null, int $current_page = null, array $options = [])
    {
        $current_page = $current_page ?: (Paginator::resolveCurrentPage() ?: 1);

        return new LengthAwarePaginator($items->forPage($current_page, $per_page), $items->count(), $per_page, $current_page, $options);
    }
}

The key to this is the seed that we generate and keep in the session (we could equally pass it around in the pagination links if we wanted). The seed here is a number between 1 and 100, but it could be anything, really this just means that there are 100 different combinations of products it could come up with. This seed is used to generate a randomised array of numbers which will correspond to the numeric keys of the list of products, and then the products are sorted into a new array accordingly. Pass these to the paginator and you’re done.

Do be aware that this method requires you to pull all the products out of the database upfront, but for a relatively small total product count it’s no issue.

04
Sep '20

by john

I’m putting this out here right now, as I just spend two hours banging my head against a wall, becoming more and more convinced that witchcraft was afoot.

If you are using boostrap tabs, and have a set displaying generally correctly, but with just one or two that won’t open no matter what you try then I may have the answer for you:

If your target pane ID begins with a digit then it WILL NOT WORK.

Whack on a prefix and all will come right. Good lord that was frustrating!

25
Apr '20

by john

I make a lot of use of bootstrap modals in my code, and I find it incredibly useful to load the content dynamically, rather than have everything embedded in the page just in case it may be needed. To do this I tend to have a modal container (sometimes multiple, allowing for different sizes or styles), and target it from the trigger link.

<a href="/remote/content" class="btn btn-xs btn-primary modal-toggle" data-toggle="modal" data-target="#remote-modal" title="Load remote modal">Load Modal</a>

<div class="modal fade remote-modal" id="remote-modal" role="dialog" aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">

        </div>
    </div>
</div>

Then all it takes is a modal show listener that grabs the URL from the trigger, and loads the content via AJAX into the modal-content section. This also gives me the opportunity to add a new modal event that is fired when the content has completed loading; ‘loaded.bs.modal’. This new event is hugely useful as the core ‘shown.bs.modal’ event will often trigger before the content is loaded.

$('.remote-modal').on('show.bs.modal', function (e) {
    var trigger = $(e.relatedTarget);
    var modal = $(this);

    $(this).find('.modal-content').load(trigger.attr('href'), function() {
        modal.trigger('loaded.bs.modal', []);
    });
});

Simple as that.

03
Mar '20

by john

I try to return the correct HTTP codes from my AJAX calls whenever possible, and I often use these for different purposes, for example a 401 indicating that a session has expired, and a 403 meaning that the permissions are lacking on the current session.
These can be caught and acted on very easily with jQuery:

$(document).ajaxError(function(event, jqxhr, settings, exception) {
    if(jqxhr.status == 401) {
        alert('Unathorised');
    }
    else if(jqxhr.status == 403) {
        alert('Forbidden');
    }
    else if(jqxhr.status >= 500) {
        alert('An error occurred');
    }
});
26
Jan '20

by john

When working with Eloquent recordsets, I tend to expect them to behave exactly like core Laravel collections. Indeed why wouldn’t they? Why wouldn’t a recordset exist as a collection, with all the properties and functions that come with them?
On the most part you can use an Eloquent collection just like a core Laravel collection, however some functions have different behaviours, such as the ‘merge’ function, which in Eloquent is hard-wired to key by the record primary key, and will overwrite a duplicate key, even if the records are otherwise different.

Enter the completely undocumented ‘toBase’ function.

This is buried within the Eloquent collection class, and will spit out the recordset as a core Laravel collection, and allow you to perform all the normal collection operations in the manner you would expect.

13
Dec '19

by john

Very quick one, because this took me waaaaaaay too long to figure out, due to the error message having no apparent relation to the issue. If you get presented with the error:

Fatal error: Uncaught RuntimeException: A facade root has not been set.

Then this probably means that there is a syntax error in one of your config files.
I hope that’ll save a few hours of head-scratching!

05
Oct '19

by john

We’ve all been there. Customer after customer calls saying they haven’t received the email notifications they were expecting from a system. The issues are pretty much always an incorrect address or an over-enthusiastic spam filter, but even so, I decided to start logging all the notifications sent from my applications. If I can track dates, times, senders, and recipients then I’ve got all the evidence I need if there is a query.

Starting with a table for the logs, allowing for tracking of the notifiable record, the recipient user and the sender:

Schema::create('log_notifications', function ($table) {
    $table->increments('id')->unsigned();
    $table->integer('parent_id');
    $table->string('parent_type');
    $table->integer('user_id')->unsigned()->index();
    $table->integer('sender_id')->unsigned()->index();
    $table->string('email');
    $table->string('type');
    $table->timestamps();

    $table->index(['parent_id', 'parent_type']);
});

Then the main code is a trait that can be used by all notifiable models:

trait Notifiable {

    use \Illuminate\Notifications\Notifiable {
        notify as coreNotify;
    }

    /**
     * Log notification relationship
     */
    public function logNotifications()
    {
        return $this->morphMany('\App\Model\LogNotification', 'parent');
    }

    /**
     * Get Sender ID attribute
     * @param  mixed  $value Original value
     * @return integer
     */
    public function getNotifiableSenderIdAttribute($value) {
        if($value)
            return $value;

        return \Auth::check() ? \Auth::user()->id : 0;
    }

    /**
     * Get User ID attribute
     * @param  mixed  $value Original value
     * @return integer
     */
    public function getNotifiableUserIdAttribute($value) {
        if($value)
            return $value;

        if(in_array(class_basename(get_called_class()), ['User']))
            return $this->id;

        return \Auth::check() ? \Auth::user()->id : 0;
    }

    /**
     * Send the given notification
     * @param  mixed  $notification
     * @return void
     */
    public function notify($instance)
    {
        $this->logNotifications()->create([
            'user_id' => $this->notifiable_user_id,
            'sender_id' => $this->notifiable_sender_id,
            'email' => $this->email,
            'type' => get_class($notification)
        ]);

        $this->coreNotify($notification);
    }
}

This assumes that you have made a model for the notification logs at ‘\App\Model\LogNotification’, but you can change that to however your architecture is set up.

It also assumes a ‘User’ model class that contains your application users. It may be that it’s only your User model that will be notifiable, in which case you can happily drop the polymorphic parent record as it should always match your user_id.

And there you go. not the most eath-shattering of scripts, but definitely a good one to help ensure your notifications are behaving themselves.

13
Sep '19

by john

I recently decided that (after far too long) I needed to get up-to-date with how I manage my package dependencies for one of my bigger projects. I’d been using gulp with bower components hooked into Laravel Elixir basically since I first ported the project to Laravel, and the deprecation was getting beyond a joke.

Despite there being tons of information out there on how to do achieve individual parts of the configuration, nowhere could I find a single example that would show me how to configure NPM for compiling assets to work with Laravel Mix (the version of elixir) and give me a ‘watch’ option to auto-compile new asset changes.

The first job was to rename my gulpfile.js to webpack.mix.js, and then convert the syntax to use Laravel Mix, rather than Elixir. I’m not going to go into the details of that as it’s extremely straightforward.

Removing bower was simply a case of finding the relevant packages in npm, and amending the paths in the webpack.mix.js accordingly.

The hardest part was getting NPM’s package.json setup with everything I needed. All references to gulp and laravel-elixir came out, webpack and webpack-cli went in along with npm-watch and laravel-mix. My full package.json is below:

{
  "private": true,
  "devDependencies": {
    "resolve-url-loader": "^3.1.0",
    "sass-loader": "^7.3.1",
    "webpack": "^4.39.3",
    "webpack-cli": "^3.3.8"
  },
  "dependencies": {
    "@fortawesome/fontawesome-free": "^5.10.2",
    "@storyous/responsive-bootstrap-toolkit": "^2.6.1",
    "babel-core": "^6.26.3",
    "bootstrap": "^3.4.1",
    "bootstrap-datepicker": "^1.6.4",
    "bootstrap-sass": "^3.3.7",
    "clipboard": "^2.0.1",
    "datatables": "^1.10.18",
    "datatables-responsive": "^1.0.7",
    "datatables.net": "^1.10.19",
    "datatables.net-bs": "^1.10.19",
    "datatables.net-plugins": "^1.10.19",
    "graceful-fs": "^4.0.0",
    "jquery": "^3.4.1",
    "jquery-ui": "^1.12.1",
    "jquery-ui-dist": "^1.12.1",
    "jquery-validation": "^1.19.1",
    "laravel-mix": "^4.1.4",
    "node-sass": "^4.12.0",
    "npm-watch": "^0.6.0",
    "pnotify": "^4.0.0",
    "requirejs": "^2.3.6",
    "select2": "^4.0.10"
  },
  "watch": {
    "dev": {
      "patterns": [
        "resources/assets"
      ],
      "extensions": "js,css",
      "quiet": false,
      "delay": 1000,
      "runOnChangeOnly": true
    }
  },
  "scripts": {
    "dev": "node_modules/.bin/webpack --config=node_modules/laravel-mix/setup/webpack.config.js",
    "start": "npm run dev",
    "watch": "node_modules/.bin/npm-watch"
  }
}

With packages in place the last part was to set up the script definitions to compile my assets. The definitions shown will allow the following commands:

npm run dev – Compiles all assets following the script in webpack.min.js

npm run watch dev – Launches an active task that watches all CSS and JS files in resources/assets for changes, and recompiles whenever changes are detected.

So that’s a working overview start to finish. Hopefully it helps to see the setup in its entirety. It’s a really slick system once it’s all configured, but good grief it takes some effort to get there!