Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
I'm trying to mock out the a service that returns promises so that I can verify it gets called with the correct parameters. The way the service is called varies based on the
state
and the first call to the service sets the
state
.
When setting the state in the promise it is not updating unless I wrap the assertion in
setTimeout
or completely stub out the promise. Is there a way to do this with just a plain promise and an expect?
My component:
class App extends Component {
constructor(props) {
super(props);
this.state = {results: []};
this.service = props.service;
this.load = this.load.bind(this);
load() {
if (this.state.results.length === 0) {
this.service.load('state is empty')
.then(result => this.setState({results: result.data}));
} else {
this.service.load('state is nonempty')
.then(result => this.setState({results: result.data}));
render() {
return (
<div className="App">
<button id="submit" onClick={this.load}/>
My test:
it('Calls service differently based on results', () => {
const mockLoad = jest.fn((text) => {
return new Promise((resolve, reject) => {
resolve({data: [1, 2]});
const serviceStub = {load: mockLoad};
let component = mount(<App service={serviceStub}/>);
let button = component.find("#submit");
button.simulate('click');
expect(mockLoad).toBeCalledWith('state is empty');
button.simulate('click');
//this assertion fails as the state has not updated and is still 'state is empty'
expect(mockLoad).toBeCalledWith('state is nonempty');
As mentioned, the following works, but I'd rather not wrap the expect if there's a way around it:
setTimeout(() => {
expect(mockLoad).toBeCalledWith('state is nonempty');
done();
}, 50);
I can also change how I mock the function to stub out the promise which will work:
const mockLoad = jest.fn((text) => {
return {
then: function (callback) {
return callback({
data : [1, 2]
But I'd like to just return a promise.
React batches setState
calls for performance reasons, so at this point
expect(mockLoad).toBeCalledWith('state is nonempty');
the condition
if (this.state.results.length === 0) {
is most likely still true
, because data
has not yet been added to state
.
Your best bets here are
Either use forceUpdate between the first and second click event
.
Or split the test into two separate, while extracting common logic outside of the test. Even the it
clause will become more descriptive, for instance: it('calls service correctly when state is empty')
for the first test, and similar for the second one.
I'd favour the second approach.
setState() does not always immediately update the component. It may batch or defer the update until later.
Read more here.
–
–
Using Sinon with Sinon Stub Promise I was able to get this to work. The stub promise library removes the async aspects of the promise, which means that state
gets updated in time for the render:
const sinon = require('sinon');
const sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);
it('Calls service differently based on results', () => {
const mockLoad = jest.fn((text) => {
return sinon.stub().returnsPromise().resolves({data: [1, 2]})();
const serviceStub = {load: mockLoad};
let component = mount(<App service={serviceStub}/>);
let button = component.find("#submit");
button.simulate('click');
expect(mockLoad).toBeCalledWith('state is empty');
button.simulate('click');
expect(mockLoad).toBeCalledWith('state is nonempty');
http://sinonjs.org/
https://github.com/substantial/sinon-stub-promise
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.