Você pode ver esse post em português
Looking for a javascript test runner for Rails, I found some options, so I decided to choose Blade, as this is not a comparison (I'm not even experienced to do this), here are the reasons why I preferred Blade:
So I started to configure as usual on a javascript lib.
# Gemfile
group :development, :test do
gem 'blade'
end
After editing Gemfile, as usual:
bundle install
And added a blade.yml
load_paths:
- test/javascripts/src
- test/javascripts/vendor
logical_paths:
- application.js
- test.js
As this is a Rails application, my idea is set load paths to test/javascripts/{src/vendor}
so Blade load my test and support libraries, test.js
is the entry point for the test suite, and application.js
is the javascript entry file on Rails.
I got blade running as expected, but it didn't load application.js
, asking @javan on Twitter, he replied there's no built-in Rails integration, so we are on our own.
The first trial was add app/assets/javascripts
to load_path, it worked, but no javascript asset that is loaded using a gem was found (like jquery-rails
).
So why not hook on blade bin file, make Rails load the assets path and configure on blade?
After some time figuring out how to get things working, I got into this bin for blade, let comment each step:
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'blade' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require "rubygems"
require "bundler/setup"
# COMMENT: here finishes bundler prelude
# COMMENT: Initialize rails application
require_relative File.join('..', 'config/application.rb')
Rails.application.initialize!
# COMMENT: Initialize Rails assets paths...
paths = Rails.application.assets.paths
cwd = `pwd`.chomp + "/"
paths = paths.map { |x| x.gsub(cwd, '') }
# COMMENT: ...and concatenate with specified on .blade.yml
blade_config = YAML::load_file(File.join(__dir__, '..', '.blade.yml'))
paths.concat(blade_config["load_paths"])
Blade.initialize!(interface: 'runner', load_paths: paths)
# COMMENT: bundler run blade
load Gem.bin_path("blade", "blade")
I'm not sure this is the better approach; I got positive feedback from @javan so next step is to make this a blade plugin :)
One of my primary motivation on getting a javascript runner was make sure my fixtures were always updated, my first idea was to process the erb and... no way!
So I had an enlightenment, it's a standard approach to test the (view) output on a Rails test controller (ok, the idea is not if this is the approach, but it's possible) because we have response.body
, so why not write this to fixtures?
The rationale behind is:
test/javascripts/src/fixtures
on blade via http://localhost:9876/fixtures
response.body
on Rails test controllerEdited test_helper.rb
and added
require 'fileutils'
class ActiveSupport::TestCase
def save_fixture(name, selector = nil)
fixture_dir = File.dirname(name)
fixture_dir = File.join(fixtures_dir, fixture_dir)
fixture_path = File.join(fixture_dir, File.basename(name) + ".html")
unless File.directory?(fixture_dir)
FileUtils.mkdir_p(fixture_dir)
end
# Get only body for fixture (or selector if set)
selector = selector.nil? ? 'body' : selector
output = Nokogiri::HTML(response.body)
if (selector == "body")
output = output.css(selector)[0].children.to_s
else
output = output.css(selector)[0].to_s
end
File.write(fixture_path, output)
end
end
# Setup JavaScript Fixtures Dir
def fixtures_dir
test_dir = File.dirname(File.expand_path(__FILE__))
File.join(test_dir, 'javascripts', 'src', 'fixtures')
end
puts fixtures_dir
unless File.directory?(fixtures_dir)
FileUtils.mkdir_p(fixtures_dir)
end
On a controller test in only needed:
require 'test_helper'
class ThingControllerTest < ActionController::TestCase
test "index" do
get :index
assert :success
# do not specify second parameter if you want the whole body
save_fixture('things/table', 'table#things')
end
end
This way we get the response.body
written to test/javascripts/src/fixtures/things/table.html
And you are ready to write you JS test
QUnit.test( "ThingsController#index", function( assert ) {
assert.expect(1);
var done = assert.async();
$.get( "/fixtures/things/table.html", function( data ) {
$( "#qunit-fixture" ).html( data );
length = $("table#things tbody").find('tr').length;
assert.equal(length, 2, "Two things listed");
done();
});
});
The result is excellent:
If the view is changed, the fixture will be updated, and if JS is broken, the test will fail.
For me this is reached all my expectations for a javascript test runner for Rails because it is a powerful setup on the following points:
And what do you think about this approach, you have any tips on testing JS on Rails that you'd like to share?
ps: This apporach became a Blade plugin, please give feedback!
Happy Testing on Rails!