Why is first Jest test causing second test to fail? - reactjs

I have a React component which renders a list of components. I'm running some tests which mock the axios module which loads in the users from JSONPlaceHolder. All works fine including the the async test and it's mocks data as expected. However if you look at the code below it only passes as long as the first test is commented out? Am I missing something? Been banging my head for ages. Is there some cleanup that needs to be done between tests? Thanks in advance.
import { waitForElement } from 'enzyme-async-helpers';
import UsersList from '../UsersList';
import axios from 'axios';
const mockUsers = [
{
"id": 1,
"name": "Leanne Mock",
"username": "Bret",
"email": "Sincere#april.biz"
},
{
"id": 2,
"name": "John Mock",
"username": "Jospeh",
"email": "wacky#april.biz"
}
]
axios.get.mockImplementationOnce(() => Promise.resolve({
data: mockUsers
}))
describe('<UsersList /> tests:', () => {
//WHEN I UNCOMMENT THIS TEST THE SECOND TEST FAILS?
test('It renders without crashing', (done) => {
// const wrapper = shallow(<UsersList />);
});
test('It renders out <User /> components after axios fetches users', async () => {
const wrapper = shallow(<UsersList />);
expect(wrapper.find('#loading').length).toBe(1); //loading div should be present
await waitForElement(wrapper, 'User'); //When we have a User component found we know data has loaded
expect(wrapper.find('#loading').length).toBe(0); //loading div should no longer be rendered
expect(axios.get).toHaveBeenCalledTimes(1);
expect(wrapper.state('users')).toEqual(mockUsers); //check the state is now equal to the mockUsers
expect(wrapper.find('User').get(0).props.name).toBe(mockUsers[0].name); //check correct data is being sent down to User components
expect(wrapper.find('User').get(1).props.name).toBe(mockUsers[1].name);
})
})
The Error message I get is:
The render tree at the time of timeout:
<div
id="loading"
>
Loading users
</div>
console.warn node_modules/enzyme-async-helpers/lib/wait.js:42
As JSON:
{ node:
{ nodeType: 'host',
type: 'div',
props: { id: 'loading', children: ' Loading users ' },
key: undefined,
ref: null,
instance: null,
rendered: ' Loading users ' },
type: 'div',
props: { id: 'loading' },
children: [ ' Loading users ' ],
'$$typeof': Symbol(react.test.json) }
Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total

You only mock the first axios.get call because you are using mockImplementationOnce.
When you shallow(<UsersList />) twice, the second time is timing out loading the users.
You can add a beforeEach method with a mockResolvedValueOnce inside, to mock the axios.get before every single test:
beforeEach(() => {
axios.get.mockResolvedValueOnce({data: mockUsers});
}

Having the same issue, but I'm not making a request. I'm building a client-side React application and testing for the render of sub-components. I have an image carousel that loads on my Home component and I'm writing tests for it. If I comment out all but one test (any test) it passes. If I have more than one test (any combination of tests), it fails. I've tried async/await/waitFor, react-test-renderer, using done() - nothing seems to change this behavior.
import { render, screen } from '#testing-library/react';
import ImageCarousel from '../carousel/ImageCarousel';
import localPhotos from '../../data/localPhotos';
// passing in the full array of images is not necessary, it will cause the test to time out
const testArray = localPhotos.slice(0, 3);
describe('Image Carousel', () => {
it('renders without error', () => {
render(<ImageCarousel images={testArray} />);
const imageCarousel = screen.getByTestId('image-carousel');
expect(imageCarousel).toBeInTheDocument();
});
// it('displays the proper alt text for images', () => {
// render(<ImageCarousel images={testArray} />);
// const photo1 = screen.getByAltText(localPhotos[0].alt);
// const photo2 = screen.getByAltText(localPhotos[1].alt);
// expect(photo1.alt).toBe(localPhotos[0].alt);
// expect(photo2.alt).toBe(localPhotos[1].alt);
// });
// it("displays full-screen icons", () => {
// render(<ImageCarousel images={testArray} />);
// const fullScreenIcons = screen.getAllByTestId('full-screen-icon');
// expect(fullScreenIcons.length).toBe(testArray.length);
// })
// shows controls when showControls is true
// does not show controls when showControls is false
// it('displays the proper number of images', () => {
// render(<ImageCarousel images={testArray} />);
// const carousel_images = screen.getAllByTestId('carousel_image');
// expect(carousel_images.length).toBe(testArray.length);
// });
// calls next when clicked
// calls previous when clicked
// returns to first image when next is clicked and last image is shown
// moves to last image when previous is clicked and first image is shown
});

Related

Mocking function in React class component using Jest produces "type is invalid" error

I'm writing tests using Jest for a custom DataTable component. I'm also using React Testing Library.
The component loads an iframe when a row is clicked. I'm attempting to mock the function that loads the iframe and I'm getting an error message when running the test that uses the mock. For context, the goal of mocking this function is preventing the test from loading the actual iframe.
The full error message is: Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
My understanding is that this error message usually happens when you're incorrectly importing a module. However, I don't believe this is my case because the component is being imported and used in other tests and it works fine in those tests. It's only the component with the mocked function that produces this error.
How would I mock this function? The function is loadIframe(id) and it's in a class component. The code for the component and the test code is below.
Stripped down version of the component with the function I want to mock:
class MyDataTable extends React.Component {
...
// I want to mock this function
loadIframe(id = "") {
this.setState({iframe: `/path/to/iframe/${id}`})
}
...
render() {
return (
...
{this.state.iframe !== "" &&
<iframe src={this.state.iframe}/>
}
...
)
);
}
export default MyDataTable;
Code for the tests:
import "#testing-library/jest-dom";
import { render, screen, waitFor } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import axios from "axios";
import MyDataTable from "../../components/DataTable/MyDataTable";
jest.mock("axios");
jest.mock("../../components/DataTable/MyDataTable", () => ({
...jest.requireActual("../../components/DataTable/MyDataTable"),
loadIframe: jest.fn((id) => {
// this mocked implementation will need to do something, but I've removed it from this example
})
}));
describe("My tests", () => {
it("should load iframe on row click", async () => {
const data = [
["row 1, col 1", "row 1, col 2"],
["row 2, col 1", "row 2, col 2"]
];
// Need to make a mock POST request to get data so a row can be clicked
axios.post.mockResolvedValue({ data });
// The error is thrown when render() is called here
render(<MyDataTable id="test_id"/>);
await waitFor(() => expect(axios.post).toHaveBeenCalled());
userEvent.click(screen.getAllByRole("gridcell")[0]);
expect(screen.getByTitle("iframe")).toBeInTheDocument();
});
});
IMO, This case is unnecessary to test logic on function loadIframe. You just query selector that you wanna test and expect src attribute should be a value according to state change from function loadIframe.
jest.mock("axios");
describe("My tests", () => {
it("should load iframe on row click", async () => {
const data = [
["row 1, col 1", "row 1, col 2"],
["row 2, col 1", "row 2, col 2"]
];
// Need to make a mock POST request to get data so a row can be clicked
axios.post.mockResolvedValue({ data });
// The error is thrown when render() is called here
const {container} = render(<MyDataTable id="test_id"/>);
await waitFor(() => expect(axios.post).toHaveBeenCalled());
userEvent.click(screen.getAllByRole("gridcell")[0]);
const iFrame = container.querySelector('iframe')
// this step is expect `src` attribute in iFrame element should be according to state change inside function `loadiFrame`
expect(iFrame).toHaveAttribute('src','/path/to/iframe/1')
});
});

Describe method can only pass with 1 test unless re-rendering each component again and again

I'm trying to figure out why my test - which passes when ran alone - is failing whenever the describe block contains more than 1 test. Take this example, which I've taken from my real code and simplified:
describe('Create Account Form', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
const password1 = container.querySelector('input[name="password1"]');
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument(); // fails
});
});
The 2nd test fails, but passes only when commenting out the first test, or re-rendering the container again in the test like this:
it('Another test', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
expect(email).toBeInTheDocument(); // passes
});
Why does this have to happen? I would much rather not have to re-render the container and declare new variables inside each test block.
Thank you
RTL will unmount React trees that were mounted with render in afterEach hook. See cleanup.
Please note that this is done automatically if the testing framework you're using supports the afterEach global and it is injected to your testing environment (like mocha, Jest, and Jasmine).
Move the render code into beforeEach or individual test case. So that we can create react trees before each test case. Isolate test cases from each other, using their own test data without affecting the rest.
E.g.
index.tsx:
import React from 'react';
export function Example() {
return (
<div>
<input name="email" />
<input name="password1" />
</div>
);
}
index.test.tsx:
import { render } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import React from 'react';
import { Example } from './';
describe('70753645', () => {
let email, password1, allInputs;
beforeEach(() => {
const { container } = render(<Example />);
email = container.querySelector('input[name="email"]');
password1 = container.querySelector('input[name="password1"]');
allInputs = container.querySelectorAll('input');
});
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument();
});
});
Test result:
PASS stackoverflow/70753645/index.test.tsx (9.222 s)
70753645
✓ Should render all fields (24 ms)
✓ Another test (3 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.717 s
package versions:
"#testing-library/react": "^11.2.2",
"jest": "^26.6.3",

How to refresh powerbi-client-react component once token has expired?

I am testing a react web app where I can display reports from Power BI. I am using powerbi-client-react to embed the reports. However, I face an issue when I load the component with an expired token, I get this error: Content not available screenshot.
So whenever that happens, I catch it with the error event handler, get a new token and update the powerbi report accessToken. However, it doesn't seem to reload/refresh the embed when I set the new accessToken in react. It only displays the screenshot above.
Error log screenshot.
Is there a way to force refresh the embed component with the new access token? or is my approach not correct? Any mistakes pointer would be appreciated.
import React from 'react';
import {models} from 'powerbi-client';
import {PowerBIEmbed} from 'powerbi-client-react';
// Bootstrap config
let embedConfigTest = {
type: 'report', // Supported types: report, dashboard, tile, visual and qna
id: reportId,
embedUrl: powerBIEmbedURL,
accessToken: null,
tokenType: models.TokenType.Embed,
pageView: 'fitToWidth',
settings: {
panes: {
filters: {
expanded: false,
visible: false,
},
},
background: models.BackgroundType.Transparent,
},
};
const PowerBiReport = ({graphName, ...props}) => {
let [embedToken, setEmbedToken] = React.useState();
let [embedConfig, setEmbedConfig] = React.useState(embedConfigTest);
React.useEffect(
() => {
setEmbedToken(EXPIRED_TOKEN);
setEmbedConfig({
...embedConfig,
accessToken: EXPIRED_TOKEN, // Initiate with known expired token
});
},
[graphName]
);
const changeSettings = (newToken) => {
setEmbedConfig({
...embedConfig,
accessToken: newToken,
});
};
// Map of event handlers to be applied to the embedding report
const eventHandlersMap = new Map([
[
'loaded',
function() {
console.log('Report has loaded');
},
],
[
'rendered',
function() {
console.log('Report has rendered');
},
],
[
'error',
async function(event, embed) {
if (event) {
console.error(event.detail);
console.log(embed);
// Simulate getting a new token and update
setEmbedToken(NEW_TOKEN);
changeSettings(NEW_TOKEN);
}
},
],
]);
return (
<PowerBIEmbed
embedConfig={embedConfig}
eventHandlers={eventHandlersMap}
cssClassName={'report-style-class'}
/>
);
};
export default PowerBiReport;
Thanks #vtCode. Here is a sample but the refresh can only happen in 15 secs interval.
import { PowerBIEmbed } from "powerbi-client-react";
export default function PowerBiContainer({ embeddedToken }) {
const [report, setReport] = useState(null);
useEffect(() => {
if (report == null) return;
report.refresh();
}, [report, embeddedToken]);
return (
<PowerBIEmbed
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
getEmbeddedComponent={(embeddedReport) => setReport(embeddedReport)};
/>
);
}
Alternatively, you can add the React "key" attribute which remounts the component when embededToken changes
<PowerBIEmbed key={embeddedToken}
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
/>
I ended up solving this issue, although not so beautiful.
I checked the powerbi-client wiki as it has dependency on it and found out that you could use embed.reload() in the embed object I get from the error function.
For some reason (I could not find out why), the error handler gets triggered twice, so to avoid refreshing the token twice, I had to create a dialog notifying the user that the token had expired and whenever that dialog is closed, I reload the powerbi report.
Exact wiki reference:
Overriding Error Experience
Reload a report
Update embed token

How to correctly mock React Navigation's getParam method using Jest

I have a React Native app in which I'm trying to write some integration tests using Jest & Enzyme. My situation is as follows, I have a component which fetches a navigation param being passed to it from the previous screen using getParam - which works fine normally, I'm just struggling to successfully get a value in there using mock data. My code looks like this:
In my container I have this in the render method:
const tickets = navigation.getParam('tickets', null);
Then in my test I have the following:
const createTestProps = (testProps: Object, navProps: any = {}) =>
({
navigation: {
navigate: jest.fn(),
getParam: jest.fn(),
...navProps,
},
...testProps,
} as any);
let props = createTestProps(
{},
{
state: {
// Mock navigation params
params: {
tickets: [
{
cellNumber: '123456789',
ticketId: 'xxx',
},
{
cellNumber: '123456789',
ticketId: 'xxx',
},
],
},
},
}
);
const container = mount(
<MockedProvider mocks={mocks} addTypename={false}>
<ThemeProvider theme={theme}>
<TicketSummaryScreen {...props} />
</ThemeProvider>
</MockedProvider>
);
As you can see I've attempted to mock the actual navigation state, which I've checked against what's actually being used in the real component, and it's basically the same. The value for tickets is still undefined each time I run the test. I'm guessing it has to do with how I've mocked the getParam function.
Anyone have any ideas? Would be much appreciated!
I just successfully fixed this problem on my project. The only advantage that I had is that I had the render method being imported from a file I created. This is a great because all my tests can be fixed by just changing this file. I just had to merge some mocked props into the component that render was receiving.
Here's what it looked like before:
/myproject/jest/index.js
export { render } from 'react-native-testing-library'
After the fix:
import React from 'react'
import { render as jestRender } from 'react-native-testing-library'
const navigation = {
navigate: Function.prototype,
setParams: Function.prototype,
dispatch: Function.prototype,
getParam: (param, defaultValue) => {
return defaultValue
},
}
export function render(Component) {
const NewComponent = React.cloneElement(Component, { navigation })
return jestRender(NewComponent)
}
This setup is great! It just saved me many refactoring hours and probably will save me more in the future.
Maybe try returning the mock data from getParam
Try bellow example code.
const parameters = () => {
return "your value"
}
.....
navigation: {
getParam: parameters,
... navProps
},
... testProps
});
Give it a try
const navState = { params: { tickets: 'Ticket1', password: 'test#1234' } }
const navigation = {
getParam: (key, val) => navState?.params[key] ?? val,
}
here navState values will be params that you are passing.

react-testing-library | Cannot Split Test into smaller chunks inside describe method

I'm learning about unit testing React components using react-testing-library
I have the component rendering correctly, however, when I aim to break the test into smaller chunks inside a describe() function. The test breaks and here's why.
Current only one or the other test() passes but not both
import React from 'react'
import 'react-testing-library/cleanup-after-each'
import { render, fireEvent } from 'react-testing-library'
import Quantity from '../components/Quantity'
describe('Quantity Component', () => {
const { container, getByTestId } = render(<Quantity />)
// first test
test('checks that quantity is never 0', () => {
expect(getByTestId('quantity')).not.toBe('0')
})
// second test
test('checks for the initial product quantity count', () => {
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
When trying to run both tests it errors:
Unable to find an element by: [data-testid="quantity"]
[data-testid="quantity"] is just an attribute that I passed inside my desired JSX tag.
The test passes when running only the first or second test but not both concurrently.
What am I missing here?
Cross-contamination is strictly discouraged in unit testing.
The problem is that a setup occurs only once per Quantity Component suite, while it should be done for each test. This is what beforeEach is for:
describe('Quantity Component', () => {
let container, getByTestId;
beforeEach(() => {
({ container, getByTestId } = render(<Quantity />));
});
...
You need to also use an afterEach cleanup.
describe('your tests', () => {
afterEach(cleanup);
beforeEach(() => ({container, getById} = render(<Quantity />))
it('does something', () => {
expect(getByTestId('quantity')).toHaveTextContent(0);
}
}
I suggest you call the render inside your it clauses, it keeps the tests easier to manage:
describe('Quantity Component', () => {
test('checks that quantity is never 0', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).not.toBe('0')
})
test('checks for the initial product quantity count', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
The added advantage is that if for some reason one of your tests needs to run with different props you can do that more easily with this setup.

Resources