How to convert react asynchronous tests synchronously? - reactjs

Below is the test for ag-grid. Documentaion can be found at https://www.ag-grid.com/javascript-grid-testing-react/
Few of my tests are failing in CI when tests are asynchronous as test1. Is there any solution to make it consistent? I tried test2 approach make it synchronous but that is also failing. Is there any better way to run tests with consistency?
describe('ag grid test 1', () => {
let agGridReact;
let component;
const defaultProps = {
//.....
}
beforeEach((done) => {
component = mount(<CustomAGGridComp {...defaultProps} />);
agGridReact = component.find(AgGridReact).instance();
// don't start our tests until the grid is ready
ensureGridApiHasBeenSet(component).then(() => done(), () => fail("Grid API not set within expected time limits"));
});
it('stateful component returns a valid component instance', async () => {
expect(agGridReact.api).toBeTruthy();
//..use the Grid API...
var event1 = {
type: 'cellClicked', rowIndex: 0, column: { field: "isgfg", colId: "isgfg", headerName: "Property 2" },
event: {
ctrlKey: false,
shiftKey: false
}
}
await agGridReact.api.dispatchEvent(event1)
//some expect statements
})
});
describe('ag grid test 2', () => {
let agGridReact;
let component;
const defaultProps = {
//.....
}
beforeEach((done) => {
component = mount(<CustomAGGridComp {...defaultProps} />);
agGridReact = component.find(AgGridReact).instance();
// don't start our tests until the grid is ready
ensureGridApiHasBeenSet(component).then(() => done(), () => fail("Grid API not set within expected time limits"));
});
it('stateful component returns a valid component instance', () => {
expect(agGridReact.api).toBeTruthy();
//..use the Grid API...
var event1 = {
type: 'cellClicked', rowIndex: 0, column: { field: "isgfg", colId: "isgfg", headerName: "Property 2" },
event: {
ctrlKey: false,
shiftKey: false
}
}
agGridReact.api.dispatchEvent(event1);
setTimeout(() => {
//some expect statements
}, 500);
})
});

Related

Mock a react hook with different return values

I'd like to test a react component, which displays a list of element or not, based on the return value of a custom hook.
In my first test, I want to make sure nothing is displayed, so I used this at the top of my test method:
jest.mock('components/section/hooks/use-sections-overview', () => {
return {
useSectionsOverview: () => ({
sections: [],
}),
};
});
in the second test, I want to display something, so I used this one
jest.mock('components/section/hooks/use-sections-overview', () => {
return {
useSectionsOverview: () => ({
sections: [
{id: '1', content: 'test'}
],
}),
};
});
Unfortunately, when running my test, it always returns an empty array.
I tried adding jest.restoreAllmocks(); in my afterEach method, but this doesn't change anything.
Am I missing something ?
jest.mock will always be pulled to the top of the file and executed first, so your tests can't change the mock beyond the initial one.
What you can do though is have the mock point at some kind of stubbed response, wrapped inside a jest.fn call (to defer execution), so it's evaluated after each change.
e.g.
const sections_stub = {
sections: [],
};
jest.mock('components/section/hooks/use-sections-overview', () => ({
useSectionsOverview: jest.fn(() => sections_stub),
}));
describe('my component', () => {
it('test 1', () => {
sections_stub.sections = [];
// run your test
});
it('test 2', () => {
sections_stub.sections = [
{ id: '1', content: 'test'}
];
// run your other test
});
});

Testing custom hook React

I created a hook
export function useRedirectStartParams() {
const scenarios: IScenario[] = useSelector(scenariosSelector);
const block: IBlockItem = useSelector(selectedBlockSelector);
const [redirects, setRedirects] = useState<IDropdownEl[]>([]);
useEffect(() => {
const newRedirects =
block?.block_data?.redirects?.map((block: any) => {
const scenarioName = scenarios.find(
(scenario) => scenario.id === block.scenario_id
)?.name;
return {
name: scenarioName,
val: {
scenarioId: block.scenario_id,
blockId: block.id,
},
};
}) || [];
setRedirects(newRedirects);
}, [block, scenarios]);
return { redirects };
}
use it in my component
const { redirects } = useRedirectStartParams();
and then try to test it with jest like this
import { useRedirectStartParams } from "#hooks/useRedirectStartParams";
jest.mock("#hooks/useRedirectStartParams");
beforeEach(() => {
useRedirectStartParams.mockReturnValueOnce({
redirects: [{ name: "ad", val: "123" }],
});
})
but got error that redirects are undefined.
I also tried to mock hook this way
jest.mock("#hooks/useRedirectStartParams", () => jest.fn());
And it didn't help me
I expect that redirects won't be undefined. Not renderHook. i want to mock value of hook for component

AG Grid React does not render cell values when testing using Enzyme and RTL

so recently we updated ag-grid-react and ag-grid-community from 27.0.1 to 28.0.0 and previous working tests now fail.
Test tries to get a value from a row cell and compares to the given one.
Test (v. 27.3.0)
describe("Simple list rendering", () => {
const handleRowDoubleClick = () => { }
const handleRowSelect = () => { }
const formDataWithId = formData.map((item, index) => {
const newItem = checkNumberLength(item, items);
return { ...newItem, id: index };
});
const colDefs = [{
headerName: "test",
valueGetter: "7"
}]
const listRender = (
<AgGridReact
columnDefs={colDefs}
rowData={formDataWithId}
rowSelection="multiple"
suppressClickEdit={true}
onRowClicked={handleRowSelect}
onRowDoubleClicked={handleRowDoubleClick}
immutableData={true}
rowHeight={28}
/>
)
let component
let agGridReact
beforeEach((done) => {
component = mount(listRender);
agGridReact = component.find(AgGridReact).instance();
// don't start our tests until the grid is ready
ensureGridApiHasBeenSet(component).then(() => done(), () => fail("Grid API not set within expected time limits"));
});
it('stateful component returns a valid component instance', () => {
expect(agGridReact.api).toBeTruthy();
});
it("agGrid shows password field as * instead of string", () => {
console.log(component.find(".ag-cell-value").first().html())
expect(component.render().find(".ag-cell-value").first()).toEqual("7");
});
it("List contains derivative field", () => {
render(listRender);
expect(screen.getAllByText("5")).toHaveLength(1);
})})
RowData Used
[
{ CPS: 2, TST: 2, DERIVATIVE: "" },
{ CPS: 5, TST: 2, DERIVATIVE: "" },
]
Console log in test
When running the App the Grid renders the cell values using custom valueGetters.
Test is running in v27.3.0 and renders the div using ag-cell-value class but not the value (in v28.0.0 does not even render the div when trying to find node using ag-cell-value class)
Is there something wrong we are doing? Any help is appreciated!

React doesn't rerender on state change while testing

I am testing a react component which has a simple Material-ui switch that updates a boolean field. The component works without any issues when I run the app locally.
In my test, I am mocking the graphql calls via MockedProvider. The mocked provider works as expected, and I can see that the initial response and the update response arrive and they update the state. However, when I find the switch and check it on the screen, it stays unchecked. My test fails with:
Received element is checked: <input checked="" class="PrivateSwitchBase-input-40 MuiSwitch-input" name="callbackEnabled" type="checkbox" value="" />
My first guess is that React doesn't rerender this state change. Do I need to somehow force rerender? Or what is the correct way of testing this kind of behaviour?
The test:
it('should update boolean field', async () => {
const mocks = [
{
request: {
query: myQuery,
variables: {
clientId: 'cl_0',
},
},
result: {
data: {
myQuery: {
callbackEnabled: true
},
},
},
},
{
request: {
query: myMutation,
variables: {
clientId: 'cl_0',
callbackEnabled: false,
},
},
result: {
data: {
myMutation: {
callbackEnabled: false,
},
},
},
},
];
let base = null;
await act(async () => {
const { baseElement, } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<MyComponent clientId="cl_0" error={undefined} />
</MockedProvider>
);
base = baseElement;
});
// eslint-disable-next-line no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, 0));
// check for info: https://www.apollographql.com/docs/react/development-testing/testing/#testing-the-success-state
// testing initial state: these pass
expect(base).toBeTruthy();
expect(screen.getByRole('checkbox', { name: /callbacks/i })).toBeInTheDocument();
expect(screen.getByRole('checkbox', { name: /callbacks/i })).toBeChecked();
// simulate a switch click
await act(async () => {
userEvent.click(screen.getByRole('checkbox', { name: 'Callbacks' }));
});
// eslint-disable-next-line no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, 0)); // wait for response
// fails here
expect(screen.getByRole('checkbox', { name: /callbacks/i })).not.toBeChecked();
});
First add a test-id to your checkbox component like this :
<input
type="checkbox"
checked={checkboxState}
data-testid="checkboxID" />
Then in ur test to test if checkbox status was changed :
const input = getByTestId("checkboxID");
// Assuming we set initial status for checkbox component to be false
expect(input.checked).toEqual(false);
fireEvent.click(input);
// check handling click in checkbox
expect(input.checked).toEqual(true);

How can I pass event properties to a customEvent using React Testing Library?

I am trying to test a component using RTL that uses a customEvent to trigger a display. Given a component like this:
const Alerts = () => {
const [alerts, setAlerts] = useState([]);
const addAlert = (body, type = 'info', timeout = 7500) => {
const key = nextIndex++;
setAlerts([
...alerts,
{ key, body, type },
]);
// Set a timer to remove the alert.
setTimeout(() => {
setAlerts(alerts.filter((alert) => alert.key !== key));
}, timeout);
};
document.addEventListener(
'newalert',
({ details:
{
body = '',
type = '',
timeout = 1000
} = {}
}) => {
addAlert(body, type, timeout);
});
return (
<>
{alerts.map((alert) => <Alert {...alert} />)}
</>
);
};
I am trying to test it like this:
test('An alert is rendered on a newalert event', () => {
render(<Alerts />);
fireEvent(
document,
createEvent('newalert', document,
{
details: {
body: 'Hello World!',
type: 'info',
timeout: 1000,
}
})
);
expect(screen.getByText('Hello world'));
});
And it is firing the custom event as expected, however none of the properties (details, body, type, or timeout) is being passed to the event. What am I missing?
You need to pass the name of the event constructor as the fourth parameter, and it works!
createEvent(
'newalert',
document,
{
detail: {
body: 'Hello World!',
type: 'info',
timeout: 1000,
}
},
{ EventType: 'CustomEvent' }
)
);

Resources