checking a component is rendered without setting state in enzyme - using fetch - javascript

i am trying to run a basic application, i am calling an api which is running in node layer. i am using proxy in package.json of react application, thats why i call fetch with relative url.
node is running in port 4000 and react is running in 3000.
so when i call api after success i will change the state of loading to false , initially when api call request happens loader will come since loading is true after that DataComponent will come on success.
Basically initital loading will be false after response comes setting it to false to load DataComponent
Here i am not able to see the DataComponent, How can i check the DataComponent is rendered.
// app.js
class App extends Component {
state = {
loading: true,
data: null
}
componentDidMount() {
this.fetchData();
}
fetchData = async() => {
const jsonRes = await fetch("/api/user");
const data = await jsonRes.json();
this.setState({
loading: false,
data
})
}
render() {
if (this.state.loading) {
return ( <
div className = "laoder-container" > < Loader / > < /div>
)
} else {
return ( <
DataComponent data = {
this.state.data
}
/>
)
}
}
}
//app.test.js
import React from 'react'
import App from './App';
import {
mount
} from 'enzyme'
describe('app.js', () => {
it('should render the app component once data is there', () => {
global.fetch = jest.fn().mockImplementation(() => {
var p = new Promise((resolve, reject) => {
resolve({
ok: true,
json: function() {
return {
userName: 'John',
surName: 'Doe'
}
}
});
});
return p;
});
})
const wrapper = mount( < App / > )
expect(fetch).toBeCalled();
wrapper.update();
console.log(wrapper.debug()) // not showing the Data component
})

The problem is that the fetch is happening asynchronously. By the time you call wrapper.debug() the state hasn't been able to update, because the fetch response has been put on the event stack. So the fetch has been called, but it hasn't returned any response.
You can see this by updating the test to something like this:
// mock out fetch...
const wrapper = mount(<App />);
expect(fetch).toBeCalled();
setTimeout(() => {
wrapper.update();
console.log(wrapper.debug()); // should be showing the Data component now
}, 0);
That allows the promise callbacks to be called before trying to see the rendered markup by placing the update/debug code at the end of the event stack.
You could wrap that setTimeout in a promise to return from the it callback function, and place any expects inside of it before you resolve (otherwise it may never actually run the expect functions before the test ends).
// mock out fetch...
const wrapper = mount(<App />);
expect(fetch).toBeCalled();
return new Promise((resolve) => {
setTimeout(() => {
wrapper.update();
console.log(wrapper.debug()); // should be showing the Data component now
expect(...)
resolve();
}, 0);
});
An alternative would be to just test that the result of the data renders what you are expecting when the state has particular data:
it("should render the app component once data is there", () => {
const wrapper = shallow(<App />);
wrapper.setState({
loading: false,
data: {
userName: "John",
surName: "Doe"
}
});
console.log(wrapper.debug()); // You'll see the DataComponent
});

Related

How to Mock A Simple API call (Get Todos ) in ComponentDidMount - React + Typescript + Jest + Enzyme

The app
The app is a simple To Do List. This App gets the todos from https://jsonplaceholder.typicode.com/todos?&_limit=5.
What I am trying to do
Test an API call that is executed in ComponentDidMount in App.tsx.
I want to mock the API call and return a list with two items. Then check if there are two items in the array or state.
What files are important for you?
App.tsx (Component to be tested)
ToDoList.test.tsx (Contains test function)
Small part of App.tsx to simplify it
class App extends Component {
public state: IState = {
items: [],
value : 5,
deleteItemParent : this.deleteItemParent
};
getAllTodos = async () => {
await fetch(
`https://jsonplaceholder.typicode.com/todos?&_limit=${this.state.value}`
)
.then((response) => response.json())
.then((json) => {
this.setState({ items: json })
});
};
componentDidMount() {
this.getAllTodos();
}
componentDidUpdate(prevProps : any, prevState: any) {
// Updates todo's if state changed
if (prevState.value !== this.state.value) {
this.getAllTodos();
}
}
render(){
return (
<div className="App">
<AddToDo addToDoParent={this.addToDo}/>
<div>
Todo's : {this.state.value}
</div>
<ToDoList items={this.state.items} deleteFromParent={this.deleteItemParent}/>
</div>
);
}
}
ToDoListMock.test.tsx
import React from 'react';
import { shallow , mount, ShallowWrapper} from 'enzyme';
import App from "../App";
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(
[
{
title: "Todo1",
completed: false
},
{
title: "Todo2",
completed: false
}
]
)
})
) as jest.Mock<any>
it("must contain 2 items in state after mock api call", async () => {
const wrapper = shallow(<App />);
await new Promise(res => setTimeout(res));
// await jest.requireActual('promise').resolve()
// Collect items from state
const itemsFromState : any = wrapper.state('items');
// Expect it to have a length of 2
expect(itemsFromState.length).toBe(2);
})
The Error
Expected: 2
Received: 0
EDIT
SetupTests.ts
/* eslint-disable import/no-extraneous-dependencies */
import Enzyme from 'enzyme';
import ReactSixteenAdapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new ReactSixteenAdapter() });
I noticed an error while doing the test.
There are a few things you have to change to make it work:
You forgot to trigger to fetch your notes as your component mounted by putting to componentDidMount:
componentDidMount() {
this.getAllTodos();
}
Like you said, the data response is the list which is not a literal object, so you have to change your mock returning an array instead of literal object with todos property. But to make sure it runs well, we have to move beforeEach:
beforeEach(() => {
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([ // Returns as an array here
{
title: "Todo1",
completed : false
},
{
title: "Todo2",
completed : false
}
])
})
) as jest.Mock<any>
})
Delay your test before getting the state since you have a mock promise need to be done:
it("must contain 2 items in state after mock api call", async () => {
const wrapper = shallow(<App />);
// Delay with either following ways to wait your promise resolved
await new Promise(res => setTimeout(res));
// await jest.requireActual('promise').resolve()
// Collect items from state
const itemsFromState: Array<any> = wrapper.state('items');
// Expect it to have a length of 2
expect(itemsFromState.length).toBe(2);
})
I also created a link for you to compare your code to: https://repl.it/#tmhao2005/Todo

Waiting for async function in React component & Showing Spinner

Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};

Testing async behavior in React with Axios and Jest

Consider the following oversimplified React component. When you click the button, it makes an API call to an external URL.
If it is successful, it increments the counter
If it is unsuccessful, it decrements the counter
import axios from 'axios';
import PropTypes from 'prop-types';
import React from 'react';
class MyCoolButton extends React.Component {
static propTypes = {
initialCounter: PropTypes.number.isRequired
};
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
counter: props.initialCounter
}
}
onClick() {
const url = `/some/url/here`;
const data = { foo: 'bar' };
const config = { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } };
const { counter } = this.state;
return axios.patch(url, data, config)
.then((response) => { /* Success! */ this.setState({ counter: counter + 1 }); })
.catch((error) => { /* Failure :( */ this.setState({ counter: counter - 1 }); });
}
render() {
return (
<div className="container">
<span>Counter value is: {this.state.counter}</span>
<input className="cool-button" type="button" onClick={this.onClick} />
</div>
);
}
}
export default MyCoolButton;
I wanted to write a test case using Jest to ensure that when there is a failure, we are correctly decrementing the button.
I tried the following:
describe('an error occurred while updating', () => {
beforeEach(() => {
axios.patch.mockImplementationOnce(() => Promise.reject('boo'));
});
it('decrements the counter', async() => {
// NOTE: The below uses Enzyme and Chai expectation helpers
wrapper = mount(<MyCoolButton initialCounter={99} />);
// Click the button
wrapper.find(`.cool-button`).first().simulate('click');
// Check for decrmented value
const body = wrapper.find('.container span');
expect(body).to.have.text('Counter value is: 98');
});
});
The problem is that the click and subsequent state update are executed asynchronously, so we check for the failure before it has a chance to even update the component with the failure.
A lot of the examples online seem to suggest async/await which I don't understand that well. It looks like await takes a Promise as an argument, but in my case I'm simulating a click which further calls a handler which returns a Promise, so I can't await on that axios Promise to complete directly.
What's the best practice in testing here?
Thanks!
I think the following will do the trick:
describe('an error occurred while updating', () => {
beforeEach(() => {});
it('decrements the counter', async () => {
const promise = Promise.reject('boo');
axios.patch.mockImplementationOnce(() => promise);
const wrapper = mount(
<MyCoolButton initialCounter={99} />
);
// Click the button
wrapper.find(`.cool-button`).first().simulate('click');
//do catch().then to make sure test executes after
// component caught the rejection.
return promise.catch(x=>x).then(() => {
// Check for decrmented value
const body = wrapper.find('.container span');
expect(body).to.have.text('Counter value is: 98');
});
});
});
Here are some async examples for jest
You need to mount the component and simulate the click event before making the assertion:
describe("an error occurred while updating", () => {
let wrapper;
beforeEach(() => {
axios.patch.mockRejectedValue("Mock error message");
wrapper = mount(<MyCoolButton initialCounter={99} />);
wrapper.find(".cool-button").simulate("click");
});
it("decrements the counter", () => {
expect(wrapper.find(".container span").text()).toEqual(
"Counter value is: 98"
);
});
});

Wait for a specific property of the redux store to update from outside a React component

Normally what I will do inside a React component is to use the connect function to subscribe to the redux store, in that case I have access to the redux store and im aware when it's updating.
I want to wait for the store to update outside from a component , and on a .js file.
Here what I've done so far:
//socket-client.js file
import { store } from "../../Redux/store";
//socket.on() function
socket.on(
"setQuestionsArrayOnTheAccepter",
async ({ questionsArray, roomNameOnTheStore }) => {
await store.dispatch(setQuestionsArray(questionsArray)); // this await on the setQuestionArray has no effect
store.dispatch(setGameProgress());
const { game } = store.getState();
const { gameQuestionArray, gameResultArray } = game;
// gameResultArray = [] will be empty here, and I want the saga to finish before the socket.emit
socket.emit("sendSetQuestionsArrayOnTheSender", {
gameQuestionArray,
gameResultArray,
roomNameOnTheStore
});
}
);
The problem is gameResultArray is empty ([]) by default, and from the setQuestionsArray action im dispatching a redux-saga that is a-synchronic and using Axios (the fetchGameResultStart saga)
// set QuestionsArray action
export const setQuestionsArray = (
questionsArray,
shouldShuffle = true,
multiplayer = false,
gameResultArray = null
) => {
if (shouldShuffle) questionsArray = getRandom(questionsArray, 10);
// using thunk middelware
return dispatch => {
dispatch({ type: GAME_TYPES.SET_QUESTIONS_ARRAY, payload: questionsArray });
if (!multiplayer) {
dispatch(fetchGameResultArrayStart(questionsArray, shouldShuffle));
} else if (gameResultArray) {
dispatch(fetchGameResultArraySuccess(gameResultArray));
}
};
};
So , i want to be able to wait for the fetchGameResultArraySuccess action to be dispatch , and the gameResultArray to be update before running the socket.emit()

How to use enzyme to test component with asyn lifecircle function?

I'm using jest and enzyme to test my component. And in this component, I will request data in componentDidMount, and then it will render view with the data.
class App extends React.Component {
async componentDidMount() {
const data = await getData(); // send a request to server to get data
this.setState({
name: data.name,
})
}
render() {
return (
<div>{this.state.name}</div>
)
}
}
It is a very simple component, just only use async function in componentDidMount, and get data width a async way, not callback. But, I don't know how to test this component.
I'd like write test code like this
it('render', async () => {
const container = mount(
<App />
);
const instance = container.instance();
console.log(instanct.state.name); // of course, state.name is undefined, but I want in this place, the state.name can be 'qyt'
});
I created a demo project on Github here.
The solution I went with was to mock the getData module:
let mockPromise;
jest.mock("./getData", () => {
return jest.fn(() => {
mockPromise = Promise.resolve({ name: "Spider man" });
return mockPromise;
});
});
Then I returned the mockPromise in my test. Jest will wait for the promise to resolve before completing the test.
it("renders without crashing", () => {
const wrapper = shallow(<App />);
return mockPromise.then((a) => {
wrapper.update();
expect(wrapper.html()).toEqual(`<h1>Name: Spider man</h1>`);
});
});

Categories