Using react hooks I am fetching some data on component mount. I set loading in state.
const fetchData = () => {
setIsLoading(true);
fetch(url)
.then(response => response.json())
.then(data => {
setData([data?.data]);
setIsLoading(false);
})
.catch(error => {
setIsLoading(false);
console.error(error);
});
};
useEffect(() => {
fetchData();
}, []);
I want to test a component with a data fetched. Here's what I tried:
const expectedResponse = {
data: [{ id: 'id', name: 'name' }],
};
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(
new Response(JSON.stringify(expectedResponse)),
);
Also with fetch-mock:
import fetchMock from 'fetch-mock';
fetchMock.restore().get(ENDPOINT, {
data: [
{ id: 'id', name: 'name' },
],
});
in both cases data is not fetched. When I check the component's snapshot I see the Loader indicator. Looks like it is never loaded?
Any advices?
You should provide as much code as possible, and ideally a working example for future questions as this helps us understand better what you are trying to solve.
I don´t have any context of the test, but I assume you can be checking the snapshot synchronously, and the Promise is not being resolved before the check. You can use any of the async methods that Testing Library provides.
The async methods do polling and try to execute the queries several times until it reaches a timeout (that you can set in the Testing Library config), and if you want to know what happens after every execution, you can wrap some function inside a waitFor call:
test('an async example', async () => {
renderXXX();
await waitFor(() => {
screen.logTestingPlaygroundURL();
expect(screen.queryByTest('just an example').toBeInTheDocument();
});
});
If it also helps, here is another tip that might help as well, regarding how to mock API requests:
Mocking react custom hook
Related
I want to automate the following test scenario:
I render some arbitrary React component tree.
I make some action (scroll some container / click a button / ...)
I assert if any components have re-rendered since taking action 2.
What would be a good way to do this? We currently use Jest, Cypress and react-test-renderer in our project - it would be great to find a way to this using those. But this is not strictly necessary.
I need this to catch improperly memoized useSelector calls high up the component tree, which result in most of the app re-rendering - we keep running into this problem over and over.
As mentioned by #morganney, welldone-software/why-did-you-render has a bunch of Cypress test examples already in the project.
Here is one example:
Cypress.Commands.add('visitAndSpyConsole', (url, cb) => {
const context = {};
cy.visit(url, {
onBeforeLoad: win => {
cy.spy(win.console, 'log');
cy.spy(win.console, 'group');
},
onLoad: win => context.win = win,
});
cy.waitFor(context.win)
.then(() => cb(context.win.console));
});
it('Child of Pure Component', () => {
cy.visitAndSpyConsole('/#childOfPureComponent', console => {
cy.contains('button', 'clicks:').click();
cy.contains('button', 'clicks:').click();
cy.contains('button', 'clicks:').should('contain', '2');
expect(console.group).to.be.calledWithMatches([
{ match: 'PureFather', times: 2 },
{ match: /props.*children\W/, times: 2 },
]);
expect(console.log).to.be.calledWithMatches([
{ match: 'syntax always produces a *NEW* immutable React element', times: 2 },
]);
});
});
If you add more details to the question, I can give you a specific example.
Generally re-render checks'/optimisation's are done at development time using Reacts Profiler API.
https://reactjs.org/docs/profiler.html
this tells you why something re-rendered, times re-rendered.
EDIT#2
I was reading a blog and i found a better way to integrate it into a test.
In my last approach you would have to have a callback to log and a babel-plugin to wrap your desired component for test.
with approach 2 you can just use -
import TestRenderer from 'react-test-renderer';
const testRenderer = TestRenderer.create(
<Link page="https://www.facebook.com/">Facebook</Link>
);
console.log(testRenderer.toJSON());
// { type: 'a',
// props: { href: 'https://www.facebook.com/' },
// children: [ 'Facebook' ] }
it prints out host(in your case react) component tree in json, You can use Jest’s snapshot testing feature to automatically save a copy of the JSON tree and check if anything has changes and compute your rerenders.
I have 2 solutions for your problem.
The first one is implemented with Puppeteer only
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto('http://localhost:3000/path-to-your-page')
await page.setViewport({ width: 1276, height: 689 });
await navigationPromise;
// Begin profiling...
await page.tracing.start({ path: 'profiling-results.json' });
// do action
await page.$eval('.class_name:last-child', e => {
e.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
});
// Stop profliling
await page.tracing.stop();
await browser.close();
})()
The second one is implemented with Puppeteer and the use of React Profiler API directly in your app:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto('http://localhost:3000/path-to-your-page')
await page.setViewport({ width: 1276, height: 689 });
await navigationPromise;
page
.on('console', message =>
console.log(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`))
// do action
await page.$eval('.class_name:last-child or any other CSS selector/id/etc', e => {
e.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
});
await browser.close();
})()
And for the second solution to work add this code to your App.jsx:
<Profiler
id="profile-all"
onRender={
(id, phase, actualDuration) => {
console.log({id, phase, actualDuration})
}
}>
{/*all of the markdown should go inside*/}
</Profiler>
I have hook which downloads data from firestore and I am trying to mock this data in cypress tests. I am trying to do it with cy.intercept() but is not working as cypress still takes data from firestore instead of taking in it from todos.json(). Can it be fixed or maybe I should take different approach to actually try mock hook. If so - how can it be done?
describe("Todo actions", () => {
beforeEach(() => {
cy.visit("localhost:3000");
});
it("filtering by name", () => {
cy.intercept(
"GET",
"https://firestore.googleapis.com/google.firestore.v1.Firestore/Listen/channel?gsessionid=Jxzmhr-WFyT0LyY1M3YlRf__I7Mjuh0z3h-hTaCRr0E&VER=8&database=projects%2Ftodos-d2fcf%2Fdatabases%2F(default)&RID=rpc&SID=ZNjOxc2aZe3ayyf87HY8IA&CI=0&AID=0&TYPE=xmlhttp&zx=iq073faeg7z7&t=1",
{ fixture: "todos.json" }
);
});
});
I am working on a Instagram clone project. I am doing the profile page part and got some problems.
I am trying to use hooks (useState) to set my response from the server with the user document. My problem is that when I use the useEffect (only once with []), the username state is not set.
I let useEffect run infinitely, and I found out that the state is set after some little time.
const getUser = async () => {
await axios.get(`/user/getbyusername/${props.match.params.username}`)
.then(res => {
console.log(res.data) // PRINTS DATA CORRECTLY
setProfileUser({
_id: res.data._id,
username: res.data.username,
fullName: res.data.fullName,
followersCount: res.data.followersCount,
followingCount: res.data.followingCount,
profileImage_PublicId: res.data.profileImage_PublicId,
bio: res.data.bio
})
console.log(profileUser)
})
}
// DOES NOT SET ANY DATA
useEffect(() => {
getUser(); // async function and awaits for the server response
// HERE, I call getUserPosts using profileUser._id, and it profileUser._id is not set yet
getPosts(); // async function as well
}, [])
Checking if setProfileUser(...) is correct, and it is because it sets the data, but after some time even though I could console.log(res.data) in the correct first time using [].
// WORKS, sets the user state after two runs. NOT GOOD PRACTICE
useEffect(() => {
getUser();
})
Add another useEffect hook that runs when profileUser changes, so that it runs after setProfileUser finishes:
useEffect(getUser, []);
useEffect(() => {
if (profileUser) {
getPosts();
}
},
[profileUser],
);
I am trying to test a react functional component using hooks. The useEffect hook makes a call to a third part API which then calls setState on return.
I have the test working but keep getting a warning that an update to the component was not wrapped in act.
The problem I have is that the expectation is inside a moxios.wait promise and therefore I cannot wrap that in an act function and then assert on the result of that.
The test passes but I know not wrapping code that updates state in an act function could lead to false positives or uncovered bugs. I'm just wondering how I should be testing this.
I've tried using the new async await act function in the react 16.9.0 alpha release as well as numerous suggestions I've found in many github issues like jest setTimers and none seem to solve the issue.
The component
const Benefits = props => {
const [benefits, setBenefits] = useState([])
const [editing, setEditing] = useState(false)
const [editingBenefit, setEditingBenefit] = useState({id: null, name: '', category: ''})
useEffect(() => {
axios.get('#someurl')
.then(response => {
setBenefits(response.data)
})
}, [])
}
The test
describe('Benefits', () => {
it('fetches the list of benefits from an api and populates the benefits table', (done) => {
const { rerender } = render(<Benefits />)
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response: benefits
}).then(() => {
expect(document.querySelectorAll('tbody > tr').length).toBe(2)
done()
})
})
})
})
The test passes but I get the following warning
Warning: An update to Benefits inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser.
in Benefits (at benefits.spec.js:28)
from react 16.9.0 you can use async/await act
Your code should look like this
describe('Benefits', () => {
it('fetches the list of benefits from an api and populates the benefits table', async() => {
const { rerender } = render(<Benefits />);
await moxios.wait(jest.fn);
await act(async() => {
const request = moxios.requests.mostRecent()
await request.respondWith({
status: 200,
response: benefits
});
});
expect(document.querySelectorAll('tbody > tr').length).toBe(2)
})
I use jest.fn in moxios.wait because it needs callback function
I'm quite new to Jest and admittedly am no expert at testing async code...
I have a simple Fetch helper I use:
export function fetchHelper(url, opts) {
return fetch(url, options)
.then((response) => {
if (response.ok) {
return Promise.resolve(response);
}
const error = new Error(response.statusText || response.status);
error.response = response;
return Promise.reject(error);
});
}
And implement it like so:
export function getSomeData() {
return (dispatch) => {
return fetchHelper('http://datasource.com/').then((res) => {
dispatch(setLoading(true));
return res.json();
}).then((data) => {
dispatch(setData(data));
dispatch(setLoading(false));
}).catch(() => {
dispatch(setFail());
dispatch(setLoading(false));
});
};
}
However I want to test that the correct dispatches are fired in the correct circumstances and in the correct order.
This used to be quite easy with a sinon.spy(), but I can't quite figure out how to replicate this in Jest. Ideally I'd like my test to look something like this:
expect(spy.args[0][0]).toBe({
type: SET_LOADING_STATE,
value: true,
});
expect(spy.args[1][0]).toBe({
type: SET_DATA,
value: {...},
});
Thanks in advance for any help or advice!
Answer as of January 2018
The redux docs have a great article on testing async action creators*:
For async action creators using Redux Thunk or other middleware, it's best to completely mock the Redux store for tests. You can apply the middleware to a mock store using redux-mock-store. You can also use fetch-mock to mock the HTTP requests.
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
import fetchMock from 'fetch-mock'
import expect from 'expect' // You can use any testing library
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('async actions', () => {
afterEach(() => {
fetchMock.reset()
fetchMock.restore()
})
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => {
fetchMock
.getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
})
Their approach is not to use jest (or sinon) to spy, but to use a mock store and assert the dispatched actions. This has the advantage of being able to handle thunks dispatching thunks, which can be very difficult to do with spies.
This is all straight from the docs, but let me know if you want me to create an example for your thunk.
* (this quote is no longer in the article as of January 2023 and the recommendations have changed dramatically, see comments on this answer for further info)
Answer as of January 2018
For async action creators using Redux Thunk or other middleware, it's best to completely mock the Redux store for tests. You can apply the middleware to a mock store using redux-mock-store. In order to mock the HTTP request, you can make use of nock.
According to redux-mock-store documentation, you will need to call store.getActions() at the end of the request to test asynchronous actions, you can configure your test like
mockStore(getState?: Object,Function) => store: Function Returns an
instance of the configured mock store. If you want to reset your store
after every test, you should call this function.
store.dispatch(action) => action Dispatches an action through the
mock store. The action will be stored in an array inside the instance
and executed.
store.getState() => state: Object Returns the state of the mock
store
store.getActions() => actions: Array Returns the actions of the mock
store
store.clearActions() Clears the stored actions
You can write the test action like
import nock from 'nock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
//Configuring a mockStore
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
//Import your actions here
import {setLoading, setData, setFail} from '/path/to/actions';
test('test getSomeData', () => {
const store = mockStore({});
nock('http://datasource.com/', {
reqheaders // you can optionally pass the headers here
}).reply(200, yourMockResponseHere);
const expectedActions = [
setLoading(true),
setData(yourMockResponseHere),
setLoading(false)
];
const dispatchedStore = store.dispatch(
getSomeData()
);
return dispatchedStore.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
P.S. Keep in ming that the mock-store does't update itself when the mocked action are fired and if you are depending on the updated data after the previous action to be used in the next action then you need to write your own instance of it like
const getMockStore = (actions) => {
//action returns the sequence of actions fired and
// hence you can return the store values based the action
if(typeof action[0] === 'undefined') {
return {
reducer: {isLoading: true}
}
} else {
// loop over the actions here and implement what you need just like reducer
}
}
and then configure the mockStore like
const store = mockStore(getMockStore);
Hope it helps. Also check this in redux documentation on testing async action creators
If you're mocking the dispatch function with jest.fn(), you can just access dispatch.mock.calls to get all the calls made to your stub.
const dispatch = jest.fn();
actions.yourAction()(dispatch);
expect(dispatch.mock.calls.length).toBe(1);
expect(dispatch.mock.calls[0]).toBe({
type: SET_DATA,
value: {...},
});
In my answer I am using axios instead of fetch as I don't have much experience on fetch promises, that should not matter to your question. I personally feel very comfortable with axios.
Look at the code sample that I am providing below:
// apiCalls.js
const fetchHelper = (url) => {
return axios.get(url);
}
import * as apiCalls from './apiCalls'
describe('getSomeData', () => {
it('should dispatch SET_LOADING_STATE on start of call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_LOADING_STATE,
value: true,
});
});
it('should dispatch SET_DATA action on successful api call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_DATA,
value: { ...},
});
});
it('should dispatch SET_FAIL action on failed api call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.reject());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_FAIL,
});
});
});
Here I am mocking the fetch helper to return Resolved promise to test success part and reject promise to test failed api call. You can pass arguments to them to validate on response also.
You can implement getSomeData like this:
const getSomeData = () => {
return (dispatch) => {
dispatch(setLoading(true));
return fetchHelper('http://datasource.com/')
.then(response => {
dispatch(setData(response.data));
dispatch(setLoading(false));
})
.catch(error => {
dispatch(setFail());
dispatch(setLoading(false));
})
}
}
I hope this solves your problem. Please comment, if you need any clarification.
P.S You can see by looking at above code why I prefer axios over fetch, saves you from lot of promise resolves! For further reading on it you can refer: https://medium.com/#thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5
Answer relevant as of January 2023
Many helpful answers here from 2018 are now outdated, the answer as of 2023 is to avoid mocking the store and instead use the real store, preferring integration tests (still using jest) over unit tests.
Some highlights from the updated, official Redux testing documentation:
Prefer writing integration tests with everything working together. For a React app using Redux, render a with a real store instance wrapping the components being tested. Interactions with the page being tested should use real Redux logic, with API calls mocked out so app code doesn't have to change, and assert that the UI is updated appropriately.
Do not try to mock selector functions or the React-Redux hooks! Mocking imports from libraries is fragile, and doesn't give you confidence that your actual app code is working.
It goes on to state how to achieve this, with the renderWithProvider function detailed here.
The article it links to for reasoning on this, includes the following quote, explaining the evolution of the thinking of redux testing best practices:
Our docs have always taught the "isolation" approach, and that does especially make sense for reducers and selectors. The "integration" approach was in a minority.
But, RTL and Kent C Dodds have drastically changed the mindset and approach for testing in the React ecosystem. The patterns I see now are about "integration"-style tests - large chunks of code, working together, as they'd be used in a real app.