简体   繁体   中英

Requiring timeouts when testing Meteor with Velocity and Jasmine

Pretty new to meteor, velocity and jasmine so not sure if I am doing something wrong, using Jasmine for something it's not designed for, or this is just the way it works.

I am finding I need to set timeouts for pretty much all of my tests in order to get them to pass. Should this be the case or am I doing something incorrectly?

For example some tests I am running to check validation messages:

    describe("add quote validation", function() {
      beforeEach(function (done) {
        Router.go('addQuote');
        Tracker.afterFlush(function(){
          done();
        });
      });

      beforeEach(waitForRouter);

      it("should show validation when Quote is missing", function(done) {
        $('#quote').val('');
        $('#author').val('Some author');
        Meteor.setTimeout(function(){
          $('#addQuoteBtn').click();
        }, 500);
        Meteor.setTimeout(function(){
          expect($('.parsley-custom-error-message').text()).toEqual("Quote can't be empty.");
          done();
          }, 500);
      });
    }

OK, we've had this exact same problem and devised a pretty elegant solution to it, that doesn't require timeouts and is the fastest way to run your tests. Basically, we use one of two strategies, depending on what screen elements you're waiting for.

All code goes into tests/mocha/client/lib.coffee, not 100% what the Jasmine equivalent is, but it should be available to all client test code. I've left it in Coffeescript, but you can compile it on coffeescript.org into Javascript, it should work fine as well.

If whatever you do (routing or something else like changing a reactive variable) causes a Template to (re)render, you can use the Template.<your_template>.rendered hook to detect when it is finished rendering. So, we've added the following function in lib.coffee:

@afterRendered = (template,f)->
    cb = template.rendered
    template.rendered = ->
      cb?()
      template.rendered = cb
      f?()
      return
    return

What does it do? It basically "remembers" the original rendered callback and temporarily replaces it with one that calls an extra function after the template is rendered and the original callback is called. It needs to do this sort of housekeeping to avoid breaking any code that may have depended on the rendered callback, as you're basically messing with the Meteor code directly.

In your test, you can then do something like this:

 it.only "should check stuff after routing", (done)->
    try
      Router.go "<somewhere>"
      afterRendered Template.<expected_template>, ->
        <your tests here>
        done()
    catch e
      done(e)

I'd recommend the try-catch as well, as I've noticed asynchronous errors don't always make it into the velocity system, merely giving you a timeout failure.

OK, then there are things that don't actually re-render, but are generated with JS or by some kind of "show/hide" mechanism. For that, you do need some kind of timeout, but you can reduce the "time cost" of the timeout by using a polling mechanism.

# evaluates if a JQuery element is visible or not
$.fn.visible = -> this.length > 0 and this.css('display') isnt 'none'

# This superduper JQuery helper function will trigger a function when an element becomes visible (display != none). If the element is already visible, it triggers immediately. 
$.fn.onVisible = (fn,it)->
    sel = this.selector
    if this.visible()
      console.log "Found immediately"
      fn?(this)
    else
      counter = 0
      timer = setInterval ->
        counter++
        el = $(sel)
        if el.visible()
          fn?(el)
          clearInterval timer
          console.log "Found on iteration #{counter}"
        else
          it?(el)
      , 50

You can remove the console logging and secondary it iterator function if you like, they're not important. This allows you to do something like this in your test:

$('#modalId').onVisible (el)->
  <tests here>
  done()
, (el)->
  console.log "Waiting for #{el.selector}"

You can remove the second function if you want, it is the it iterator function mentioned above. However, do note that this particular code works with "display: hidden" as the marker for invisibility (Bootstrap does this). Change it if your code uses another mechanism to hide/show parts.

Works like a charm for us!

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