4

I can't seem to figure this one out. I'm using create-react-app and it's built in test runner Jest. For all synchronous code it seems to work really well, but when mocking promises I can't seem to get it to work.

A react component has a form that I'm able to simulate a submit.

React component code snippets.

//Top of the page
import {auth} from '../../lib/API_V2'
// ... //

// Handle submit runs when the form is submitted
handleSubmit = (event) => {
  console.log('submit')
  event.preventDefault()
  this.setState(prevState => ({
    ...prevState,
    loading: true
  }))
  console.log('stateSet')
  auth(this.state.userName, this.state.password)
    .then(results => {
      // NEVER RUNS
      console.log('then')
      // stuff omitted
      this.setState(prevState => ({
        ...prevState,
        loading: false
      }))
      this.props.afterAuth()
    })
  .catch(() => {
    // also never runs
    // omitted
    this.setState(prevState => ({
      ...prevState,
      loading: false
    }))
    this.props.afterAuth()
  })
}

Test code

jest.mock('../../lib/API_V2')
it.only(`should mock a login`, () => {
  const myMock = jest.fn()
  const authComp = mount(<AuthComponent afterAuth={myMock}/>)

  authComp.find('.userName').simulate('change', {target: {value: 'userName'}})
  authComp.find('.password').simulate('change', {target: {value: 'password'}})
  expect(authComp.state().userName).toEqual('userName')
  expect(authComp.state().password).toEqual('password')
  authComp.find('[type="submit"]').get(0).click()
  expect(myMock.mock.calls.length).toBe(1) // FAILS
})

The API lib returns a promise. Instead of using that I have a __mocks__/API_V2.js next to it. That looks like this

function auth (lastname, accountNumber) {
  console.log('yay!?')
  return new Promise((resolve) => {
    resolve({
      accountNumber,
      lastName: lastname
    })
  })
}     

My mock test code never seems to be run. If I log the mock function I get function auth() {return mockConstructor.apply(this,arguments);}

I've tried to follow the instructions https://facebook.github.io/jest/docs/tutorial-async.html but it seems as though my mock methods aren't being called. And neither are the actual methods. Instead my call to auth() returns undefined.

Anyone have any ideas?

-- Supplementary Information --

src
  Components
    AuthComponent
      AuthComponent.js
      AuthComponent.test.js
      index.js
  Lib
    API_V2
      API_V2.js
      index.js
      __mocks__
        API_V2.js
1
  • I ended up manually mocking it instead of using the mocks directory. jest.mock('../../lib/API_V2, () => {auth: function ...}) Commented Feb 16, 2017 at 15:54

2 Answers 2

3

I think you're hitting a bug related to this issue: https://github.com/facebook/jest/issues/2070

Since you're actually trying to import a file called API_V2/index.js, you need to mock index.js. However, you're going to have a really bad time doing that, since it'll be a valid mock for every index.js file that you try to mock.

The best way to do this at the moment is to rewrite some of your code to use dependency-injection and pass in a mock to whatever needs to use { auth }

Sign up to request clarification or add additional context in comments.

3 Comments

In my experience it just annoyed me with warnings, but same name mocks didn't get "swapped".
> However, you're going to have a really bad time doing that, since it'll be a valid mock for every index.js file that you try to mock. I don't think this is correct. If he put it in Lib/API_V2/__mocks__/index.js that should work and only mock for Lib/API_V2/index.js
Sorry, just looked at the referenced bug. That's annoying. Maybe just rename the file? ;-)
2

In the new Promise from your mock, even though you immediately resolve, this resolution does not occur synchronously. Promise callbacks always run as an enqueued microtask, so when you simulate a click in your test, the Promise callback in your mock has not yet run (and so myMock has not been called yet, either). This is why your expectation fails.

One (somewhat hacky) way you could work around this issue would be with a setTimeout. setTimeout will enqueue a task, and tasks always run after microtasks. Jest supports async tests via returning Promises from it callbacks, so you might write:

jest.mock('../../lib/API_V2')
it.only(`should mock a login`, () => new Promise(resolve => {
  const myMock = jest.fn()
  const authComp = mount(<AuthComponent afterAuth={myMock}/>)

  authComp.find('.userName').simulate('change', {target: {value: 'userName'}})
  authComp.find('.password').simulate('change', {target: {value: 'password'}})
  expect(authComp.state().userName).toEqual('userName')
  expect(authComp.state().password).toEqual('password')
  authComp.find('[type="submit"]').get(0).click()
  setTimeout(() => {
    expect(myMock.mock.calls.length).toBe(1)
    resolve() // Tell jest this test is done running
  }, 0);
}))

There is a good explanation of how tasks and microtasks work here: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.