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>`);
});
});
Related
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
I have this code in my component, that takes listUsers, as a required PropType func. I want to test that in the componentDidMount(), it should be called only once.
Component Code:
static propTypes = {
securityMode: PropTypes.string.isRequired,
listUsers: PropTypes.func.isRequired
};
componentDidMount() {
const { listUsers } = this.props;
listUsers();
}
onCreateUserSucess= response => {
this.setState({ isCreateUserModalOpen: false });
const { listUsers, notify } = this.props;
listUsers();
this.closeCreateUserModal();
notify({
title: 'Created User',
message: `User ${response.username} was added to group: ${response.group}`,
status: STATUS.success,
dismissible: true,
dismissAfter: 3000
});
};
I get an error, saying that spyOn can only be appliead to functions. Can someone help me to test the. componentDidMount and onCreateUserSucess. I even tried to mock the functions but I always get failings.
Testing componentDidMount is pretty simple. Let's suppose that the component you created above is called UsersDisplayer.
class UsersDisplayer extends Component {
constructor(props) {
// ...
}
// The code above goes here.
}
In order to test whether the listUsers function runs or not, you should do something similar to this:
// Import React, shallow and UsersDisplayer at the top.
describe('<UsersDisplayer />', () => {
let usersDisplayerWrapper;
let listUsers;
beforeEach(() => {
listUsers = jest.fn();
usersDisplayerWrapper = <UsersDisplayer listUsers={listUsers} />;
});
describe('componentDidMount', () => {
it('calls listUsers props function', () => {
// The `componentDidMount` lifecycle method is called during the rendering of the component within the `beforeEach` block, that runs before each test suites.
expect(listUsers).toHaveBeenCalled();
});
});
describe('onCreateUserSucess', () => {
it('calls listUsers props function', () => {
// Create a dummy `response` data that will be passed to the function.
const response = {
username: 'username',
group: 'group'
};
// Simply just call the function of the instance.
usersDisplayerWrapper.instance().onCreateUserSucess(response);
expect(listUsers).toHaveBeenCalled();
});
});
});
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
});
I'm using React in my application. I'm making an API call in my componentDidMount but it is conditional. My code in component is
componentDidMount() {
if (!this.props.fetch) {
fetchAPICall()
.then(() => {
/** Do something **/
});
}
}
I've written test as :
it('should not fetch ', () => {
const TFCRender = mount(<Component fetch />);
const didMountSpy = jest.spyOn(TFCRender.prototype, 'componentDidMount');
expect(didMountSpy).toHaveBeenCalledTimes(1);
expect(fetchAPICall).toHaveBeenCalledTimes(0);
});
The test is throwing me error as
TypeError: Cannot read property 'componentDidMount' of undefined
What am I doing wrong and what is the right way to test such case.
From the official docs, you need to spy the component before mounting it.
Following is a working example that I have created with create-react-app. I've also added some comments in the example code:
App.js
import { fetchAPICall } from './api';
class App extends Component {
componentDidMount() {
if (!this.props.fetch) {
fetchAPICall().then(console.log);
}
}
render() {
return <div>Testing the result</div>;
}
}
export default App;
api.js
export const fetchAPICall = () => {
return Promise.resolve('Getting some data from the API endpoint');
};
App.test.js
import Component from './App';
import * as apis from './api'; // assuming you have a separate file for these APIs
// Mock the fetchAPICall, and since the data fetching is asynchronous
// you have to mock its implementation with Promise.resolve()`
apis.fetchAPICall = jest.fn(() => Promise.resolve('test'));
describe('spyOn', () => {
let didMountSpy; // Reusing the spy, and clear it with mockClear()
afterEach(() => {
didMountSpy.mockClear();
});
didMountSpy = jest.spyOn(Component.prototype, 'componentDidMount');
test('should not fetch ', () => {
// Ensure the componentDidMount haven't called yet.
expect(didMountSpy).toHaveBeenCalledTimes(0);
const TFCRender = mount(<Component fetch />);
expect(didMountSpy).toHaveBeenCalledTimes(1);
expect(apis.fetchAPICall).toHaveBeenCalledTimes(0);
});
test('should fetch', () => {
expect(didMountSpy).toHaveBeenCalledTimes(0);
const TFCRender = mount(<Component fetch={false} />);
expect(didMountSpy).toHaveBeenCalledTimes(1);
expect(apis.fetchAPICall).toHaveBeenCalledTimes(1);
});
});
Not sure if this is the best practice, but this is how I usually write my own tests.
Hope this help!
I do have a SplashContainer with a async componentDidMount
export class SplashContainer extends Component {
async componentDidMount() {
let token = await AsyncStorage.getItem('#XXX:token')
if (token !== null) {
await this.props.setTokenAvalability(true)
await this.props.getUserDetails()
}
await this.props.navToNextScreen()
}
render() {
return <Splash />
}
}
function mapDispatchToProps(dispatch) {
return {
navToNextScreen: () => dispatch(navToNextScreen(...)),
setTokenAvalability: (status) => dispatch(setTokenAvalability(status)),
getUserDetails: () => dispatch(getUserDetails()),
}
}
export default connect(null, mapDispatchToProps)(SplashContainer);
I do have two questions here.
1. I wanted to test setTokenAvalability and getUserDetails is been dispatched or not. I do know how to test if there is no async/await, like below.
it('test SplashContainer', () => {
const store = mockStore({});
const props = {
dispatch: store.dispatch
}
const tree = renderer.create(
<SplashContainer {...props}/>
).toJSON();
const expectedAction = [
...
]
expect(store.getActions()).toEqual(expectedAction);
});
2. How to stub value for AsyncStorage.getItem()
Thanks,
Well, componentDidMount is not an async function, the only thing you can do here I think is to use the componentDidUpdate method which will give you an update of your component.
Reading the this.props.navToNextScreen() function, I think you misunderstand how redux works here.
You probably don't need to wait for the navToNextScreen function, it should just send a global event and your main component should listen to a change in your store to show / hide your Splash screen