How would I test this code in jest? I'd like to make sure that the error and success of the passed promise is being called as needed. I'm sure it's something sorta simple, but it's driving me crazy. Thanks very much.
handleStatusChangeRequest (changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
How would I test this code in jest? I'd like to make sure that the error and success of the passed promise is being called as needed. I'm sure it's something sorta simple, but it's driving me crazy. Thanks very much.
handleStatusChangeRequest (changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
Share
Improve this question
edited Nov 2, 2018 at 13:19
skyboyer
23.8k7 gold badges62 silver badges71 bronze badges
asked Oct 6, 2018 at 1:50
Cameron StrandbergCameron Strandberg
1342 silver badges11 bronze badges
2
- What do you have in mind for assertions? Snapshots, mocking and verifying method calls, ...? – stealththeninja Commented Oct 6, 2018 at 2:01
-
It's just the full jest test suite . . . not bringing in mocha or anything like that. Just within the
react-create-app
(sp?) tool chain. – Cameron Strandberg Commented Oct 6, 2018 at 2:16
4 Answers
Reset to default 2If your code uses promises, there is a nice way to handle asynchronous tests. Just return a promise
from your test, and Jest
will wait for that promise
to resolve.
If the promise is rejected, the test will automatically fail.
For example, let's say that changeData
, instead of using a callback, returns a promise
that is supposed to resolve to the string "status has been successfully modified".
Be sure to return
the promise
- if you omit this return statement, your test will plete before your changeData()
-[async function] pletes.
Here's a convenient and easy to follow pattern
test('if the data is changed', () => {
return changeData().then((data) => {
expect(data).toBe('status has been successfully modified');
});
})
Happy testing :)
This could be refactored, but for the sake of demonstration, I left the repeating bits in.
In example.spec.js
, the callback, changeEntryStatus
, is stubbed to return a promise. In order to check if other instance methods (this.method
) were called, they are first mocked, then assertions are called on the mock after running the method being tested. Learn more in the Jest docs. (See my thoughts on mocking methods of the unit being tested at the bottom.)
Run the example on repl.it.
example.js:
class Example {
handleStatusChangeRequest(changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
handleStatusChangeSuccess() {
console.log('stubbed handleStatusChangeSuccess')
}
handleErrorDisplay(error) {
console.log('stubbed handleErrorDisplay:', error)
}
}
module.exports = Example;
example.spec.js:
const Example = require('./entryStatus')
describe('handleStatusChangeRequest', () => {
it('should run the changeEntryStatus callback', () => {
const {handleStatusChangeRequest} = new Example()
const stub = jest.fn().mockResolvedValue()
handleStatusChangeRequest(stub)
// must return because handleStatusChangeRequest is asynchronous
return expect(stub).toHaveBeenCalled()
});
it('should call example.handleStatusChangeSuccess', async () => {
const example = new Example()
const stub = jest.fn().mockResolvedValue()
example.handleStatusChangeSuccess = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleStatusChangeSuccess).toHaveBeenCalled();
})
it('should call example.handleErrorDisplay', async () => {
const example = new Example()
const fakeError = { code: 'fake_error_code' }
const stub = jest.fn().mockRejectedValue(fakeError)
example.handleErrorDisplay = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleErrorDisplay).toHaveBeenCalled()
expect(example.handleErrorDisplay).toHaveBeenCalledWith(fakeError)
});
});
Opinionated Disclaimer: Mocking methods of the unit under test is a smell. Consider checking for the expected effects of calling handleStatusChangeSuccess
and handleErrorDisplay
instead of checking to see if they were called. Then don't even expose those methods publicly unless consumers of the class need access.
Opinionated Disclaimer: Mocking methods of the unit under test is a smell. Consider checking for the expected effects of calling
handleStatusChangeSuccess
andhandleErrorDisplay
instead of checking to see if they were called. Then don't even expose those methods publicly unless consumers of the class need access.
I wholeheartedly agree with webprojohn's disclaimer. Mocks are a smell as tests should assert the behavior of the code, not its implementation. Testing the latter makes the code brittle to change.
Stepping off my soapbox... :) We're looking for a way to test an asynchronous method. I'm not sure what assertions your tests should make to verify the behavior inside handleStatusChangeSuccess()
and handleErrorDisplay(err)
so the example below leaves a ment where those assertions would go. The following uses Promise.resolve()
and Promise.reject()
to trigger the outes to test. I've used async/await, Jest has other async examples in their docs.
const Example = require('./example')
describe('handleStatusChangeRequest', () => {
it('should resolve successfully', async () => {
const {handleStatusChangeRequest} = new Example();
const resolvePromise = () => Promise.resolve();
await handleStatusChangeRequest(resolvePromise);
// resolution assertions here
});
it('should resolve errors', async () => {
const {handleStatusChangeRequest} = new Example();
const fakeError = new Error('eep');
const rejectPromise = () => Promise.reject(fakeError);
// if your method doesn't throw, we can remove this try/catch
// block and the fail() polyfill
try {
await example.handleStatusChangeRequest(rejectPromise);
// if we don't throw our test shouldn't get here, so we
// polyfill a fail() method since Jest doesn't give us one.
// See https://github./facebook/jest/issues/2129
expect(true).toBe(false);
}
catch (e) {
// rejection assertions here
}
});
});
The answer I have looks so:
Success tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleStatusChangeSuccess')
await instance.handleStatusChangeRequest(() => Promise.resolve('cool man'))
expect(spy).toHaveBeenCalledTimes(1)
Error tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleErrorDisplay')
await instance.handleStatusChangeRequest(() => Promise.reject(Error('shit')))
expect(spy).toHaveBeenCalledTimes(1)
As I stated above, the handleStatusChangeSuccess
and handleError
methods are test else where with some snapshots (they just set state and render out some different jsx). I feel pretty good about this. I'm using spys/mocks, but I'm testing the implementation functions elsewhere. Sufficient?