简体   繁体   中英

Running a JS test suite from a Rails environment with a fixture factory, or an API for frontend to request certain fixtures to be loaded on backend

I'm a frontend developer working with EmberJS. That's a fantastic frontend framework that adopts a lot of virtues from Rails: it's very sophisticated and opinionated and at the same time it's extremely agile and handy to use.

Ember has its own test suite perfectly capable of acceptance testing. But it either tests against a backend mock, which is bad for two reasons: it is tedious to create a functional backend mock, and it does not test frontend-backend integration. Or Ember can test against a backend running normally, which makes it impossible to use a fixture factory like FactoryGirl/Fabrication, and you'll have to manually reset the testing database after every test.

A traditional solution to this is to use Capybara. The problem with Capybara is that Ember is very asynchronous by its nature. Capybara is unable to track whether Ember has finished requesting data/calculating reactive properties/rendering GUI/etc.

There are some people using Capybara to test Ember, and all of them use ugliest hacks. Here are just a couple for you to get the foul taste of it:

patiently do
  return unless page.has_css? '.ember-application'
end

2000.times do #this means up to 20 seconds
  return if page.evaluate_script "(typeof Ember === 'object') && !Ember.run.hasScheduledTimers() && !Ember.run.currentRunLoop"
  sleep 0.01
end

source

def wait_for_ajax
 counter = 0
 while true

 active = page.execute_script(“return $.active”).to_i
 #puts “AJAX $.active result: “ + active.to_s

 break if active < 1
 counter += 1
 sleep(0.1)
 raise “AJAX request took longer than 5 seconds OR there was a JS error. Check your console.” if counter >= 50
 end
end

source

So the idea is to use the wonderful Ember's JS test suite to execute tests from Rails . It would allow using Factory Girl to set up the database for every test specifically... and run a NodeJS/Ember test instead of Capybara.

I can imagine something like this:

describe "the signin process", :type => :feature do
  before :each do
    FactoryGirl.create(:user, :email => 'user@example.com', :password => 'password')
  end

  it "signs me in" do
    test_in_ember(:module => 'Acceptance: User', :filter => 'signs me in')
  end

  it "logs me out" do
    test_in_ember(:module => 'Acceptance: User', :filter => 'logs me out')
  end
end

Ember has a command-line interface to testing that can run specific tests, based on Testem .

Alternatively, Ember test suite could be used normally, but it would start every test with a call to a special API, requesting certain fixture factory definitions to be loaded.

Formal question: how do I use the best from both worlds together: native test suite from EmberJS and native fixture factory from Ruby on Rails?

There are a number of issues:

  1. FactoryGirl is a Ruby DSL for creating factories - each factory definition is just a block of ruby code, not some static definition. If you wanted to port the dynamic aspects of a factory such as sequences etc you would have to write a parser which translates the DSL into a javascript DSL.
  2. Javascript factories would not be able to write to the database without accessing your public API's anyways.

Some rough ideas of how this could bridged:

  1. Set up fixtures before your specs - and pass them to your scripts. Most javascript drivers allow you use page.execute_script which can be used to pass a serialized fixture. Cons: must be done before test execution begins.

  2. Set up a factories API which allows you to invoke factories via ajax. Preferably in a mountable Rails Engine. Cons: slow, asynchronous


def FactoriesController
   # get /spec/factories/:factory/new
   def new
     @factory = FactoryGirl.new(params[:factory_uid])
     respond_to do |f|
       render json: @factory
      end
   end

   # post /spec/factories/:factory
   def create
     FactoryGirl.create(params[:factory_uid], factory_params)
   end

   # ...
end
  1. Create a FactoryGirl parser which still does not solve issue 2.

added:

A crappy example factory client:

var FactoryBoy = {
    build: function(factory, cb){
       $.ajax('/factories/' + factory + '/new' , dataType: 'json').done(function(data){ cb(data) });
    }, 
    create: function(factory, cb){
        $.ajax('/factories/' + factory , dataType: 'json' method:'POST').done(function(data){ cb(data) });
    }
}

Test example:

module('Unit: SomeThing');

test('Nuking a user', function() {
  var done = assert.async(); 
  FactoryBoy.create('user', function(user){
     this.store.find('user', user.id).then(function(u){
       $('#nuke_user').click();
       ok(u.get('isDeleted'));
       done();
     });
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM