Usando Blade Para Testar JavaScript no Rails
Motivação
You can check this post in english
Eu estava procurando por um executor de teste javascript com integração para o Rails, encontrei algumas opções, porém decidi escolher o Blade. Como isto não é uma comparação (eu não sou tão experiente para isto), aqui estão as razões do porque eu preferi o Blade:
- Fácil de configurar em bibliotecas javascript
- Suporta sprockets
- Ativamente usado no Turbolinks, Trix e ActionCable
- Eu acredito que ele pode se tornar o testador padrão de javascript para o Rails no futuro
Então comecei a configurar como seria de costume em uma biblioteca javascript
# Gemfile group :development, :test do gem 'blade' end
Depois de editar o Gemfile, temos:
bundle install
E adicionando um arquivo .blade.yml
load_paths: - test/javascripts/src - test/javascripts/vendor logical_paths: - application.js - test.js
Como isto é uma aplicação do Rails, minha idéia é definir os caminhos test/javascripts/{src/vendor}
para serem carregados, então Blade carrega meu teste e as bibliotecas de apoio.
O arquivo test.js
é o ponto de entrada para o conjunto de testes, e application.js
é o arquivo de entrada de javascript no Rails.
Consegui executar o Blade como esperado, mas não foi carregado application.js
. Perguntando para o @javan no Twitter, ele respondeu que não há integração built-in no Rails, então estamos por nossa própria conta.
A primeira tentativa foi adicionar app/assets/javascripts
para load_path, funcionou, mas nenhum javascript usado por gems era carregado (exemplo jquery-rails
)
Então porque não entrar no arquivo bin no blade, fazer o Rails carregar o caminho dos assets e configurar o blade?
Depois de algum tempo descobrindo como as coisas funcionavam, eu alterei o binário do Blade, vamos comentar cada passo:
#!/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" # COMENTÁRIO: aqui termina o prelúdio do _bundler_ # COMENTÁRIO: Inicializar a aplicação rails require_relative File.join('..', 'config/application.rb') Rails.application.initialize! # COMENTÁRIO: Inicializar o caminhos dos assets no Rails ... paths = Rails.application.assets.paths cwd = `pwd`.chomp + "/" paths = paths.map { |x| x.gsub(cwd, '') } # COMENTÁRIO: ... E concatenar com o caminho especificado em .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) # COMENTÁRIO: bundler executa blade load Gem.bin_path("blade", "blade")
Eu não tenho certeza se está é a melhor abordagem, mas tive um feedback positivo do @javan, então o próximo passo é fazer um plugin para o blade :)
Bonus: Fixtures
Uma das minhas principais motivações em obter um javascript rodando era pela certeza que minhas fixtures foram sempre atualizadas, minha primeira idéia foi de processar o erb e… não funcionou!
Então eu tive uma luz, é uma abordagem padrão testar a saída (view) em um controller do Rails (ok, a idéia não é discutir se esta é a melhor abordagem, o importante é que é factível) porque nós temos o response.body
, então porque não escrever isto nas fixtures?
A lógica por trás é:
- Nós podemos acessar
test/javascripts/src/fixtures
no blade viahttp://localhost:9876/fixtures
- Nós podemos acessar
response.body
no controlador de teste do Rails - Vamos escrever em arquivos durante a executação de testes do Rails
- Executar teste conjunto Javascript com a fixture mais atualizada
Editei test_helper.rb
e adicionei
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
Em um teste do controller é necessário apenas:
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
Desta forma temos response.body
escrita para test/javascripts/src/fixtures/things/table.html
E você está pronto para escrever seu teste JS
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(); }); });
O resultado é excelente:
Se a view for mudada, a fixture será atualizada, e se o JS quebrar, o teste falhará.
Finalizando
Para mim, atingiu todas as minhas expectativas para um teste javascript executando no Rails, porque é uma configuração poderosa nos seguintes pontos:
- Fácil de instalar
- Executa o teste em diversos navegadores
- Pode ser executado em CI utilizando sauce labs (talvez em blade-phantomjs no futuro?)
E o que você acha sobre essa abordagem, você tem alguma dica sobre testes JS no Rails que você gostaria de compartilhar?
ps: Esta abordagem se tornou um plugin para o Blade, aguardo feedback!
Faça ótimos testes com Rails!