React-native: Promises broken in Jest tests

Created on 23 Feb 2016  ·  11Comments  ·  Source: facebook/react-native

We have a very simple test case that reproduces this issue:

describe('promises', function() {
  it('should resolve', function(done) {
    new Promise((resolve, reject) => {
      resolve('it works');
    }).then((result) => {
      expect(result).toEqual('it works');
      done();
    }, (error) => {
      expect(error).toBeFalsy();
      done(error);
    });
  });
});

result:

Using Jest CLI v0.8.2, jasmine2
 FAIL  __tests__/api_tests.js (5.02s)
● promises › it should resolve
  - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
1 test failed, 0 tests passed (1 total in 1 test suite, run time 5.736s)
npm ERR! Test failed.  See above for more details.

expected:

Using Jest CLI v0.8.2, jasmine2
 PASS  __tests__/api_tests.js (0.212s)
1 test passed (1 total in 1 test suite, run time 0.894s)

We've tracked it down to this commit:

https://github.com/facebook/react-native/commit/3ff39870ce776c653823e1733363be0401896294

and this line in file jestSupport/env.js

global.Promise = require('promise');

When we comment out this line the promise resolves as expected.

AsyncStorage Locked

Most helpful comment

@cpojer
I also got this error, but I found an interesting law here. Promise wrote in ./jestSupport/env.js can resolve but if it is wrote in some test files, it will not resolve so leading to error:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL

I was trying to mock AsyncStorage in my test, the code below can reproduce that bug. I had two versions for the mock, versionA works but versionB does not.

VersionA

./jestSupport/AsyncStorage.js

var AsyncStorage = {
  getItem: jest.fn().mockReturnValue(
    new Promise((resolve, reject) => {
      resolve(JSON.stringify({
        id: 32,
        name: "Grace Han"
      }))
    })
  ),

  setItem: jest.fn(),
}

module.exports = AsyncStorage

./jestSupport/env.js

import AsyncStorage from './AsyncStorage'
jest.setMock('AsyncStorage', AsyncStorage)

actions-test.js

// Some other test code

it('should get user info', () => {
    const expectedActions = [
      { type: PATIENT_AUTH.GET_DEFAULT_USERINFO }
    ]
    const store = mockStore({})
    //the action: getUserInfo will call AsyncStorage.getItem() 
    return store.dispatch(actions.getUserInfo())
      .then(() => {
        expect(store.getActions()[0].type).toEqual(expectedActions[0].type)
      })
  })

VersionB

./jestSupport/AsyncStorage.js

var AsyncStorage = {
  getItem: jest.fn(),
  setItem: jest.fn(),
}

module.exports = AsyncStorage

./jestSupport/env.js

import AsyncStorage from './AsyncStorage'
jest.setMock('AsyncStorage', AsyncStorage)

actions-test.js

// Some other test code

it('should get user info', () => {
    const expectedActions = [
      { type: PATIENT_AUTH.GET_DEFAULT_USERINFO }
    ]
    const store = mockStore({})
    const AsyncStorage = require('react-native').AsyncStorage
    AsyncStorage.getItem.mockReturnValue(
      new Promise((resolve, reject) => {
        resolve(JSON.stringify({
          id: 32,
          name: "Grace Han"
        }))
      })
    )
   //the action: getUserInfo will call AsyncStorage.getItem() 
    return store.dispatch(actions.getUserInfo())
      .then(() => {
        expect(store.getActions()[0].type).toEqual(expectedActions[0].type)
      })
  })

Actually versionB can meet my requirements, because I want to mock different return values for different test, versionA can only return then same promise for me. So this troubles me a lot.
I had seen the Solutions but that does not work for me.
Hope this can help for tracking down the reason. and wait for response.

All 11 comments

@cpojer :)

Ugh, this is so annoying that it keeps popping up. Once the testing docs are rewritten, this will hopefully not be an issue any longer. I'll take a look soon.

great, sorry for bothering you on a Sunday :)

Any update on this issue? I've just started getting it and don't really want to touch the library...

Thank you for your patience. We launched Jest 14.0 with experimental react-native support:

Please feel free to create new issues after trying out the new integration if any issues remain.

@cpojer
I also got this error, but I found an interesting law here. Promise wrote in ./jestSupport/env.js can resolve but if it is wrote in some test files, it will not resolve so leading to error:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL

I was trying to mock AsyncStorage in my test, the code below can reproduce that bug. I had two versions for the mock, versionA works but versionB does not.

VersionA

./jestSupport/AsyncStorage.js

var AsyncStorage = {
  getItem: jest.fn().mockReturnValue(
    new Promise((resolve, reject) => {
      resolve(JSON.stringify({
        id: 32,
        name: "Grace Han"
      }))
    })
  ),

  setItem: jest.fn(),
}

module.exports = AsyncStorage

./jestSupport/env.js

import AsyncStorage from './AsyncStorage'
jest.setMock('AsyncStorage', AsyncStorage)

actions-test.js

// Some other test code

it('should get user info', () => {
    const expectedActions = [
      { type: PATIENT_AUTH.GET_DEFAULT_USERINFO }
    ]
    const store = mockStore({})
    //the action: getUserInfo will call AsyncStorage.getItem() 
    return store.dispatch(actions.getUserInfo())
      .then(() => {
        expect(store.getActions()[0].type).toEqual(expectedActions[0].type)
      })
  })

VersionB

./jestSupport/AsyncStorage.js

var AsyncStorage = {
  getItem: jest.fn(),
  setItem: jest.fn(),
}

module.exports = AsyncStorage

./jestSupport/env.js

import AsyncStorage from './AsyncStorage'
jest.setMock('AsyncStorage', AsyncStorage)

actions-test.js

// Some other test code

it('should get user info', () => {
    const expectedActions = [
      { type: PATIENT_AUTH.GET_DEFAULT_USERINFO }
    ]
    const store = mockStore({})
    const AsyncStorage = require('react-native').AsyncStorage
    AsyncStorage.getItem.mockReturnValue(
      new Promise((resolve, reject) => {
        resolve(JSON.stringify({
          id: 32,
          name: "Grace Han"
        }))
      })
    )
   //the action: getUserInfo will call AsyncStorage.getItem() 
    return store.dispatch(actions.getUserInfo())
      .then(() => {
        expect(store.getActions()[0].type).toEqual(expectedActions[0].type)
      })
  })

Actually versionB can meet my requirements, because I want to mock different return values for different test, versionA can only return then same promise for me. So this troubles me a lot.
I had seen the Solutions but that does not work for me.
Hope this can help for tracking down the reason. and wait for response.

Yeah. I'm getting weird results:

    it("promises work?", () => {
      return Promise.resolve().then(() => {
        expect(true).toBe(false)
      })
    })

    it("async works?", async () => {
      await Promise.resolve()
      expect(true).toBe(false)
    })

both time out instead of hitting the expectation

For what it's worth:

Using Jest CLI v14.1.0, jasmine2, babel-jest, jest-react-native preset

I'm wondering is this issue still not fixed ? (jest-cli: 14.1.0)
Im trying to test a view method called in chain of bluebird promises and get this error all the time
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. at callback [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:477:19) at Timer.listOnTimeout (timers.js:92:15)
But if I move this async it to the top of my describe block where are 4 other sync tests, It passes.

first time I noticed that replacing
import Promise from 'bluebird';
for
const Promise = require('bluebird');
helps. But there is still some problem with mixing async tests that relates to testing methods with use of Promises with sync tests. And yes I pass done callback and call it when test should be finished.

looks like it's fixed on "jest-cli": "15.1.1".

I was still seeing this issue with v15.1.1, but fixed it by saving Promise, then restoring it after requiring (not importing) react-native.

//importing 'react-native' seems to break Promise and setTimeout.
//use require() instead of import, so we can save these first, and restore them after require('react-native')
const SavePromise = Promise;
const saveSetTimeout = setTimeout;

import React, { Component } from 'react';
const {
  View,
  Text,
  StyleSheet,
  Dimensions,
  Platform
} = require('react-native');

Promise = SavePromise;
setTimeout = saveSetTimeout;

it("promises work?", () => {
  return Promise.resolve().then(() => {
    expect(true).toBe(true)
  })
})

it("async works?", async () => {
  await Promise.resolve()
  expect(true).toBe(true)
})

it('works with async/await', async () => {
  const userName = await 'Mark'
  expect(userName).toEqual('Mark');
});

it('works after timeout', () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      expect(true).toBe(true)
      resolve()
    }, 10)
  })
})

Since this issue has been closed, I opened a new one here with updated details and the latest versions of Jest and React Native.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aniss picture aniss  ·  3Comments

upbit picture upbit  ·  3Comments

despairblue picture despairblue  ·  3Comments

anchetaWern picture anchetaWern  ·  3Comments

josev55 picture josev55  ·  3Comments