Mocking an API function gives error while testing the code - reactjs

Below is my App.js code for your reference
import React from "react";
import "./App.css";
import axios from "axios";
function App() {
const fetchTheComments = async () => {
let commentsFetched = await axios.get(
`https://jsonplaceholder.typicode.com/comments/1`
);
return commentsFetched;
};
return (
<div className="App">
<h1>Testing Jest-Enzyme</h1>
<button
id="fetch-comments"
onClick={() => {
fetchTheComments();
}}
>
Fetch
</button>
<p>
{JSON.stringify(fetchTheComments())
? JSON.stringify(fetchTheComments())
: ""}
</p>
</div>
);
}
export default App;
Below is my App.test.js code for your reference
import App from "./App";
import { mount } from "enzyme";
import mockAxiosApi from "../src/__mocks__/mockAxiosApi";
describe("Before testing", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(<App />);
});
test("render the correct title", () => {
expect(wrapper.find("h1").text()).toBe("Testing Jest-Enzyme");
});
test("button click", () => {
wrapper.find("#fetch-comments").simulate("click");
expect(wrapper.find("comments")).not.toBe("");
});
test("should fetch comments", async () => {
wrapper.find("#fetch-comments").simulate("click");
mockAxiosApi.get.mockImplementationOnce(() =>
Promise.resolve({
data: {},
})
);
console.log(wrapper.debug());
let response = await wrapper.instance().fetchTheComments();
console.log(response);
});
});
I am not sure why i am getting the error, i have one lambda function inside the component which i am testing but whenever i run a test getting an error stating fetchTheComments function is null. I have pasted my App.js and App.test.js here for your reference. Can someone help me in this issue ?

Related

Jest mock variables of imported component

I am trying to mock a variable (auth) inside my App component as it is doing conditional rendering. How should I do it without trying to export the variable itself? Been trying for a few days with various solutions but I can't seem to cover it, and now I am stuck.
App.js
import React from "react";
import { useRoutes } from "react-router-dom";
import Routing from "./routes";
import useAuth from "./hooks/useAuth";
import SplashScreen from "./components/splashScreen/SplashScreen";
const App = () => {
const content = useRoutes(Routing());
const auth = useAuth();
return (
<>
{auth.isInitialized ? content : <SplashScreen />}
</>
);
};
export default App;
App.test.js
import React from "react";
import { mount } from "enzyme";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
describe("App Unit Tests", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<BrowserRouter>
<App />
</BrowserRouter>
);
});
it("App should render", () => {
expect(wrapper.length).toEqual(1);
});
//Below fails
it("should render splashscreen", () => {
jest.mock("./hooks/useAuth", () => ({
isInitialized: false,
}));
expect(wrapper.length).toEqual(1);
});
it("should render content", () => {
jest.mock("./hooks/useAuth", () => ({
isInitialized: true,
}));
expect(wrapper.length).toEqual(1);
});
});
You could do something like this:
jest.mock('./hooks/use-auth', () => ({
isInitialized: true
});
This basically means that use-auth returns an object which has a inInitialized property
Instead of auth, the useAuth hook should be mocked into an object (say mockUseAuth) that has the isInitialized getter. The getter should return a mockIsInitialized value, that can be changed on per test case basis. Something like this :
let mockIsInitialized = true;
let mockUseAuth = {
isAuthenticated: true
};
Object.defineProperty(mockUseAuth, 'isInitialized', {
get: jest.fn(() => mockIsInitialized)
});
jest.mock('./hooks/use-auth', () => {
return jest.fn(() => (mockUseAuth))
})
describe("App Unit Tests", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<BrowserRouter>
<App />
</BrowserRouter>
);
});
it("App should render", () => {
expect(wrapper.length).toEqual(1);
});
it("should render splashscreen", () => {
mockIsInitialized = false;
expect(wrapper.length).toEqual(1);
});
it("should render content", () => {
mockIsInitialized = true;
expect(wrapper.length).toEqual(1);
});
});

React-Jest : axios.get.mockRejectedValue doesnt respect async-await

I am using jest and enzyme for Unit Testing of a React App created using create-react-app. I have an axios call and I call other functions like myfunction.onSuccess() and myfunction.onError() from another component on success and error of the axios call respectively. I have put an assert to expect(myfunction.onSuccess).toHaveBeenCalledTimes(1); This works fine for mockResolvedValue but not for mockRejectedValue. I see that async-await is not respected for mockRejectedValue.
I have created a sample code and used window.alert as the function to be called in both then and catch block to keep it simple here and expecting this function to be called once as an assert. Why is there a difference and how to tackle this?
Async.js
import React from 'react';
import axios from 'axios';
class Async extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
getAllPosts() {
axios.get(`https://jsonplaceholder.typicode.com/posts`)
.then(response => {
if (response.data) {
this.setState({ posts: response.data });
window.alert("Data fetch Success");
}
}).catch(error => {
window.alert(error.message);
});
}
render() {
return (
<div>
<button onClick={() => { this.getAllPosts() }}>Fetch Posts</button>
<ul>
{this.state.posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
}
export default Async;
Async.test.js
import React from 'react';
import Async from './Async';
import { shallow } from 'enzyme';
import axios from "axios";
jest.mock("axios");
describe('Async Component', () => {
const wrapper = shallow(<Async />);
const layout = wrapper.instance();
it('to notify success when posts', async () => {
window.alert = jest.fn();
const response = { data: [{ id: '1', title: 'something' }, { id: '2', title: 'something else' }] };
axios.get.mockResolvedValue(response);
await layout.getAllPosts();
expect(layout.state.posts).not.toBe([])
expect(window.alert).toHaveBeenCalledTimes(1);
});
it('to notify failure when error is encountered', async () => {
window.alert = jest.fn();
const error = { message: '500 Internal Server Error' };
axios.get.mockRejectedValue(error);
await layout.getAllPosts();
expect(window.alert).toHaveBeenCalledTimes(1); // why doesn't this work?
});
});

Jest mock factory not working for class mock

I'm trying to mock an service class to test an React component. But the module factory from jest.mock is not working.
Search component:
import React, { useState } from "react";
import SearchService from "../../services/SearchService";
export default function Search() {
const [searchResults, setSearchResults] = useState([]);
function doSearch() {
const service = new SearchService();
service.search().then(setSearchResults);
}
return (
<div className="component-container">
<div>
<button onClick={doSearch}>search</button>
</div>
{searchResults.map((result) => (
<div key={result}>{result}</div>
))}
</div>
);
}
SearchService:
export default class SearchService {
search = function () {
return new Promise((resolve) => {
setTimeout(
() => resolve(["result 1", "result 2", "result 3", "result 4"]),
1000
);
});
};
}
Test file:
import React from "react";
import { screen, render } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import { act } from "react-dom/test-utils";
import Search from "../features/search/Search";
jest.mock("../services/SearchService", () => {
return jest.fn().mockImplementation(() => {
return { search: jest.fn().mockResolvedValue(["mock result"]) };
});
});
test("Search", async () => {
render(<Search />);
const button = screen.getByRole("button");
expect(button).toBeDefined();
act(() => {
userEvent.click(button);
});
await screen.findByText("mock result");
});
This is the same structure as the Jest documentation example. In the code above I'm passing the mock implementation through the module factory parameter of the jest.mock.
But it does not work. When I log the new SerchService() I get "mockConstructor {}" and when I run the test it throws the error "service.search is not a function".
When I change my test file to...
import React from "react";
import { screen, render } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import { act } from "react-dom/test-utils";
import Search from "../features/search/Search";
import SearchService from "../services/SearchService";
jest.mock("../services/SearchService");
test("Search", async () => {
SearchService.mockImplementation(() => {
return { search: jest.fn().mockResolvedValue(["mock result"]) };
});
render(<Search />);
const button = screen.getByRole("button");
expect(button).toBeDefined();
act(() => {
userEvent.click(button);
});
await screen.findByText("mock result");
});
It works...
I kinda can understand why it works in the second way, it is like using jest.spyOn I guess. What I cant understand is why it doesnt work with the first approach.
What I'm doing wrong? How can I mock a module implementation with jest.mock without calling .mockImplementation inside each test?
I found that there is a problem with the documentation and that the factory needs to return an function() (not an arrow function), so I changed the mock to the following and it works:
jest.mock("../services/SearchService.js", () => {
return function () {
return { search: jest.fn().mockResolvedValue(["mock result"]) };
};
});
Found on this post.

jest mock axios doesn't provide proper mock for axios

I'm trying to provide a mock request for this class and then expect that history.push is called with some path.
Start.js
import React from 'react'
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { ReactComponent as Arrow } from '../../arrow.svg';
export default function Start() {
let history = useHistory();
const doInitializeApp = () => {
axios.get('http://localhost:8080/api/v1/asap/start')
.then(res => {
if (res.data == true) {
history.push('/login')
} else {
alert('something went wrong. Could not start the application')
}
}).catch(err => {
alert('something went wrong. Could not contact the server!')
});
}
return (
<div>
<div className="container">
<div className="content">
<div id="box">
<h1>Welcome</h1>
<Arrow id="next" onClick={doInitializeApp} />
</div>
</div>
</div>
</div>
);
}
And this is my approach for the test
Start.test.js
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Start from '../components/startscreen/Start';
import { ReactComponent as Arrow } from '../arrow.svg';
import axios from "axios";
Enzyme.configure({ adapter: new Adapter() });
describe('Start', () => {
it('test axios get reroute the application to path /login', () => {
const mProps = { history: { push: jest.fn() } };
const wrapper = shallow(<Start {...mProps} />);
const arrow = wrapper.find(Arrow);
const axiosSpy = jest.spyOn(axios, 'get');
//mock axios
jest.mock("axios");
//mock axios response
axios.get.mockResolvedValue({ data: true });
//simulate onclick
arrow.simulate('click');
expect(axiosSpy).toHaveBeenCalled(); --> this pass
expect(mProps.history.push).toBeCalledWith('/login'); --> this doesn't pass
})
});
However, the test did not pass because the actual axios.get(url) doesn't take the response which I mocked and it always come to the .catch(err => ... "Could not contact the server!")
What did I do wrong in here ? Because that the code didn't come to the if (res.data===true) so that I also couldn't test whether the history.push is actually called or not.
Your mocking code is fine. The code in the catch block is getting executed since useHistory() returns undefined (You can confirm this by console.logging the error inside the catch block).
One way to fix it would be to mock useHistory and pass a mock function for history.push. You can then spy on useHistory() to confirm the history.push got called with /login.
import { useHistory } from 'react-router-dom'
// other import statements omitted for brevity
jest.mock('axios')
jest.mock('react-router-dom', () => {
const fakeHistory = {
push: jest.fn()
}
return {
...jest.requireActual('react-router-dom'),
useHistory: () => fakeHistory
}
})
const flushPromises = () => new Promise(setImmediate)
describe('Start component', () => {
test('redirects to /login', async () => {
const pushSpy = jest.spyOn(useHistory(), 'push')
axios.get.mockResolvedValue({ data: true })
const wrapper = shallow(<App />)
const button = wrapper.find(Arrow)
button.simulate('click')
await flushPromises()
expect(pushSpy).toBeCalledWith('/login')
})
})
I'm using setImmediate to wait for the async action to complete as suggested here.

Jest/Enzyme Unit Test on Axios Requests in ComponentDidMount()

I am trying to perform some unit testing on my existing react application with Jest and Enzyme. I am totally new to this stuff and accurately I do not know how to approach such test scenarios. I know that to test API request calls I have to perform some "mocking", but how should I write the test for that?. What will be the steps that needs to be followed?
Following is the code snippet I am looking to test.
Home.js
import React,{Component} from 'react'
import axios from 'axios';
import {Link} from 'react-router-dom';
import FacilityModal from '../Table/FacilityModal';
class Home extends Component {
state = {
cities:[],
name:''
}
componentDidMount() {
axios.get('/cities').then(res => {
this.setState({cities:res.data})
console.log("Oza" + JSON.stringify(res))
});
console.log(this.state.cities)
}
render() {
let postList = this.state.cities.map(city => {
return(
<div key = {city.id}>
<p>
<Link to = {'/'+city.id}>{city.name}</Link>
</p>
</div>
)
})
return(
<div className = 'align'>All Facilities (NCAL)
<div className="hr-sect">OR</div>
<div className = 'Modal'>
{postList}
</div>
<FacilityModal cityname = {this.props} />
</div>
)
}
}
import React from 'react';
import axios from 'axios';
export default class ArticleList extends React.Component {
constructor(props) {
super(props);
this.state = {
articles: []
}
}
componentDidMount() {
return axios.get('GET_ARTICLES_URL').then(response => {
this.setState({
articles: response.data
});
});
}
render() {
return (
<ul>
{this.state.articles.map(a => <li><a href={a.url}>{a.title}</a></li>)}
</ul>
)
}
}
// ---------
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
jest.mock('axios', () => {
const exampleArticles = [
{ title: 'test article', url: 'test url' }
];
return {
get: jest.fn(() => Promise.resolve(exampleArticles)),
};
});
const axios = require('axios');
it('fetch articles on #componentDidMount', () => {
const app = shallow(<App />);
app
.instance()
.componentDidMount()
.then(() => {
expect(axios.get).toHaveBeenCalled();
expect(axios.get).toHaveBeenCalledWith('articles_url');
expect(app.state()).toHaveProperty('articles', [
{ title: 'test article', url: 'test url' }
]);
done();
});
});
1) Extract the API call in another method that returns the promise(for eg: fetchCities()) for ease of testing.
2) Mock the axios node module with Jest. Refer docs: https://jestjs.io/docs/en/mock-functions#mocking-modules
3) Use Enzyme to get a reference to your component: https://airbnb.io/enzyme/docs/api/ShallowWrapper/shallow.html
Once that's in place, you can verify if the state is set correctly. For eg:
test('should fetch users', () => {
const wrapper = shallow(<Home/>);
const resp = {data: [{cities: ['NY']}]};
axios.get.mockResolvedValue(resp);
wrapper.instance().fetchCities().then(resp => {
expect(wrapper.state('cities')).toEqual(resp.data.cities);
});
});
How do i improve this answer? It is not what i am expecting as response which is name of the cities.
axios.js (seperate function for promise)
'use strict';
module.exports = {
get: () => {
return Promise.resolve({
data: [
{
id: 0,
name: 'Santa Clara'
},
{
id: 1,
name: 'Fremont'
}
]
});
}
};
Home.test.js (actual test file)
import React from 'react';
import { shallow,configure } from 'enzyme';
import Home from './Home';
import axios from 'axios';
import Adapter from 'enzyme-adapter-react-16';
configure({adapter:new Adapter()});
jest.mock('axios');
describe('Home component', () => {
describe('when rendered', () => {
it('should fetch a list of cities', () => {
const getSpy = jest.spyOn(axios, 'get');
const cityInstance = shallow(
<Home/>
);
expect(getSpy).toBeCalled();
});
});
});

Resources