Reputation: 758
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
Upvotes: 4
Views: 1730
Reputation: 773
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/
Upvotes: 2
Reputation: 7156
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 }
Upvotes: 3