Wait for fetch() to resolve using Jest for React test? - javascript

In my componentDidMount of a React.Component instance I have a fetch() call that on response calls setState.
I can mock out the request and respond using sinon but I don't know when fetch will have resolved it's promise chain.
componentDidMount() {
fetch(new Request('/blah'))
.then((response) => {
setState(() => {
return newState;
};
});
}
In my test using jest with enzyme:
it('has new behaviour from result of set state', () => {
let component = mount(<Component />);
requests.pop().respond(200);
component.update() // fetch() has not responded yet and
// thus setState has not been called yet
// so does nothing
assertNewBehaviour(); // fails
// now setState occurs after fetch() responds sometime after
});
Do I need to flush the Promise queue/callback queue or something similar? I could do a repeated check for newBehaviour with a timeout but that's less than ideal.

The best answer it seems is to be use a container pattern and pass down the API data from a container class with separated concerns and test the components separately. This allows the component under test to simply take the API data as props and makes it much more testable.

Since you're not making any real api calls or other time-consuming operations, the asynchronous operation will resolve in a predictably short time.
You can therefore simply wait a while.
it('has new behaviour from result of set state', (done) => {
let component = mount(<Component />);
requests.pop().respond(200);
setTimeout(() => {
try {
component.update();
assertNewBehaviour();
done();
} catch (error) {
done(error);
}
}, 1000);
});

The react testing library has a waitFor function that works perfectly for this case scenario.
I will give an example with hooks and function as that is the current react pattern. Lets say you have a component similar to this one:
export function TestingComponent(props: Props) {
const [banners, setBanners] = useState<MyType>([]);
React.useEffect(() => {
const response = await get("/...");
setBanners(response.banners);
}, []);
return (
{banners.length > 0 ? <Component> : </NoComponent>}
);
}
Now you can write a test like this to make sure that when banners are set Component is rendered
test("when the banner matches the url it renders", async () => {
const {container} = render(<TestingComponent />);
await waitFor(() => {expect(...).toBe(...)});
});
waitFor will wait for the condition in the function to be met before proceeding. There is a timeout that will fail the test if the condition is not met in X time. Check out the react testing library docs for more info

Related

Waiting for React component state to update before testing with Jest

I have a component with a handleAdd function. This function calls a library, which in turn calls axios and returns a promise. Once that's resolved, the handleAdd() method updates component state which in turns renders child(ren).
In other words, it checks with the server first to make sure the item is added before displaying it locally.
When testing with Jest, i have to await sleep for a few msec before the expect runs otherwise the shallow render isn't updated yet, even though I mock/overwrite the api call. There's some delay between the promise resolving, rerender and expect(). Here's what that kind of looks like:
it('adds a thing', async () => {
ThingManager.default.addPlan = () => {
const response = new Promise((resolve, reject) => { resolve() })
return response;
}
const wrapper = shallow(<Home />)
wrapper.find('button').simulate('click')
const input = wrapper.find('#plan-title')
input.simulate('change', { target: { value: 'TEST ITEM' } })
await sleep(500) // without it, <Thing /> isn't rendered yet.
expect(wrapper.find('Thing').length).toBe(1)
});
What's the proper way of doing this?
Just wanted to throw it out there that I use simple setTimeout with the combination of jest's done().
EDIT
it('sample test case', (done) => {
// initialize your component
setTimeout(function () {
// expect something that's available after the timeout
done();
}, 500);
});
You can use act from test-utils. That is what the React docs recommend, but I have had more success with waitFor from testing-library.

Mocking a function returned by a react hook

I'm building a pagination using the useQuery hook as part of the Apollo Client in React, which exposes a function called fetchMore seen here: https://www.apollographql.com/docs/react/data/pagination/
Everything works fine, but I'm trying to write a test one of the use cases, which is when the fetchMore function fails due to a network error. The code in my component looks like this.
const App = () => {
// Some other component logic
const {loading, data, error, fetchMore} = useQuery(QUERY)
const handleChange = () => {
fetchMore({
variables: {
offset: data.feed.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
feed: [...prev.feed, ...fetchMoreResult.feed]
});
}
}).catch((e) => {
// handle the error
})
}
}
Basically I want to test the case where the fetchMore function function throws an Error. I DON'T want to mock the entire useQuery though, just the fetchMore function. What would be the best way to mock just the fetchMore function in my test?
One way to do it is to just mock the hook
In your spec file:
import { useQuery } from '#apollo/react-hooks'
jest.mock('#apollo/react-hooks',() => ({
__esModule:true
useQuery:jest.fn()
});
console.log(useQuery) // mock function - do whatever you want!
/*
e.g. useQuery.mockImplementation(() => ({
data:...
loading:...
fetchMore:jest.fn(() => throw new Error('bad'))
});
*/
You could also mock the stuff that goes on "behind the scenes" to simulate a network error, do whatever you need to to test your catch.
EDIT:
Search for __esModule: true on this page and you'll understand.
It's probably easier to just mock the whole function and return everything as mock data. But you can unmock it to use the real one so as not to conflict with other tests.

Enzyme: on simulating "click", test asynchronously changed state value

I am trying to test a state value (say: state.name) which asynchronously changes on a button click.
Initially
state.nameis "Random Value"
When a button is clicked state.name changes to "peter" from "Random Value"
works as expected in browser
Need it to work the same on button.simulate('click') in my test
My component App.js:
import React, { Component } from 'react';
class App extends Component {
state = {name: "Random Value"};
render() {
return (
<div className="App">
<div>{this.state.name}</div>
<button onClick={this.handleClick}>GetData</button>
</div>
);
}
handleClick = () => {
const currentContext = this;
fetch('http://api-call/getdata')
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
// jsonData.name = "peter"
currentContext.setState({name: jsonData.name});
})
}
}
export default App;
The test file App.test.js
describe('<App/>', () => {
it('should handle click correctly', () => {
const wrapper = shallow(<App />);
expect(wrapper.find('button').length).toBe(1);
expect(wrapper.state().name).toEqual("Random Value");
wrapper.find('button').simulate('click');
expect(wrapper.update().state().name).toEqual("peter");
});
});
Result:
// FAILED!
Expected value to equal:
"peter"
Received:
"Random Value"
What else have I tried?
All sorts of solutions out there like using async await, setImmediate and then wrapper.update() and many other things. Maybe I did something wrong or missed something. Anyways I have spent an evening trying to do it. I need help from enzyme experts.
Thanks
First you need mock fetch somehow. Sending real request not only breaks isolation of unit-tests and adds risks of inconsistency. It's also harder to wait for response when you don't know when it may finish. There are different ways to achieve that. jest-fetch-mock is one of them.
Also I advice you don't check for state but rather check for render() results.
function getName(wrapper) {
return wrapper.find('.App > div').at(0).props().children;
}
it('should handle click correctly', async () => {
fetch.mockResponseOnce(JSON.stringify({ name: '12345' }));
const wrapper = shallow(<App />);
expect(wrapper.find('button').length).toBe(1);
expect(getName(wrapper)).toEqual("Random Value");
wrapper.find('button').simulate('click');
await Promise.resolve();
expect(getName(wrapper)).toEqual("peter");
});
What's going on here. await Promise.resolve() is just just waits until all promises already resolved are run. It means without that our mocked response will not run between button click and expect runs.
Another way to get positive result is making handleClick() to return Promise we can await for:
...
handleClick = () => {
const currentContext = this;
return fetch('http://api-call/getdata')
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
// jsonData.name = "peter"
currentContext.setState({name: jsonData.name});
})
}
....
it('should handle click correctly', async () => {
....
expect(getName(wrapper)).toEqual("Random Value");
await wrapper.find('button').simulate('click');
expect(getName(wrapper)).toEqual("peter");
});
or without async-await syntax:
it('should handle click correctly', () => {
....
expect(getName(wrapper)).toEqual("Random Value");
return wrapper.find('button').simulate('click').then(() => {
expect(getName(wrapper)).toEqual("peter");
});
});
But I really don't like this way since event handler have to return a Promise that it typically does not do.
More on microtasks(Promises)/macrotasks(timers, events) yuo may read here: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
More on testing async code in jest you better check their docs: https://jestjs.io/docs/en/asynchronous

How would I test this promise based code with jest?

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)
})
}
If 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 complete before your changeData() -[async function] completes.
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 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.
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 comment where those assertions would go. The following uses Promise.resolve() and Promise.reject() to trigger the outcomes 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.com/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?

React Jest Test Fails when setState Called in Promise

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}/>
</div>
);
}
}
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');
});
See:
http://sinonjs.org/
https://github.com/substantial/sinon-stub-promise

Categories

Resources