[solved] setTimeout not working/firing in specs/tests


#1

Hi all, I have a very weird behaviour when using setTimeout function. No matter what delay is passed, it always fires after about 30 seconds.

Here’s my code:

foo: ->
  setTimeout =>
    console.log (new Date).getTime() - last
    last = (new Date).getTime()
    @foo()
  , 1000

Am I using it wrong or is it like this by design?


#2

You probably want:


?

Rather than calling recursively like this. Also, make sure your last variable is in scope in the parent object.

If you need date / time specific stuff, might want to check into moment.js also.


#3

Your code looks ok to me.

BTW, you can replace (new Date).getTime() with Date.now().


#4

Ok, scratch that. It looks like 30s delay was due to my code and setInterval proposed by @DavidLGoldberg goes around it, making it work.

There’s still one place where setTimeout doesn’t work: tests. I.e. this spec always timeouts after 5s (callback is never called):

fdescribe 'testing setTimeout', ->
  done = false
  it 'sets timeout', ->
    setTimeout =>
      done = true
    , 1000

    waitsFor ->
      done

    runs ->
      expect(done).toBe(true)

#5

My package ‘jumpy’ has a spec test that gets around it. I explicitly wait 110ms.

    it "the beacon animation class is removed", ->
        editorView.trigger 'jumpy:a'
        waitsFor ->
            setTimeout ->
                editorView.trigger 'jumpy:c'
            ,100 + 10 # max default I'd probably use + a buffer
        runs ->
            expect(editorView.find('.beacon')).not.toExist()

So of course the key here is

waitsFor

#6

Nice workaround! Unfortunately I can’t use it as setTimeout is in separate module which is used in test.


#7

?

So I set a longer timeout (in my case it’s short). The classic alternative is to either use dependency injection or property injection. Both allow you to override the system under test’s timeout property to a very short one so that your test can just timeout over it? Maybe I’m misunderstanding.


#8

I’m injecting fake library which simulates making requests to a server. In original one, methods return promises which are resolved/rejected in other thread (request callback). To achieve the same result I wanted to resolve promise in setTimeout callback.


#9

I would probably need a better example but there’s also:

    waitsForPromise ->

#10

Waiting for promise works but resolving/rejecting it in separate callback from my injected library isn’t possible using setTimeout. The question is if setTimeout was disabled for tests and if yes, how can one get it back.


#11

How are they turned off? Works fine for me…


#12

I never experienced this kind of issues, can you point us to the current code that cause the problem?


#13

Sure. Here’s example package: https://github.com/suda/settimeout-test

lib/library.coffee - here’s method foo() which returns promise and resolves it in setTimeout callback.

spec/library-spec.coffee - this test will timeout instead of passing.


#14

Not sure it’s related, but the waitsFor and runs calls should be done inside the it block.


#15

Following up on what @abe said, your spec is not indented correctly; your file should look like this

Library = require '../lib/library'

fdescribe 'Library test', ->
  library = new Library
  promise = null

  it 'waits for promise to be resolved', ->
    promise = library.foo()
    console.log promise

    waitsFor ->
      (promise != null) && (promise.inspect().state != 'pending')

    runs ->
      expect(promise).not.toBe(null)
      expect(promise.inspect().state).toBe('fulfilled')

#16

And as for why setTimeout is never called, this is due to the jasmine clock mock being installed:

// in the console of the tests window:  setTimeout
function (funcToCall, millis) {
  if (jasmine.Clock.installed.setTimeout.apply) {
    return jasmine.Clock.installed.setTimeout.apply(this, arguments);
  } else {
    return jasmine.Clock.installed.setTimeout(funcToCall, millis);
  }
} 

In your test you’ll need to call jasmine.clock().tick(timelapse) to make the clock go forward. For instance, if you set the timeout to 200ms, calling jasmine.clock().tick(101) will not trigger the timeout, but calling jasmine.clock().tick(201) will.


#17

@abe & @thomasjo Yup the indentation was wrong, but it’s not the problem (it only changed execution order little bit).

@abe it looks like clock mock isn’t installed as jasmine.Clock.isInstalled() returns false.

It looks like spec-helper.coffee is faking setTimeout.

So the solution to my problem is to unspy setTimeout if you want to use it:

jasmine.unspy(window, 'setTimeout')

If you want to spy on it again just do:

spyOn(window, 'setTimeout').andCallFake window.fakeSetTimeout

How can I add my own matchers to Jasmine?
#18

Hmm weird, but ok, in my atom version (v0.120.0) the setTimeout method isn’t a spy but the jasmine mock and to make setTimeout calls the passed-in function I had to use the tick method. BTW, the mock was installed only during the test, meaning that if I run a timeout in the console the method was called, but not in the test. I’m curious to know why we have such different results.

Because you had only one test, but with several tests it would have become a big mess. I’m not even sure that waitsFor and runs behaves properly outside a it or a beforeEach hook.


#19

Thank you!! What a PITA.

Adding the code jasmine.unspy(window, 'setTimeout') solves setTimeout not working for me.
Wow, that was annoying.

I wish we didn’t have to use Jasmine, especially 1.3. Definitely not a fan.


#20

You don’t have to use Jasmine:

It’s just what we’ve been using and all the tutorials are written in, so there is the most help for it. (And we haven’t gotten around to documenting exactly what it takes to set up a custom package testing system.)