Unit-testing your front-end code in a Rails project: Yarn, Tape & Rails

An article, posted almost 7 years ago filed in yarn, coffeescript, javascript, testing, rails, ruby, homebrew & Tape.

I like Rails, but one thing that Rails falls short in is Javascript dependency management.

Unit-testing your front-end code in a Rails project: Yarn, Tape & Rails

While Rails Assets, a proxy that allows for listing Bower packages in your Gemfile makes managing front-end libraries good enough for most front-end work, RailsAssets itself is mainly addressing asset management; it doesn’t allow for integrated management of additional development tools and binaries, useful for e.g. JavaScript-testing (besides the fact that Bower is kind of considered to be deprecated these days).

There are different ways of bundling Javascript, but since Rails 5.1, yarn is the defacto choice for Rails.

Installing Yarn

You can install yarn either trough npm npm install -g yarn, or if you’re on a mac, using homebrew: brew install yarn. I chose the latter.

To prepare your rails project run rails yarn:install.

Add tape for testing JavaScript & Coffeescript

There are different testing frameworks for javascript. But most things I work on are not very complex, so I choose Tape, a small testing tool, as a development module:

yarn add coffeetape --dev

After installed you get the coffeetape yarn command, so you can run:

yarn run coffeetape test/javascripts/test_*.*

Yep, here I followed the Rails convention of placing tests in the test directory and prefixing each test file with test_. This also allows us to easily match both .coffee files as .js files (whenever you want to mix the two).

Writing your first test-script

Let’s assume you’re using the typical animal.coffee example

we create the test/javascript/test_animal.coffee:

test = require 'tape' 
obj = require '../../app/assets/javascripts/animal.coffee'

test 'animal', (t)->

This isn’t doing anything yet. If we would console.log(obj) here we wouldn’t find anything, because CoffeeScript wraps everything in a self-executing anonymous function. So we have to bind something to ‘this’. So modify animal.coffee to bind to ‘this’ at the end of the file:

@Animal = Animal    
@Horse = Horse    
@Snake = Snake    

Without it, the functions in the script cannot be accessed outside the context of the script, so this is necessary for unit-style testing. this in the front-end context is typically window, in server side it gets bind to whatever is exported. Hence, Animal will be a property on the exported object, addressable through, in this case, obj.Animal.

test = require 'tape'
obj = require '../../app/assets/javascripts/animal.coffee'

test 'animal', (t)->
  a = new obj.Snake
  t.plan 1
  t.equal typeof(a.move), "function"

Of course you could also use the typical node ‘exports’ helper at the end of your File:

exports = Snake

Which would allow you to use Snake without the intermediate object.

Bonus: Integrating it with regular tests

Call me lazy, but adding below to tests/javascripts/javascripts_test.rb helps me to not forget about the tests.

require 'test_helper'

class JavascriptsTest < ActionDispatch::IntegrationTest
  test "all javascripts" do

    require "open3"
    output = nil
    cmd = "yarn run coffeetape test/javascripts/test_*.*"
    Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
      if stderr.read.to_s.match /error/i
        output = stdout.read
      end
    end
    assert_nil(output)
  end
end

By installing the coffeescript executable via homebrew (brew install coffeescript I can also simply run the individual tape-test-files easily from within my editor (Textmate 2).

Photo by chuttersnap on Unsplash (Unsplash License)

Op de hoogte blijven?

Maandelijks maak ik een selectie artikelen en zorg ik voor wat extra context bij de meer technische stukken. Schrijf je hieronder in:

Mailfrequentie = 1x per maand. Je privacy wordt serieus genomen: de mailinglijst bestaat alleen op onze servers.