Matthew Daly's Blog

I'm a web developer in Norfolk. This is my blog...

8th September 2017 10:05 pm

Installing Nginx Unit on Ubuntu

Recently Nginx announced the release of the first beta of Unit, an application server that supports Python, PHP and Go, with support coming for Java, Node.js and Ruby.

The really interesting part is that not only does it support more than one language, but Unit can be configured by making HTTP requests, rather than by editing config files. This makes it potentially very interesting to web developers like myself who have worked in multiple languages - I could use it to serve a Python or PHP web app, simply by making different requests during the setup process. I can see this being a boon for SaaS providers - you could pick up the language from a file, much like the runtime.txt used by Heroku, and set up the application on the fly.

It’s currently in public beta, and there are packages for Ubuntu, so I decided to try it out. I’ve created the Ansible role below to set up Unit on an Ubuntu 16.04 server or VM:

---
- name: Install keys
apt_key: url=http://nginx.org/keys/nginx_signing.key state=present
- name: Setup main repo
apt_repository: repo='deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx' state=present
- name: Setup source rep
apt_repository: repo='deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx' state=present
- name: Update system
apt: upgrade=full update_cache=yes
- name: Install dependencies
apt: name={{ item }} state=present
with_items:
- nginx
- unit
- golang
- php-dev
- php7.0-dev
- libphp-embed
- libphp7.0-embed
- python-dev
- python3
- python3-dev
- php7.0-cli
- php7.0-mcrypt
- php7.0-pgsql
- php7.0-sqlite3
- php7.0-opcache
- php7.0-curl
- php7.0-mbstring
- php7.0-dom
- php7.0-xml
- php7.0-zip
- php7.0-bcmath
- name: Copy over Nginx configuration
copy: src=nginx.conf dest=/etc/nginx/sites-available/default owner=root group=root mode=0644

Note the section that copies over the Nginx config file. Here is that file:

upstream unit_backend {
server 127.0.0.1:8300;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
fastcgi_param HTTP_PROXY "";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
root /var/www/public;
index index.php index.html index.htm;
server_name server_domain_or_IP;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri /index.php =404;
proxy_pass http://unit_backend;
proxy_set_header Host $host;
}
}

This setup proxies all dynamic requests to the Unit backed in a similar fashion to how it would normally pass it to PHP-FPM.

There were still a few little issues. It doesn’t exactly help that the Nginx package provided with this repository isn’t quite the same as the one in Ubuntu by default - not only is it the unstable version, but it doesn’t set up the sites-available and sites-enabled folders, so I had to do that manually. I also had an issue with Systemd starting the process (at /run/control.unit.sock) with permissions that didn’t allow Nginx to access it. I’m not that familiar with Systemd so I wound up just setting the permissions of the file manually, but that doesn’t persist between restarts. I expect this issue isn’t that big a deal to someone more familiar with Systemd, but I haven’t been able to resolve it yet.

I decided to try it out with a Laravel application. I created a new Laravel app and set it up with the web root at /var/www. I then saved the following configuration for it as app.json:

{
"listeners": {
"*:8300": {
"application": "myapp"
}
},
"applications": {
"myapp": {
"type": "php",
"workers": 20,
"user": "www-data",
"group": "www-data",
"root": "/var/www/public",
"index": "index.php"
}
}
}

This is fairly basic, but a good example of how you configure an application with Unit. The listener section maps a port to an application, while the applications section defines an application called myapp. In this case, we specify that the type should be php. Note that each platform has slightly different options - for instance, the Python type doesn’t have the index or root options, instead having the path option, which specifies the path to the wsgi.py file.

I then ran the following command to upload the file:

$ curl -X PUT -d @app.json --unix-socket /run/control.unit.sock http://localhost

Note that we send it direct to the Unix socket file - this way we don’t have to expose the API to the outside. After this was done, the Laravel app began working as expected.

We can then make a GET request to view the configured applications:

$ curl --unix-socket /run/control.unit.sock http://localhost/
{
"listeners": {
"*:8300": {
"application": "saas"
}
},
"applications": {
"saas": {
"type": "php",
"workers": 20,
"user": "www-data",
"group": "www-data",
"root": "/var/www/public",
"index": "index.php"
}
}
}

It’s also possible to update and delete existing applications via the API using PUT and DELETE requests.

Final thoughts

This is way too early to be seriously considering using Unit in production. It’s only just been released as a public beta, and it’s a bit fiddly to set up. However, it has an enormous amount of promise.

One thing I can’t really see right now is whether it’s possible to use a virtualenv with it for Python applications. In the Python community it’s standard practice to use Virtualenv to isolate the dependencies for individual applications, and it’s not clear how I’d be able to go about using this, if it is possible. For deploying Python applications, lack of virtualenv support would be a deal breaker, and I hope this gets clarified soon.

I’d also be curious to see benchmarks of how it compares to something like PHP-FPM. It’s quite possible that it may be less performant than other solutions. However, I will keep a close eye on this in future.

2nd September 2017 2:45 pm

Making Internal Requests With Laravel

Recently I’ve been working on a Phonegap app that needs to work offline. The nature of relational databases can often make this tricky if you’re dealing with related objects and you’re trying to retrofit it to something that wasn’t built with this use case in mind.

Originally my plan was to push each request that would have been made to a queue in WebSQL, and then on reconnect, make every request individually. It quickly became apparent, however, that this approach had a few problems:

  • If one request failed, the remaining requests had to be stopped from executing
  • It didn’t allow for storing the failed transactions in a way that made them easy to retrieve

Instead, I decided to create a single sync endpoint for the API that would accept an object containing all the requests that would be made, and then step through each one. If it failed, it would get the failed request and all subsequent ones in the object, and store them in the database. That way, even if the data didn’t sync correctly, it wasn’t lost, and if necessary it could be resolved manually.

Since the necessary API endpoints already existed, and were thoroughly tested, it was not a good idea to start duplicating that functionality. Instead, I implemented the functionality to carry out internal requests, and I thought I’d share how you can do this.

For any service you may build for your Laravel applications, it’s a good idea to create an interface for it first:

<?php
namespace App\Contracts;
interface MakesInternalRequests
{
/**
* Make an internal request
*
* @param string $action The HTTP verb to use.
* @param string $resource The API resource to look up.
* @param array $data The request body.
* @return \Illuminate\Http\Response
*/
public function request(string $action, string $resource, array $data = []);
}

That way you can resolve the service using dependency injection, making it trivial to replace it with a mock when testing.

Now, actually making an internal request is pretty easy. You get the app instance (you can do so by resolving it using dependency injection as I do below, or call the app() helper). Then you put together the request you want to make and pass it as an argument to the app’s handle() method:

<?php
namespace App\Services;
use Illuminate\Http\Request;
use App\Contracts\MakesInternalRequests;
use Illuminate\Foundation\Application;
use App\Exceptions\FailedInternalRequestException;
/**
* Internal request service
*/
class InternalRequest implements MakesInternalRequests
{
/**
* The app instance
*
* @var $app
*/
protected $app;
/**
* Constructor
*
* @param Application $app The app instance.
* @return void
*/
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Make an internal request
*
* @param string $action The HTTP verb to use.
* @param string $resource The API resource to look up.
* @param array $data The request body.
* @throws FailedInternalRequestException Request could not be synced.
* @return \Illuminate\Http\Response
*/
public function request(string $action, string $resource, array $data = [])
{
// Create request
$request = Request::create('/api/' . $resource, $action, $data, [], [], [
'HTTP_Accept' => 'application/json',
]);
// Get response
$response = $this->app->handle($request);
if ($response->getStatusCode() >= 400) {
throw new FailedInternalRequestException($request, $response);
}
// Dispatch the request
return $response;
}
}

Also note that I’ve created a custom exception, called FailedInternalRequestException. This is fired if the status code returned from the internal requests is greater than or equal to 400 (thus denoting an error):

<?php
namespace App\Exceptions;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
/**
* Exception for when a bulk sync job fails
*/
class FailedInternalRequestException extends \Exception
{
/**
* Request instance
*
* @var $request
*/
protected $request;
/**
* Response instance
*
* @var $response
*/
protected $response;
/**
* Constructor
*
* @param Request $request The request object.
* @param Response $response The response object.
* @return void
*/
public function __construct(Request $request, Response $response)
{
parent::__construct();
$this->request = $request;
$this->response = $response;
}
/**
* Get request object
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get response object
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
}

You can catch this exception in an appropriate place and handle it as you wish. Now, if you import the internal request class as $dispatcher, you can just call $dispatcher->request($action, $resource, $data), where $action is the HTTP verb, $resource is the API resource to send to, and $data is the data to send.

It’s actually quite rare to have to do this. In this case, because this was a REST API and every request made to it was changing the state of the application (there were no GET requests, only POST, PUT, PATCH and DELETE), it made sense to just break down the request body and do internal requests against the existing API, since otherwise I’d have to duplicate the existing functionality. I would not recommend this approach for something like fetching data to render a page on the server side, as there are more efficient ways of accomplishing it. In all honesty I can’t think of any other scenario where this would genuinely be the best option. However, it worked well for my use case and allowed me to implement this functionality quickly and simply.

19th August 2017 3:40 pm

Run Your Tests Locally With Sismo

Continuous integration is a veritable boon when working on any large software project. However, the popularity of distributed version control systems like Git over the older, more centralised ones like Subversion means that when you commit your changes, they don’t necessarily get pushed up to a remote repository immediately. While this is a good thing because it means you can commit at any stage without worrying about pushing up changes that break everyone else’s build, it has the downside that the tests aren’t automatically run on every commit, just every push, so if you get sloppy about running your tests before every commit you can more easily get caught out. In addition, a full CI server like Jenkins is a rather large piece of software that you don’t really want to run locally if you can help it, and has a lot of functionality you don’t need.

Sismo is a small, simple continuous integration server, implemented in PHP, that’s ideal for running locally. You can set it up to run your tests on every commit, and it has an easy-to-use web interface. Although it’s a PHP application, there’s no reason why you couldn’t use it to run tests for projects in other languages, and because it’s focused solely on running your test suite without many of the other features of more advanced CI solutions, it’s a good fit for local use. Here I’ll show you how I use it.

Setting up Sismo

Nowadays I don’t generally install a web server on a computer directly, preferring to use Vagrant or the dev server as appropriate, so Sismo generally doesn’t have to coexist with anything else. I normally install PHP7’s FastCGI implementation and Nginx, along with the SQLite bindings (which Sismo needs):

$ sudo apt-get install nginx php7.0-fpm php7.0-sqlite3

Then we can set up our Nginx config at /etc/nginx/sites-available/default:

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
fastcgi_param HTTP_PROXY "";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
root /var/www/html;
index sismo.php index.html index.htm;
server_name server_domain_or_IP;
location / {
try_files $uri $uri/ /sismo.php?$query_string;
}
location ~ \.php$ {
try_files $uri /sismo.php =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_index sismo.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SISMO_DATA_PATH "/home/matthew/.sismo/data";
fastcgi_param SISMO_CONFIG_PATH "/home/matthew/.sismo/config.php";
include fastcgi_params;
}
}

You’ll probably want to adjust the paths as appropriate. Then set up the required folders:

$ mkdir ~/.sismo
$ mkdir ~/.sismo/data
$ touch ~/.sismo/config.php
$ chmod -R a+w ~/.sismo/

Then, download Sismo and put it in your web root (here it’s at /var/www/html/sismo.php).

Now, say you have a project you want to test (I’m using my Laravel ETag middleware for this example). We need to specify the projects we want to test in ~/.sismo/config.php:

<?php
$projects = array();
$notifier = new Sismo\Notifier\DBusNotifier();
Sismo\Project::setDefaultCommand('if [ -f composer.json ]; then composer install; fi && vendor/bin/phpunit');
$projects[] = new Sismo\GithubProject('Laravel ETag Middleware', '/home/matthew/Projects/laravel-etag-middleware', $notifier);
return $projects;

Hopefully this shouldn’t be too difficult to understand. We create an array of projects, then specify a notifier (this is Linux-specific - refer to the documentation for using Growl on Mac OS). Next, we specify that by default the tests should run composer install followed by vendor/bin/phpunit. We then specify this project is a Github project - it also supports Bitbucket, or plain SSH, or the default Project, but in general it shouldn’t be a problem to use it with any repository as you can just run it against the local copy. Finally we return the list of projects.

Now, we should be able to run our tests as follows:

$ php /var/www/html/sismo.php build
Building Project "Laravel ETag Middleware" (into "68a087")

That should be working, but it doesn’t get us anything we don’t get by running the tests ourselves. To trigger the build, we need to set up a post-commit hook for our project in .git/hooks/post-commit:

#!/bin/sh
php /var/www/html/sismo.php --quiet --force build laravel-etag-middleware `git log -1 HEAD --pretty="%H"` &>/dev/null &

You should now be able to view your project in the Sismo web interface at http://localhost:

Sismo

Clicking on the project should take you through to its build history:

Sismo project page

From here on, it should be straightforward to add new projects as and when necessary. Because you can change the command on a per-project basis, you can quite happily use it to run tests for Python or Node.js projects as well as PHP ones, and it’s not hard to configure it.

I personally find it very useful to have something in place to run my tests on every commit like this, and while you could just use a post-commit hook for that, this approach is less obtrusive because it doesn’t force you to wait around for your test suite to finish.

14th August 2017 12:40 pm

Profiling Your Laravel Application With Clockwork

If you’re building any non-trivial application, it’s always a good idea to profile it to find performance problems. Laravel Debugbar is the usual solution for profiling Laravel web applications, but it isn’t really much use for REST API’s or single-page web apps that consume them.

Recently I was introduced to Clockwork, which is a server-side extension for profiling PHP applications. It’s made it a whole lot easier to track down issues like excessive numbers of queries when building an API, and as a result I’ve been able to dramatically improve the performance of an API I’ve been working on. Here I’ll show you how you can use it on a project.

Installing Clockwork

Clockwork is available via Composer:

$ composer require itsgoingd/clockwork

You also need to register the service provider in config/app.php:

   Clockwork\Support\Laravel\ClockworkServiceProvider::class,

And register the middleware globally in app/Http/Kernel.php:

protected $middleware = [
\Clockwork\Support\Laravel\ClockworkMiddleware::class,
]

Note that it only works when APP_DEBUG is set to true in your .env file. This means that you can keep it in your application without worrying about exposing too much data in production, as long as debug mode is not active on your production server (which it shouldn’t be anyway).

You will also need to install the Chrome extension in order to actually work with the returned data. Clockwork works by adding its own route to your Laravel application, and this extension makes sure that it makes the appropriate request on loading a page, and then displays the data in the dev tools.

Once it’s all installed and your application is running, open the dev tools and you should see the new Clockwork tab in there. On the left of this tab is a list of requests - if you make a request, you’ll see it added to the list. When you click on each request, you’ll see the following tabs, where applicable:

Request

Request tab

This is similar to Chrome’s network tab in that it shows all of the headers for a given request. It’s not anything you can’t get using Chrome’s existing dev tools, but because it doesn’t show any static content it’s arguably a bit easier to navigate.

Timeline

Timeline tab

This shows how long the response takes to respond, which can be helpful in identifying slower requests.

In addition, you can create your own events using the clock() helper, which will appear in the timeline, as in this example:

clock()->startEvent('email_sent', 'Email sent.');
clock()->endEvent('email_sent');

Log

Log tab

The log tab is only displayed if you use the clock() helper to log data. You can log text or JSON objects as appropriate:

clock('Message text.'); // 'Message text.' appears in Clockwork log tab
clock(['hello' => 'world']); // logs json representation of the array

This is arguably more convenient than using the Log facade to write to the application log, since it’s kept in the browser and you can easily see what request caused what message to be logged.

Database

Database tab

The database tab displays details of the queries made by a request. This is useful for identifying things such as:

  • Repeated queries that should be cached
  • The n+1 problem (which can be resolved by use of eager loading)
  • Slow queries that need to be optimised

Note that if a particular endpoint does not trigger a query, this tab will not be visible.

Cookies

Cookies tab

For a REST API, you shouldn’t really have much use for cookies, but if you do, this tab lets you view the cookies set on the request.

Session

Session tab

As with cookies, the session isn’t normally something you’d use for an API, but this tab lets you view it.

Views

Views tab

This tab shows the views used on the page, and all of the data passed to them.

Routes

Routes tab

This tab shows all of the routes defined within your application.

Clockwork isn’t limited to Laravel - you can also use it with Lumen, Slim 2, and CodeIgniter 2.1, and it’s possible to write your own integration for other frameworks. It’s still fundamentally browser-based, so it’s difficult to use it if your API doesn’t have at least some kind of web front end (whether that’s a single page web app or Phonegap app that consumes the API, or that the API is itself browseable and returns HTML in a web browser), but I’ve found it to be superior to Laravel Debugbar for most of what I do.

17th June 2017 2:12 pm

Snapshot Test Your Vue Components With Jest

At work I’ve recently started using Vue as my main front-end framework instead of Angular 1. It has a relatively shallow learning curve and has enough similarities with both React and Angular 1 that if you’re familiar with one or both of them it feels quite familiar. We’re a Laravel shop and Laravel comes out of the box with a basic scaffolding for using Vue, so not only is it the path of least resistance, but many of my colleagues knew it already and it’s used on some existing projects (one of which I’ve been helping out on this week), so it made sense to learn it. Add to that the fact that the main alternative is Angular 2, which I vehemently dislike, and learning Vue was a no-brainer.

Snapshot tests are a really useful way of making sure your user interface doesn’t change unexpectedly. Facebook introduced them to their Jest testing framework last year, and they’ve started to appear in other testing frameworks too. In their words…

A typical snapshot test case for a mobile app renders a UI component, takes a screenshot, then compares it to a reference image stored alongside the test. The test will fail if the two images do not match: either the change is unexpected, or the screenshot needs to be updated to the new version of the UI component.

This makes it easy to make sure than a UI component, such as a React or Vue component, does not unexpectedly change how it is rendered. In the event that it does change, it will fail the test, and it’s up to the developer to confirm whether or not that’s expected - if so they can generate a new version of the snapshot and be on their way. Without it, you’re stuck manually testing that the right HTML tags get generated, which is a chore.

Jest’s documentation is aimed pretty squarely at React, but it’s not hard to adapt it to work with Vue components. Here I’ll show you how I got it working with Vue.

Setting up a new project

I used the Vue CLI boilerplate generator to set up my initial dependencies for this project. I then had to install some further packages:

$ npm install --save-dev jest babel-jest jest-vue-preprocessor

After that, I had to configure Jest to work with Vue. The finished package.json looked like this:

{
"name": "myproject",
"version": "1.0.0",
"description": "A project",
"author": "Matthew Daly <matthew@matthewdaly.co.uk>",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js",
"lint": "eslint --ext .js,.vue src",
"test": "jest __test__/ --coverage"
},
"dependencies": {
"vue": "^2.3.3",
"vue-router": "^2.3.1"
},
"devDependencies": {
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-eslint": "^7.1.1",
"babel-jest": "^20.0.3",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eslint": "^3.19.0",
"eslint-config-standard": "^6.2.1",
"eslint-friendly-formatter": "^2.0.7",
"eslint-loader": "^1.7.1",
"eslint-plugin-html": "^2.0.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^2.0.1",
"eventsource-polyfill": "^0.9.6",
"express": "^4.14.1",
"extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"jest": "^20.0.4",
"jest-vue-preprocessor": "^1.0.1",
"opn": "^4.0.2",
"optimize-css-assets-webpack-plugin": "^1.3.0",
"ora": "^1.2.0",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"url-loader": "^0.5.8",
"vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.3.3",
"webpack": "^2.6.1",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"jest": {
"testRegex": "spec.js$",
"moduleFileExtensions": [
"js",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
}
}
}

I won’t include things like the Webpack config, because that’s all generated by Vue CLI. Note that we need to tell Jest what file extensions it should work with, including .vue, and we need to specify the appropriate transforms for different types of files. We use jest-vue-preprocessor for .vue files and babel-jest for .js files.

With that done, we can create a basic component. We’ll assume we’re writing a simple issue tracker here, and our first component will be at src/components/Issue.vue:

<template>
<div>
<h1>An Issue</h1>
</div>
</template>
<script>
export default {
data () {
return {}
}
}
</script>
<style scoped>
</style>

Next, we create a simple test for this component. Save this as __test__/components/issue.spec.js:

import Issue from '../../src/components/Issue.vue'
import Vue from 'vue'
const Constructor = Vue.extend(Issue)
const vm = new Constructor().$mount()
describe('Issue', () => {
it('should render', () => {
expect(vm.$el.querySelector('h1').textContent).toEqual('An Issue')
});
it('should match the snapshot', () => {
expect(vm.$el).toMatchSnapshot()
});
});

Constructor is what creates our Vue component, while vm is our actual newly-mounted Vue component. We can refer to the HTML inside the component through vm.$el, so we can then work with the virtual DOM easily.

In the first test we use the more traditional method of verifying our UI component has worked as expected - we fetch an HTML tag inside it and verify that the content inside is what we expect. This is fine for a small component, but as the components get larger we’ll find it more of a chore.

The second test is much simpler and more concise. We simply assert that it matches the snapshot. Not only is that easier, but it can scale to components of any size because we don’t have to check every little element.

Let’s run our tests:

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
PASS __test__/components/issue.spec.js
Issue
✓ should render (46ms)
✓ should match the snapshot (14ms)
Snapshot Summary
› 1 snapshot written in 1 test suite.
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 added, 1 total
Time: 8.264s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.15 | 50 | 100 | 96 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Note this section:

Snapshot Summary
› 1 snapshot written in 1 test suite.

This tells us that the snapshot has been successfully written. If we run the tests again we should see that it checks against the existing snapshot:

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
PASS __test__/components/issue.spec.js
Issue
✓ should render (40ms)
✓ should match the snapshot (12ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 passed, 1 total
Time: 3.554s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.15 | 50 | 100 | 96 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Great stuff. Now, if we make a minor change to our component, such as changing the text from An Issue to My Issue, does it pick that up?

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
FAIL __test__/components/issue.spec.js (5.252s)
● Issue › should render
expect(received).toEqual(expected)
Expected value to equal:
"An Issue"
Received:
"My Issue"
at Object.<anonymous> (__test__/components/issue.spec.js:9:52)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
● Issue › should match the snapshot
expect(value).toMatchSnapshot()
Received value does not match stored snapshot 1.
- Snapshot
+ Received
<div>
<h1>
- An Issue
+ My Issue
</h1>
</div>
at Object.<anonymous> (__test__/components/issue.spec.js:13:20)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
Issue
✕ should render (48ms)
✕ should match the snapshot (25ms)
Snapshot Summary
› 1 snapshot test failed in 1 test suite. Inspect your code changes or run with `npm test -- -u` to update them.
Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total
Snapshots: 1 failed, 1 total
Time: 7.082s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.15 | 50 | 100 | 96 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Yes, we can see that it’s picked up on the change and thrown an error. Note this line:

 › 1 snapshot test failed in 1 test suite. Inspect your code changes or run with `npm test -- -u` to update them.

Jest is telling us that our snapshot has changed, but if we expect that, we can just run npm test -- -u to replace the existing one with our new one. Then, our tests will pass again.

Now, this component is pretty useless. It doesn’t accept any external input whatsoever, so the response is always going to be the same. How do we test a more dynamic component? Amend the component to look like this:

<template>
<div>
<h1>{{ issue.name }}</h1>
</div>
</template>
<script>
export default {
props: {
issue: Object
},
data () {
return {}
}
}
</script>
<style scoped>
</style>

We’re now passing the issue object into our component as a prop, and getting the name from that. That will break our test, so we need to amend it to pass through the props:

import Issue from '../../src/components/Issue.vue'
import Vue from 'vue'
const Constructor = Vue.extend(Issue)
const issue = {
name: 'My Issue'
}
const vm = new Constructor({
propsData: { issue: issue }
}).$mount()
describe('Issue', () => {
it('should render', () => {
expect(vm.$el.querySelector('h1').textContent).toEqual('My Issue')
});
it('should match the snapshot', () => {
expect(vm.$el).toMatchSnapshot()
});
});

Here we pass our prop into the constructor for the component. Now, let’s run the tests again:

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
FAIL __test__/components/issue.spec.js
● Issue › should match the snapshot
expect(value).toMatchSnapshot()
Received value does not match stored snapshot 1.
- Snapshot
+ Received
<div>
<h1>
- An Issue
+ My Issue
</h1>
</div>
at Object.<anonymous> (__test__/components/issue.spec.js:18:20)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
Issue
✓ should render (39ms)
✕ should match the snapshot (25ms)
Snapshot Summary
› 1 snapshot test failed in 1 test suite. Inspect your code changes or run with `npm test -- -u` to update them.
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 1 failed, 1 total
Time: 3.717s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.3 | 50 | 100 | 96.15 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Jest has picked up on our changes and thrown an error. However, because we know the UI has changed, we’re happy with this situation, so we can tell Jest to replace the prior snapshot with npm test -- -u as mentioned earlier:

$ npm test -- -u
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage "-u"
PASS __test__/components/issue.spec.js
Issue
✓ should render (39ms)
✓ should match the snapshot (14ms)
Snapshot Summary
› 1 snapshot updated in 1 test suite.
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 updated, 1 total
Time: 3.668s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.3 | 50 | 100 | 96.15 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Great, we now have a passing test suite again! That’s all we need to make sure that any regressions in the generated HTML of a component get caught.

Of course, this won’t help with the actual functionality of the component. However, Jest is pretty easy to use to write tests for the actual functionality of the application. If you prefer another testing framework, it’s possible to do the same with them, although I will leave setting them up as an exercise for the reader.

Recent Posts

Installing Nginx Unit on Ubuntu

Making Internal Requests With Laravel

Run Your Tests Locally With Sismo

Profiling Your Laravel Application With Clockwork

Snapshot Test Your Vue Components With Jest

About me

I'm a web and mobile app developer based in Norfolk. My skillset includes Python, PHP and Javascript, and I have extensive experience working with CodeIgniter, Laravel, Django, Phonegap and Angular.js.