I use react-idle-timer 4.6.4
I have a component with useIdleTimer hook.
import {useDispatch} from "react-redux";
import {logoutRequest} from "auth/actions";
const App = () => {
const dispatch = useDispatch();
const logout = () => dispatch(logoutRequest);
useIdleTimer({
timeout: 1000 * 60 * 5,
onIdle: logout,
debounce: 500
});
return <Component />
}
Also, I have a test that looks like this
test("render App", () => {
render(<App />);
const someElement = screen.getByText("Some element");
expect(someElement).toBeInTheDocument();
})
This test covers all lines of code except for the logout function.
How to cover the logout function with tests?
I'm using Jest to test a file written in react, I'm trying to mock the hooks but for some reason I still fail. I've tried to follow several suggestions (included stackoverflow answers) but nothing is working. I've tried to simplify the code to find my mistake but it still failing.
// myFileToTest.js
import { useContext, useState } from 'react';
export const returnsUseContext = () => {
return useContext;
};
export const returnsUseState = () => {
return useState;
};
// myFileToTest.test.js
import React from 'react';
import { returnsUseContext, returnsUseState } from './myFileToTest';
describe('test 1', () => {
let realUseContext;
let useContextMock;
beforeEach(() => {
realUseContext = React.useContext;
useContextMock = React.useContext = jest.fn();
});
// Cleanup mock
afterEach(() => {
React.useContext = realUseContext;
});
it('first try', () => {
expect(returnsUseContext()).toBe(useContextMock);
});
});
describe('test 2', () => {
beforeEach(() => {
jest.spyOn(React, 'useState').mockReturnValue({
name: 'hello'
});
});
it('second try', () => {
const useStateMock = jest.spyOn(React, 'useState');
expect(returnsUseState()).toBe(useStateMock);
});
});
and are both failing. Am I making any silly mistakes?
I'm wondering why I need to put fetch mock logic inside my test to make it work.
Here is simple example:
Component to test with fetch inside useEffect and state update after response:
// Test.jsx
import React, {useEffect, useState} from 'react'
export const Test = () => {
const [description, setDescription] = useState<string | null>(null)
const fetchData = async () => {
const response = await fetch('https://dummyendpoint/');
const parsed = await response.json();
const description = parsed.value;
setDescription(description);
}
useEffect(() => {
fetchData();
}, [])
return (
<div data-testid="description">
{description}
</div>
)
};
export default Test;
Test logic:
// Test.test.js
import React from 'react';
import {render, screen} from '#testing-library/react';
import Test from "./Test";
global.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve({
value: "Testing something!"
})
}));
describe("Test", () => {
it('Should have proper description after data fetch', async () => {
// need to put mock logic here to make it work
render(<Test/>);
const description = await screen.findByTestId('description');
expect(description.textContent).toBe("Testing something!");
});
})
If I keep global.fetch mock at the top of my test file, I keep getting an error:
TypeError: Cannot read property 'json' of undefined
at const parsed = await response.json();
It's really strange that it does not work as it is.
But I was able to fix it by moving the setup into beforeEach block (I assume beforeAll would also work).
It is a common pattern to backup global variable value, override it for tests and restore it back.
import React from 'react';
import { render, screen } from '#testing-library/react';
import Test from "./Test";
describe("Test", () => {
let originalFetch;
beforeEach(() => {
originalFetch = global.fetch;
global.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve({
value: "Testing something!"
})
}));
});
afterEach(() => {
global.fetch = originalFetch;
});
it('Should have proper description after data fetch', async () => {
// need to put mock logic here to make it work
render(<Test />);
const description = await screen.findByTestId('description');
expect(description.textContent).toBe("Testing something!");
});
});
I am trying to spy on useState React hook but i always get the test failed
This is my React component:
const Counter= () => {
const[counter, setCounter] = useState(0);
const handleClick=() => {
setCounter(counter + 1);
}
return (
<div>
<h2>{counter}</h2>
<button onClick={handleClick} id="button">increment</button>
</div>
)
}
counter.test.js:
it('increment counter correctlry', () => {
let wrapper = shallow(<Counter/>);
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, 'useState');
useStateSpy.mockImplementation((init) => [init, setState]);
const button = wrapper.find("button")
button.simulate('click');
expect(setState).toHaveBeenCalledWith(1);
})
Unfortunately this doesn't work and i get the test failed with that message:
expected 1
Number of calls: 0
diedu's answer led me the right direction and I came up with this solution:
Mock use state from react to return a jest.fn() as useState:
1.1 Also import useState immediately after - that will now be e jest mock (returned from the jest.fn() call)
jest.mock('react', ()=>({
...jest.requireActual('react'),
useState: jest.fn()
}))
import { useState } from 'react';
Later on in the beforeEach, set it to the original useState, for all the cases where you need it to not be mocked
describe("Test", ()=>{
beforeEach(()=>{
useState.mockImplementation(jest.requireActual('react').useState);
//other preperations
})
//tests
})
In the test itself mock it as needed:
it("Actual test", ()=>{
useState.mockImplementation(()=>["someMockedValue", someMockOrSpySetter])
})
Parting notes: While it might be conceptually somewhat wrong to get your hands dirty inside the "black box" one is unit testing, it is indeed super useful at times to do it.
You need to use React.useState instead of the single import useState.
I think is about how the code gets transpiled, as you can see in the babel repl the useState from the single import ends up being different from the one of the module import
_react.useState // useState
_react.default.useState // React.useState;
So you spy on _react.default.useState but your component uses _react.useState.
It seems impossible to spyOn a single import since you need the function to belong to an object, here is a very extensive guide that explains the ways of mocking/spying modules https://github.com/HugoDF/mock-spy-module-import
And as #Alex Mackay mentioned, you probably want to change your mindset about testing react components, moving to react-testing-library is recommended, but if you really need to stick to enzyme you don't need to go that far as to mock react library itself
Annoyingly Codesandbox is currently having trouble with its testing module so I can't post a working example but I will try to explain why mocking useState is generally a bad thing to do.
The user doesn't care if useState has been called, they care about when I click increment the count should increase by one therefore that is what you should be testing for.
// App
import React, { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
</div>
);
}
// Tests
import React from "react";
import App from "./App";
import { screen, render } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
describe("App should", () => {
it('increment count value when "Increment" btn clicked', () => {
// Render the App
render(<App />);
// Get the count in the same way the user would, by looking for 'Count'
let count = screen.getByText(/count:/);
// As long as the h1 element contains a '0' this test will pass
expect(count).toContain(0);
// Once again get the button in the same the user would, by the 'Increment'
const button = screen.getByText(/increment/);
// Simulate the click event
userEvent.click(button);
// Refetch the count
count = screen.getByText(/count:/);
// The 'Count' should no longer contain a '0'
expect(count).not.toContain(0);
// The 'Count' should contain a '1'
expect(count).toContain(1);
});
// And so on...
it('reset count value when "Reset" btn is clicked', () => {});
it('decrement count value when "Decrement" btn is clicked', () => {});
});
Definitely check out #testing-library if you are interested in this style of testing. I switched from enzyme about 2 years ago and haven't touched it since.
just you need to import React in your test file like:
import * as React from 'react';
after that you can use the mock function.
import * as React from 'react';
:
:
it('increment counter correctlry', () => {
let wrapper = shallow(<Counter/>);
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, 'useState');
useStateSpy.mockImplementation((init) => [init, setState]);
const button = wrapper.find("button")
button.simulate('click');
expect(setState).toHaveBeenCalledWith(1);
})
you should use React.useState() instead useState(), But there are other ways...
in React you can set useState without React with this config
// setupTests.js
const { configure } = require('enzyme')
const Adapter = require('#wojtekmaj/enzyme-adapter-react-17')
const { createSerializer } = require('enzyme-to-json')
configure({ adapter: new Adapter() });
expect.addSnapshotSerializer(createSerializer({
ignoreDefaultProps: true,
mode: 'deep',
noKey: true,
}));
import React, { useState } from "react";
const Home = () => {
const [count, setCount] = useState(0);
return (
<section>
<h3>{count}</h3>
<span>
<button id="count-up" type="button" onClick={() => setCount(count + 1)}>Count Up</button>
<button id="count-down" type="button" onClick={() => setCount(count - 1)}>Count Down</button>
<button id="zero-count" type="button" onClick={() => setCount(0)}>Zero</button>
</span>
</section>
);
}
export default Home;
// index.test.js
import { mount } from 'enzyme';
import Home from '../';
import React, { useState as useStateMock } from 'react';
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
describe('<Home />', () => {
let wrapper;
const setState = jest.fn();
beforeEach(() => {
useStateMock.mockImplementation(init => [init, setState]);
wrapper = mount(<Home />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Count Up', () => {
it('calls setCount with count + 1', () => {
wrapper.find('#count-up').simulate('click');
expect(setState).toHaveBeenCalledWith(1);
});
});
describe('Count Down', () => {
it('calls setCount with count - 1', () => {
wrapper.find('#count-down').props().onClick();
expect(setState).toHaveBeenCalledWith(-1);
});
});
describe('Zero', () => {
it('calls setCount with 0', () => {
wrapper.find('#zero-count').props().onClick();
expect(setState).toHaveBeenCalledWith(0);
});
});
});
I am trying to test below function or in other words I am trying to write unit test cases of below function.But I am getting error _axios.default.get.mockResolvedValueOnce is not a function
import React from "react";
import axios from "axios";
export default () => {
const [state, setState] = React.useState([]);
const fetchData = async () => {
const res = await axios.get("https://5os4e.csb.app/data.json");
setState(res.data);
};
React.useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
return [state];
};
here is my code
https://codesandbox.io/s/awesome-jepsen-5os4e?file=/src/usetabData.test.js
I write unit test case like that
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import mockAxios from "axios";
describe("use tab data", () => {
afterEach(cleanup);
it("fetch tab data", async () => {
mockAxios.get.mockResolvedValueOnce({
data: {
name: "hello"
}
});
await act(async () => renderHook(() => useTabData()));
expect(mockAxios.get).toHaveBeenCalled();
});
});
Code sandbox doesn't support manual mocks as far as I know.
However, your __mock__ is placed in wrong directory structure. It should be a sibling of node_module.
Having said that, easiest way is to use https://github.com/ctimmerm/axios-mock-adapter
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import axios from "axios";
import MockAxiosAdapter from "axios-mock-adapter";
const mockAxios = new MockAxiosAdapter(axios);
afterEach(() => {
cleanup();// this is not needed . its done by testing library after each.
mockAxios.reset();
});
describe("use tab data", () => {
it("fetch tab data", async () => {
mockAxios.onGet(200, { data: { test: "123" } }); // response code and object.
const { result, waitForNextUpdate } = renderHook(() => useTabData());
const [value] = result.current;
// assert value
// assert the axios call by using history object
});
});
You can use history to assert: https://github.com/ctimmerm/axios-mock-adapter#history