i want to check condition based on the boolean value
export const Button:React.FunctionComponent<ButtonProps>
const [loading, setLoading] = React.useState(false)
return(
<container
color={props.color},
border={5}
>
{loading ? <itemlist /> : props.title}
/>
)
I want to test if loading is true then render "<itemList/"> if not just show some title
I want to add some test cases for this
I tried this but i guess this seems to be wrong approach
test('check if loading is true', () => {
const isloadedProp = {
loading: false
}
const output = mount(<Button {...ButtonProps} />)
expect(output.find(container).find(itemlist)).toBeFalsy()
expect(output.find(container).title).toBe('ABC')
any suggestion or answers
You can try jest.spyOn() to gain control of React.useState call.
const useStateSpy = jest.spyOn(React, 'useState');
const setLoadingMock = jest.fn();
let isLoadingState = true;
useStateSpy.mockImplementation(() => [isLoadingState, setLoadingMock]);
test('check if loading is true', () => {
isLoadingState = true;
// [loading, setLoading] = React.useState() will result in
// loading = true, setLoading = setLoadingMock
// your test code
});
test('check if loading is false', () => {
isLoadingState = false;
// [loading, setLoading] = React.useState() will result in
// loading = false, setLoading = setLoading Mock
// your test code
});
UPDATED: Rewrite to using just props and not state
As discussed, here is a solution to Button component using isLoading prop instead of useState.
Button.js
import React from 'react';
import Container from '....'; // insert path to Container component here
import ItemList from '....'; // insert path to ItemList component here
const Button = props => {
return (
<Container
color={props.color}
border={5}
>
{props.isLoading ? <Itemlist /> : props.title}
</Container>
);
};
export default Button;
Please mind that component names should always start with upper-case letter for React to distinguish between HTML tag and components.
Button.test.js
import React from 'react';
import { mount } from 'enzyme';
import Button from './Button';
const ButtonProps = {
color: 'red',
title: 'ABC',
};
describe('when isLoading === true', () => {
let output;
beforeAll(() => {
output = mount(<Button {...ButtonProps} isLoading />);
});
test('ItemList is rendered', () => {
expect(output.find('Itemlist')).toHaveLength(1);
});
test('title is not rendered', () => {
expect(output.find('Container').text()).not.toContain('ABC');
});
});
describe('when isLoading === false', () => {
let output;
beforeAll(() => {
output = mount(<Button {...ButtonProps} isLoading={false} />);
});
test('ItemList is not rendered', () => {
expect(output.find('Itemlist')).toHaveLength(0);
});
test('title is rendered', () => {
expect(output.find('Container').text()).toContain('ABC');
});
});
Related
I have a component that renders either a sign in comp or an iframe based on a variable that is set to true or not. The variable will be true if the endpoint(using useParams hook) state is "signIn".
I'm having trouble trying to set or test this in Jest. I've tried setting a variable in jest const isSignIn = true and using props but it's still just showing the iframe comp.
The JSX
const myComp = () => {
const { target } = useParams();
const navigate = useNavigate();
const [endpoint, setEndpoint] = useState(null);
const isSignIn = endpoint === "signIn";
useEffect(() => {
if (Object.values(targetEndpoints).includes(`${BaseURL}/${target}`)) {
setEndpoint(`${BaseURL}/${target}`);
} else {
navigate(`${SignInURL}`);
}
}, [target, navigate]);
console.log({ isSignIn });
return (
<section className="container">
{isSignIn ? (
<SignIn />
) : (
<>
<div className="child__container">
<iframe
className="iframe"
src={`${iframe_URL}/${iframeEndpoints[endpoint]}`}
title="my_iframe"
></iframe>
</div>
</>
)}
</section>
);
};
The Test
import React from "react";
import * as ReactRouterDom from "react-router-dom";
import MyComp from ".";
import SignIn from "#Src/routes/SignIn/SignIn";
import { mount } from "enzyme";
describe("<MyComp/>", () => {
const mockSetState = jest.fn();
jest.mock("react", () => ({
useState: (initial) => [initial, mockSetState],
}));
const comp = mount(<MyComp />);
beforeEach(() => {
jest.spyOn(ReactRouterDom, "useParams").mockImplementation(() => {
return { target: "signIn" };
});
});
it("Should render SignIn comp", () => {
//Below just shows the iframe and not the sign in comp
console.log(comp.debug())
});
});
I have a component which has multiple useState calls. It renders button when one of the state is populated. How can I mock populated state to be able to test if the button is rendered?
const Component = () => {
const [csvData, setCsvData] = useState([]);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState([]);
const handleCSVLoad = (data) => {
// ...some logic to process the data
setCsvData(data)
}
// ...other functions
return(
<>
// ...some other components
<CSVReader
onFileLoaded={handleCSVLoad}
/>
csvData[0]&& <button data-testId='submit_button'>Submit</button>
</>
)
the csvData is populated when CSV data is loaded with react-csv-reader.
I want to be able to write test like that:
it('does render Submit button', () => {
const { queryByTestId } = render(<Component />);
const submitButton = queryByTestId('submit_button');
expect(submitButton).toBeTruthy();
});
I tried to mock useState like this:
import React, { useState as useStateMock } from 'react';
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
describe('testing useState mocked', () => {
const setState = jest.fn();
const useStateMock = () => [[{email: 'test#test.com'}], setState];
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
afterEach(() => {
jest.clearAllMocks();
});
});
but it breaks as errors state is now defined and submit button is not rendered when error is not an empty array.
My component has a function which is triggered when a save button is clicked. Then based on that a fetch is done in the wrapper and the fetch response is then again passed down as a prop. So the putFn property accepts a function, the putResponse accepts a Promise.
I would like to mock the wrapper and focus in this test just on the component, in this example "myComponent".
Given the following test setup:
./MyComponent.test.js
function setup() {
let mockPutResponse;
const putMockFn = jest.fn(() => {
mockPutResponse = Promise.resolve(
JSON.stringify({ success: true, loading: false })
);
});
render(<MyComponent putFn={putMockFn} putResponse={mockPutResponse} />);
return { putMockFn };
}
test("MyComponent saves the stuff", async () => {
const { putMockFn } = setup();
const button = screen.getByRole("button", { name: /save changes/i });
userEvent.click(button);
// this passes
expect(putMockFn).toHaveBeenCalled();
// this does not pass since the component shows this message
// based on the putResponse property
expect(await screen.findByText(/saved successfully/i)).toBeInTheDocument();
});
How can I mock the return value passed into the putResponse property?
The component I want to test is something in the line of this:
./MyComponent.js
import React from "react";
const MyComponent = ({ putFn, putResponse }) => {
return (
<form onSubmit={putFn}>
{putResponse?.loading && <p>Loading...</p>}
{putResponse?.success && <p>saved succesfully</p>}
<label htmlFor="myInput">My input</label>
<input name="myInput" id="myInput" type="text" />
<button>Save changes</button>
</form>
);
};
export default MyComponent;
Which is used by a kind of wrapper, something similar to:
./App.js (arbitrary code)
import React, { useState } from "react";
import MyComponent from "./MyComponent";
export default function App() {
const [wrapperPutResponse, setWrapperPutResponse] = useState();
const handlePut = e => {
e.preventDefault();
setWrapperPutResponse({ loading: true });
// timeout, in the actual app this is a fetch
setTimeout(function() {
setWrapperPutResponse({ success: true, loading: false });
}, 3000);
};
return <MyComponent putFn={handlePut} putResponse={wrapperPutResponse} />;
}
Created a sandbox: codesandbox.io/s/bold-cloud-2ule8?file=/src/MyComponent.test.js
You can create a Wrapper component to render and control MyComponent
import React, { useState, useEffect } from "react";
import { screen, render } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import MyComponent from "./MyComponent";
const mockPutResponse = jest.fn()
function setup() {
const Wrapper = () => {
const [clicked, setClicked] = useState(false)
const response = clicked ? { success: true, loading: false} : null
useEffect(() => {
mockPutResponse.mockImplementation(() => {
setClicked(true)
})
}, [])
return <MyComponent putFn={mockPutResponse} putResponse={response} />
}
render(<Wrapper />);
}
test("MyComponent saves the stuff", async () => {
setup()
// expect(await screen.findByText(/loading.../i)).toBeInTheDocument();
const button = screen.getByRole("button", { name: /save changes/i });
userEvent.click(button);
// this passes
expect(mockPutResponse).toHaveBeenCalled();
// had some issue with expect acting up when using the toBeInDocument assertion
// so I used this one instead
const text = await screen.findByText(/saved succesfully/i)
expect(text).toBeTruthy()
});
Codesandbox
I nav component then will toggle state in a sidebar as well as open and close a menu and then trying to get this pass in code coverage. When I log inside my test my state keeps showing up as undefined. Not sure how to tackle this one here.
Component.js:
const Navigation = (props) => {
const {
classes,
...navProps
} = props;
const [anchorEl, setanchorEl] = useState(null);
const [sidebarOpen, setsidebarOpen] = useState(false);
const toggleSidebar = () => {
setsidebarOpen(!sidebarOpen);
};
const toggleMenuClose = () => {
setanchorEl(null);
};
const toggleMenuOpen = (event) => {
setanchorEl(event.currentTarget);
};
return (
<Fragment>
<Button
onClick={toggleMenuOpen}
/>
<SideMenu
toggleSidebar={toggleSidebar}
>
<Menu
onClose={toggleMenuClose}
>
</SideMenu>
</Fragment>
);
};
export default Navigation;
Test.js:
import { renderHook, act } from '#testing-library/react-hooks';
// Components
import Navigation from './navigation';
test('sidebar should be closed by default', () => {
const newProps = {
valid: true,
classes: {}
};
const { result } = renderHook(() => Navigation({ ...newProps }));
expect(result.current.sidebarOpen).toBeFalsy();
});
Author of react-hooks-testing-library here.
react-hooks-testing-library is not for testing components and interrogating the internal hook state to assert their values, but rather for testing custom react hooks and interacting withe the result of your hook to ensure it behaves how you expect. For example, if you wanted to extract a useMenuToggle hook that looked something like:
export function useMenuToggle() {
const [anchorEl, setanchorEl] = useState(null);
const [sidebarOpen, setsidebarOpen] = useState(false);
const toggleSidebar = () => {
setsidebarOpen(!sidebarOpen);
};
const toggleMenuClose = () => {
setanchorEl(null);
};
const toggleMenuOpen = (event) => {
setanchorEl(event.currentTarget);
};
return {
sidebarOpen,
toggleSidebar,
toggleMenuClose,
toggleMenuOpen
}
}
Then you could test it with renderHook:
import { renderHook, act } from '#testing-library/react-hooks';
// Hooks
import { useMenuToggle } from './navigation';
test('sidebar should be closed by default', () => {
const newProps = {
valid: true,
classes: {}
};
const { result } = renderHook(() => useMenuToggle());
expect(result.current.sidebarOpen).toBeFalsy();
act(() => {
result.current.toggleSidebar()
})
expect(result.current.sidebarOpen).toBeTruthy();
});
Generally though, when a hook is only used by a single component and/or in a single context, we recommend you simply test the component and allow the hook to be tested through it.
For testing your Navigation component, you should take a look at react-testing-library instead.
import React from 'react';
import { render } from '#testing-library/react';
// Components
import Navigation from './navigation';
test('sidebar should be closed by default', () => {
const newProps = {
valid: true,
classes: {}
};
const { getByText } = render(<Navigation {...newProps} />);
// the rest of the test
});
I created a custom hook to force a component to update but I'm having issues figuring out how to write a unit test with jest.
This is the hook
function useForceUpdate(condition) {
const [, setState] = useState(0);
const forceUpdate = () => setState(1);
useEffect(() => {
if (condition) {
forceUpdate();
}
}, [condition]);
}
export default useForceUpdate;
I was able to successfully test this hook this way
import React from "react";
import useForceUpdate from "hooks/use-force-update";
const Component = ({ shouldUpdate }) => {
const hasUpdated = useForceUpdate(shouldUpdate);
return <div>{hasUpdated}</div>;
};
describe("useForceUpdate", () => {
let subject;
let props;
beforeEach(() => {
props = { shouldUpdate: true };
subject = memoize(() => mount(<Component {...props} />));
});
describe("when the condition is true", () => {
it("it calls forceUpdate", () => {
expect(
subject()
.find("div")
.text()
).toBe("1");
});
});
describe("when the condition is false", () => {
beforeEach(() => {
props = { shouldUpdate: false };
});
it("it does not call forceUpdate", () => {
expect(
subject()
.find("div")
.text()
).toBe("0");
});
});
});