Jest mock factory not working for class mock - reactjs

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.

Related

Failed to run test case: jest.mock is not a function

I am writing my first test case but facing following error:
jest.doMock is not a function
May be I am not doing hook mock in right way.
I am sharing my code, please go through this and let me know if you guys found anything wrong with code.
useCustomHook.js
export const useCustomHook = () => {
return {
val: "abcd"
};
}
App.js
import "./styles.css";
import { useCustomHook } from "./useCustomHook";
export default function App() {
const { val } = useCustomHook();
return (
<div className="App" data-testid="hi">
{val}
</div>
);
}
demoTest.spec.js
import { render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import App from "../App";
jest.doMock("../useCustomHook", () => {
return {
useCustomHook: () => {
return { val: "mg" };
}
};
});
describe("demo test describe", () => {
it("demo it", async () => {
const { getByTestId } = render(<App />);
const n = await getByTestId("hi");
expect(n).toHaveTextContent("mg");
});
});

Mocking an API function gives error while testing the code

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 ?

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.

Promise doesn't work with jest in REACTJS

I keep having a TypeError: Network request failed when I try to test a Snapshot of a component
here is the component
import {GetAllUsersPost} from './postdata';
class ManageUsers extends React.Component{
render(){
return(
{...}
);
}
componentDidMount(){
GetAllUsersPost(UserProfile.getId()).then((result) => {
this.setState({
parsed:result,
loading:false
})
});
}
}
Here is postdata
export function GetAllUsersPost(id) {
const json = JSON.stringify({id: id})
return new Promise((resolve, reject) => {
fetch(BaseURL + 'allusers', BdRequest(json)).then((response) => response.json()).then((res) => {
resolve(res);
}).catch((error) => {
reject(error);
});
});
}
And here is the test file (\src__tests__\ManageUsers.test.jsx)
import React from 'react';
import ManageUsers from '../component/ManageUsers';
import Adapter from 'enzyme-adapter-react-16';
import Enzyme, {shallow,mount} from 'enzyme';
Enzyme.configure({adapter:new Adapter()});
describe("ManageUsers", ()=>{
const wrapper = shallow(<ManageUsers/>);
const instance = wrapper.instance();
let response;
test("loading()",()=>{
wrapper.setState({loading:false})
response = JSON.stringify("")
expect(JSON.stringify(instance.loading())).toBe(response);
})
});
I know that my error is because of the promise (when Enzyme tries to shallow the component) but I can't make it to work...
thanks
Your test has to be set up as an async test. e.g.
it('should do something', async () => {
const result = await myAsyncMethod();
});
Edited for clarity - note that this is clearly untested, but what you need to look for is something from the render method and state, since that's all you do with the results.
import React from 'react';
import ManageUsers from '../component/ManageUsers';
import Adapter from 'enzyme-adapter-react-16';
import Enzyme, {shallow,mount} from 'enzyme';
Enzyme.configure({adapter:new Adapter()});
describe("ManageUsers", () => {
const wrapper = shallow(<ManageUsers/>);
test("loading()", async () => {
// wrapper.setState({loading:false}) // This should be a default
expect(wrapper.find('something from the render'));
expect(wrapper.state.parsedResults).toEqual('some result')
});
});

Test asynchronous nested component

Say I have the following wrapper component:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {update} from '../../actions/actions'
import LoadFromServerButton from '../LoadFromServerButton'
import {connect} from 'react-redux'
export class FooDisplay extends PureComponent {
render () {
return (
<p>
<span className='foo'>
{this.props.foo}
</span>
<LoadFromServerButton updateFunc={this.props.update} />
</p>
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
FooDisplay.propTypes = {
foo: PropTypes.string
}
export const mapDispatchToProps = (dispatch) => {
return {
update: (foo) => dispatch(update(foo))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FooDisplay)
and the following inner component:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {get} from '../../actions/actions'
import ActiveButton from '../ActiveButton'
import {connect} from 'react-redux'
export class LoadFromServerButton extends PureComponent {
doUpdate () {
return this.props.get().then(this.props.updateFunc)
}
render () {
return (
<ActiveButton action={this.doUpdate.bind(this)} actionArguments={[this.props.foo]} text='fetch serverside address' />
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
export const mapDispatchToProps = (dispatch) => {
return {
get: () => dispatch(get())
}
}
LoadAddressFromServerButton.propTypes = {
updateFunc: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(LoadFromServerButton)
ActiveButton is a very thin wrapper around a button with an onclick and arguments destructuring.
Now lets say that I my get action is written as follows:
export const get = () => dispatch => http('/dummy_route')
.spread((response, body) => dispatch(actOnThing(update, body)))
Now if I write a test like so:
/* global window, test, expect, beforeAll, afterAll, describe */
'use strict'
import React from 'react'
import FooDisplay from './index'
import {mount} from 'enzyme'
import {Provider} from 'react-redux'
import configureStore from '../../store/configureStore'
import nock, {uriString} from '../../config/nock'
import _ from 'lodash'
const env = _.cloneDeep(process.env)
describe('the component behaves correctly when integrating with store and reducers/http', () => {
beforeAll(() => {
nock.disableNetConnect()
process.env.API_URL = uriString
})
afterAll(() => {
process.env = _.cloneDeep(env)
nock.enableNetConnect()
nock.cleanAll()
})
test('when deep rendering, the load event populates the input correctly', () => {
const store = configureStore({
address: {
address: 'foo'
}
})
const display = mount(<Provider store={store}><FooDisplay /></Provider>,
{attachTo: document.getElementById('root')})
expect(display.find('p').find('.address').text()).toEqual('foo')
const button = display.find('LoadFromServerButton')
expect(button.text()).toEqual('fetch serverside address')
nock.get('/dummy_address').reply(200, {address: 'new address'})
button.simulate('click')
})
})
This results in:
Unhandled rejection Error: Error: connect ECONNREFUSED 127.0.0.1:8080
After a little bit of thinking, this is due to the fact that the test does not return a promise, as the button click causes the promise to fire under the hood, therefore, afterAll runs immediatly, cleans nock, and a real http connection goes over the wire.
How do I test this case? I don't seem to have an easy way to return the correct promise... How do I test updates to the DOM resulting from these updates?
In order to mock only one method of the imported module, use .requireActual(...)
jest.mock('../your_module', () => ({
...(jest.requireActual('../your_module')),
YourMethodName: () => { return { type: 'MOCKED_ACTION'}; }
}));
As you mentioned the problem is that you dont have the promise to return from test. So to make get return a know promise you can just mock get directly without using nock:
import {get} from '../../actions/actions'
jest.mock('../../actions/actions', () => ({get: jest.fn}))
this will replace the action module with an object {get: jestSpy}
in your test you can then create a promise and let get return this and also return this promise from your test:
it('', ()=>{
const p = new Promise.resolve('success')
get.mockImplementation(() => p)//let get return the resolved promise
//test you suff
return p
})

Resources