JavaScript Unit Tests in a Phoenix Application

There’s a guide to writing browser acceptance tests for Phoenix. Acceptance tests are nice, but sometimes you want to have unit tests. This is very easy to do with your Elixir code, but what about your JavaScript code that lives inside your Phoenix application?

I couldn’t find a good guide on this, so I’ll go over what I have set up for one of my latest Phoenix projects.


First, install mocha if you haven’t already. I’ll be using mocha, but you can use whatever test runner you want to. You’ll also need babel-register — this will allow you to use Babel while the tests are being run in Node.

$ npm install --save-dev mocha
$ npm install --save-dev babel-register

Set up mocha to run your tests in package.json. The default Phoenix & Brunch installation doesn’t include a “scripts” section, so you’ll probably have to create it. If it’s already there, just add the “test” line.

  "dependencies": {
  "scripts": {
    "test": "mocha --compilers js:babel-register test/js/**/*.js"

Now, set up Babel to pick up the default preset (if you’re setting a different preset in your brunch-config.js, you just replace es2015 with what you have there). Put this in .babelrc in the root directory of your project.

  "presets": ["es2015"]

Now, you can put JavaScript unit tests in test/js and by running npm test, they will run inside Node.

Note that npm test will not be automatically run when you run mix test, so you’ll have to change the way tests are run on your CI run by either changing the test command to mix test && npm test or adding the npm test command to your CI configuration.

Example Test

Say we have a module that does nothing but export a function that returns the string "something":

export default function() {
  return "something";

This file lives in web/static/js/something.js.

Now, let’s write a test for it in test/js/something_test.js:

import assert from 'assert';
import something from '../../web/static/js/something';

describe('something()', function() {
  it('does something', function () {
    assert.equal('something', something());

Now, run the tests:

$ npm test

> @ test /Users/keita/personal/phoenix-mocha-example
> mocha --compilers js:babel-register test/js/**/*.js

    ✓ does something

  1 passing (8ms)

This example project is available on GitHub if you would like to take a closer look at it. As always, please leave a comment or get in touch if you’d like to provide some input!

Elixir’s StringIO may not be what you think it is

In Ruby, there is a very handy class called StringIO. Basically, it allows you to treat a string like you would an IO object, such as an open file, etc. Very useful for in-memory “files” that you may not want to write to a temporary file.

In Elixir, there is a module called StringIO in the standard library. At first glance, these seem pretty similar:


Pseudo I/O on String object.


This module provides an IO device that wraps a string.

However, there are some subtle differences. For example, you can’t rewind the position of an Elixir StringIO:

iex(1)> {:ok, io} ="foo")
{:ok, #PID<0.59.0>}
iex(2)>, :all)
iex(3)> :file.position(io, :bof)
===> hang!

(For more information about the :file.position/2 function, check the docs out. :bof stands for “beginning of file”)

Let’s see why this is happening. StringIO has a function to show its current buffers:

iex(1)> {:ok, io} ="foo")
{:ok, #PID<0.59.0>}
iex(2)> StringIO.contents(io)
{"foo", ""}
iex(3)>, :all)
iex(4)> StringIO.contents(io)
{"", ""}

Basically, after you read data from an Elixir StringIO, it’s gone. So, we look to Erlang. Erlang’s file:open/2 function accepts a ram option that we might be interested in:

Returns an fd() which lets the file module operate on the data in-memory as if it is a file.

Let’s try it out.

iex(1)> {:ok, io} ="foo", [:ram, :binary])
{:ok, {:file_descriptor, :ram_file, #Port<0.1471>}}
iex(2)> IO.binread(io, :all)
iex(3)> IO.binread(io, :all)
iex(4)> :file.position(io, :bof)
{:ok, 0}
iex(5)> IO.binread(io, :all)

Rewinding works now.

Note that because of differences between Elixir’s IO / File modules and Erlang’s file module (probably related to how Elixir works with character encodings), you have to use the binary option to and the bin-prefixed functions in Elixir land.

Web Development Tools: Mailtrap

This is the inaugural article of my “Web Development Tools” series I plan on continuing for at least a few more posts, sharing some of the essential tools I use for web development every day. When I have the chance to work with new people, we always exchange useful information about the tools and libraries we use. This series is an attempt to organize this information.

Today’s pick: Mailtrap

Once upon a time, I was working on a 2.0 for a client. Major overhaul. The database schema was completely different. So, I wrote a data migration script. Runs fine locally. Then, it became time to import test data to the staging environment.

I had completely forgotten that when creating a new user, a “Welcome to our service, please confirm your account!” e-mail was sent out.

You can imagine what happened next. Thousands of e-mails were being sent while the migration script was running, sending “Welcome!” e-mails to unsuspecting customers — with a link to the staging environment.

This continues to be one of the most embarrassing moments of my career to date. Following this event, I sought out to find something that would make sure this never happened again. I found Mailtrap.

Mailtrap is an SMTP server — an SMTP server that doesn’t forward messages to users. Instead, they’re saved to the Mailtrap mailbox, accessible via a nice web GUI.

Incoming Mailbox
Incoming Mailbox

From there, Mailtrap gives you some nice tools — inspecting the text content if you have a multipart e-mail, viewing HTML source, seeing the raw e-mail.

Raw E-mail View -- headers and all.
Raw E-mail View — headers and all.

When I started using Mailtrap, it just had the most basic feature — catching e-mail. Now, it has a lot of very useful features — shared mailboxes so you can share test e-mails with your team, forwarding so you can forward test e-mails to a real client to see how they look — the list goes on.

Today, I use Mailtrap by default in all of my projects that send any kind of e-mail. It’s very simple to set up — they have instructions for the popular platforms and frameworks (including Sendmail, heh!) — and if your framework isn’t in there, they have the plain old SMTP settings for you to plug in.

Mailtrap is free for one mailbox with up to 50 messages, and plans start from $9.99/monthly. Once the 50 message limit is reached, older messages will be deleted to make room for the new messages. For personal projects, the free tier has been more than enough for my needs.

Disclosure: I am not affiliated with Mailtrap, nor am I receiving any compensation (financial or otherwise) from Mailtrap for writing this article.

Mailtrap is a trademark of Railsware Products, Inc..

Homebrew and PostgreSQL 9.5 (or 9.6)

Edit Sept. 30 2016: PostgreSQL 9.6 was released today, and these instructions should work — just replace 9.4 with 9.5 and 9.5 with 9.6. I also have a guide using pg_upgradecluster on Ubuntu.

PostgreSQL 9.5 was released on Jan. 7, with lots of exciting new features.

I wrote a post about upgrading from 9.3 to 9.4 in the past, and many people found it useful, so I decided to update it a bit for the 9.4 to 9.5 upgrade.

  1. Turn PostgreSQL off first:
    $ launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
    # or, if you're running a current version of Homebrew
    $ brew services stop postgresql
  2. Update PostgreSQL itself:
    $ brew update && brew upgrade postgresql
  3. Make a new, pristine 9.5 database:
    $ initdb /usr/local/var/postgres9.5 -E utf8
  4. Migrate the data to the new 9.5 database. Note that I have 9.4.5_2 in here, it could be that you aren’t on the latest version. Replace 9.4.5_2 with the most current version of postgres in that directory.
    $ pg_upgrade \
      -d /usr/local/var/postgres \
      -D /usr/local/var/postgres9.5 \
      -b /usr/local/Cellar/postgresql/9.4.5_2/bin/ \
      -B /usr/local/Cellar/postgresql/9.5.0/bin/ \
  5. Move 9.5 data directory back to where PostgreSQL expects it to be:
    $ mv /usr/local/var/postgres /usr/local/var/postgres9.4
    $ mv /usr/local/var/postgres9.5 /usr/local/var/postgres
  6. Start PostgreSQL back up!
    $ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
    # or, if you're running a current version of Homebrew
    $ brew services start postgresql

Note: If you’re using the pg gem for Rails, you should recompile:

$ gem uninstall pg
$ gem install pg

Note 2: If you’ve already uninstalled a previous version of PostgreSQL, there is a good post on StackOverflow with instructions to install previous versions.

.gitignore と .gitignore_global の分け方

(If you aren’t interested in my posts written in Japanese, please subscribe to the “English” feed.)

Git のレポジトリーの中の .gitignore というのは、レポジトリーにコミットしない(無視する)ファイルを定義するものです。

Git のバージョン管理システムでは、二つの gitignore ファイルがあります。一つ目の一番知られてるのはレポジトリー内の。こちらは、プロジェクトのルートダイレクトリーに置かれて、そのプロジェクトに対する無視するファイルなど定義されています。

一方、デフォルトで全てのプロジェクトに適用する gitignore ファイルも設定できます。「グローバル gitignore」と呼びます。


$ git config --global core.excludesfile ~/.gitignore_global

~/.gitignore_global のファイルに全てのプロジェクトに適用する gitignore コンテンツを入力します(ファイルが存在しない場合は作成する)。


  • プロジェクト内 gitignore ⇒ プロジェクトと直接関する定義。例えば、ビルドで作られるテンポラリーファイル、テスト時の自動で生成されるキャッシュファイル、ログファイルなど。
  • グローバル gitignore ⇒ 当ローカル環境に関する定義。例えば、 OS X なら .DS_Store のファイルなど、 vim が作る *.swp のファイルなど。

.DS_Store*.swp の定義をプロジェクト内の gitignore に入れるケースも少なくはないと思いますが、こちらは間違っています。一人一人開発環境が違うので、責任を持って自分の開発環境の無視するべきファイルをグローバル gitignore に定義した方が良いでしょう。

Hosting a Single-Page App on S3, with proper URLs

Note (2019/07/05): I’ve posted a follow-up to this post about limitations about the technique used here, especially when hosting an API on the same domain.

Amazon S3 is a great place to store static files. You might want to even serve a single-page application (SPA) written in JavaScript there.

When you’re writing a single-page app, there are a couple ways to handle URLs:


A is easy to serve from S3. The server only sees the part, and so it serves that file to everyone.

B, however, is a little tricky. Single-page apps usually use pushState or replaceState to change the current URL without reloading, but once you reload (or give the URL to someone else) — BAM! You’ll get presented with a 404 Not Found error.

So why don’t we just use A? There are quite a few advantages to using B, over just being more elegant than putting that pesky #! in there. In my opinion, the biggest advantage of using B is that you’ll be able to make backend changes in the future without having to redirect URLs. For example, as your app gets bigger, you want to render some (or all) components server-side (see Isomorphic or Universal JavaScript).

To implement the B strategy, we need to serve the same index.html file to any URL requested by the client. As I mentioned earlier, we can’t do this with S3 itself, so we’ll enlist the help of CloudFront.

First, create a CloudFront distribution for the S3 bucket. Since CloudFront caches items for quite a long time, you might want to either set Cache-Control headers on your S3 files, or set the default TTL to something short, like a few seconds, in the CloudFront distribution settings. Once everything is set up (and you can access index.html by itself), click the “Error Pages” tab.

Screen Shot 2015-11-24 at 9.28.46 AM

Click the big blue button, “Create Custom Error Response”:

Screen Shot 2015-11-24 at 9.28.55 AM

Now, I think you can tell what I’m up to now. Enabling “Customize Error Response” allows you to change a 404 from the backend (in this case, S3) in to a 200! Note that S3 will return a 403 response if you use the “S3 Origin” option instead of the S3-hosted origin. If you’re getting a 403 error from S3, customize the 403 error as well.

Screen Shot 2015-11-24 at 9.29.23 AM

You can try out this setup below:

These all serve the same index.html. If you inspect the headers, the first link should be X-Cache: RefreshHit from cloudfront or Miss from cloudfront. However, if you look at the other requests, it will be X-Cache: Error from cloudfront. The status returned, however, is 200 — just as we wanted it.

Any questions? Contact me or leave a comment in the box below.

Podcasts I’m Listening To (November 2015 Edition)

My wife Naoko wrote a reply to this post. It was fun comparing how different the podcasts we listen to are. 🙂

First, I’d like to plug a podcast that I’m a semi-regular guest on, techsTalking(5417), a podcast where technology people just talk about whatever is on our mind.

Here are some other podcasts that I’m currently subscribed to:

  • The Incomparable — a podcast about anything geeky. Star Wars? Check. Star Trek? Check. Silly drafts? Check. Crazy movies? Check.
  • The Incomparable Game Show — born from The Incomparable proper, regular panelists play crazy games for your entertainment. On the podcast.
  • Incomparable Radio Theater — The Incomparable podcast, once upon a time, liked to do funny things on April Fools. Like, say: release a full-length episode in the format of old-time radio drama. Including equally funny sponsors (some fake, some real). Now, they’ve spun it off in to a separate podcast.
  • Random Trek — Incomparable regular Scott McNulty hosts a podcast with non-random guests talking about random episodes of Star Trek.
  • Robot or Not? — Is it a robot? Or not?
  • Astronomy Cast — A weekly “facts-based journey through the cosmos”.
  • Reconcilable Differences — Two of my favorite podcasters, John Siracusa and Merlin Mann, get together on one podcast.

A few other podcasts I listen to occasionally:

And assorted programming-specific podcasts.

Runroller UI

I recently released a simple API to un-shorten URLs. A few people wanted a super-simple interface to this, so I whipped one up: Enjoy!

Some notes about the tools I’ve used:

  • React — I’ve used React in portions of sites before, but this is the first, albeit simple, full-page React app I’ve made.
  • Brunch — used by default in Phoenix apps, it’s just what I’m used to these days.

Just like the service that runs the API, the UI is also open-source. Hack away!