I am writing unit tests for my React JS application using Jest and React testing library. I am trying to test the async functions. But it is not working.
This is my test
import React from 'react';
import "regenerator-runtime/runtime"
import {render, waitFor, fireEvent, screen} from '#testing-library/react';
import {DonorSelect} from "../../src/components";
import MockAdapter from "axios-mock-adapter";
import Axios from 'axios';
import vars from "../configVars";
import {searchUsersResponse} from "../mock/apiResponse";
import { act } from "react-dom/test-utils"
test ("DonorSelect Component", async () => {
let selectedUser = null;
let users = [ ];
let inputValue=""
//mock the users api endpoint
let mock = new MockAdapter(Axios);
mock.onGet(`${vars.baseApEndpoint}/users?keyword=client`)
.reply(200, searchUsersResponse)
await act(async() => await render(
<DonorSelect
id={"user_select"}
onSelect={(user, displayText) => {
selectedUser = { ...user }
}}
onInputChange={(textFieldValue) => {
inputValue = textFieldValue;
}}
onUsersFetched={(userItems) => {
users = [ ...userItems ]
}}
onChange={(name, value) => {
}}
label={"Search User"}
placeholder={"Please, select a user."}
name={"user"}
value={selectedUser!=null? selectedUser.id:""}
inputValue={inputValue}
/>
))
//assert that input is rendered
expect(screen.getByTestId("user_select_input")).toBeTruthy()
fireEvent.change(screen.getByTestId("user_select_input"), {
target: { value: "client" }
})
fireEvent.focus(screen.getByTestId("user_select_input"))
await waitFor(() => {
//assert that if the label is rendered
expect(screen.getByTestId("user_select_label")).toBeTruthy()
// assert that input is rendered
expect(screen.getByTestId("user_select_user_item_0")).toBeTruthy()
})
})
As you can see in the test what is not working is the last expect()
expect(screen.getByTestId("user_select_user_item_0")).toBeTruthy()
It is always failing. What that component is doing is that, when the input value changes and focus on the input, it will make the api request and render the items. The component is working as expected. I have fully tested it. But it is just not working in the test. What is wrong with my code and how can I fix it?
The default waitFor timeout time is 1000ms.
If you are calling a real endpoint without mocking (mocking is recommended, for example using msw), this might take more than 1 second to execute.
This will result in the timeout being exceeded and the waitFor throws an error.
IF you do not want to mock the endpoint, intercept it and return a test value, which should be under 1 sec, you could also extend the timeout time ti wait for the real api call to be executed and resolved:
await waitFor(() => {
//assert that if the label is rendered
expect(screen.getByTestId("user_select_label")).toBeTruthy()
// assert that input is rendered
expect(screen.getByTestId("user_select_user_item_0")).toBeTruthy()
}
,{timeout: 4000}) // this will now wait 4 secs for the execution, but you should see what works for you.
Based on the information here:
Testing: waitFor is not a function #8855 link
The Solution that works for me is update the library to new version:
This module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:
npm install --save-dev #testing-library/react
or
for installation via yarn
yarn add --dev #testing-library/react
This library has peerDependencies listings for react and react-dom.
React Testing Library versions 13+ require React v18. If your project uses an older version of React, be sure to install version 12:
npm install --save-dev #testing-library/react#12
yarn add --dev #testing-library/react#12
For more information
Related
I created complete offline ReactJS web application and I want to run it from android application from Web View using React-Native.
I followed the following procedure to do so:
1. I created a compiled ReactJS web application got the build using the following command:
npm run build
Then I created react-native project and placed the build folder with following architecture
I updated App.js with the following content:
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, WebView} from 'react-native';
import {roscon} from "./build/index.html";
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<View style={{height: 300, width: 300,overflow:'hidden' }}>
<WebView
source={{uri: roscon}}
scalesPageToFit={true}
domStorageEnabled={true}
javaScriptEnabled={true}
startInLoadingState={true}
/>
</View>
);
}
}
After running this code I expected it to run my ReactJS Web application, instead I got white screen.
Can you please tell what can be the causing issues and how i can make my ReactJS Web App run on react-native?
Note: I was able to run generated build folder using npm command
serve -s build
But I still can't figure out how to port it to react-native project as WebView
After research and testing, I found a solution.
The main issue i found was the compiled build folder is rendered as static html. And it needed a server to serve pages.
So, I followed this link for getting build project to get it up and running
Then, integrating it with nodejs Android Project Samples to get my build folder running in android as a Webview.
Note: I also tried react-snapshot and react-snap but they didn't gave satisfactory results.
Try to require the html file correctly and pass it in to source prop in this way:
<WebView
source={require('./build/index.html')}
/>
Install
npm install react-native-react-bridge
These are used to render React app in WebView
npm install react-dom react-native-webview
Requirements
react 16.8+
react-native 0.60+
Usage
Fix metro.config.js to use babelTransformer from this library.
module.exports = {
transformer: {
babelTransformerPath:
require.resolve('react-native-react- >.
.bridge/lib/plugin'),
...
},
};
Make entry file for React app. web.js
import React, { useState } from "react";
import {
webViewRender,
emit,
useSubscribe,
} from "react-native-react-bridge/lib/web";
const Root = () => {
const [data, setData] = useState("");
// useSubscribe hook receives message from React Native
useSubscribe((message) => {
if (message.type === "success") {
setData(message.data);
}
});
return (
<div>
<div>{data}</div>
<button
onClick={() => {
// emit sends message to React Native
// type: event name
// data: some data which will be serialized by JSON.stringify
emit({ type: "hello", data: 123 });
}}
/>
</div>
);
};
// This statement is detected by babelTransformer as an entry point
// All dependencies are resolved, compressed and stringified into one file
export default webViewRender(<Root />);
Use the entry file in your React Native app with WebView.
import React from "react";
import WebView from "react-native-webview";
import { useBridge } from "react-native-react-bridge";
import webApp from "./WebApp";
const App = () => {
// useBridge hook create props for WebView and handle communication
// 1st argument is the source code of React app
// 2nd argument is callback to receive message from React
const { ref, source, onMessage, emit } = useBridge(webApp, (message) => {
// emit sends message to React
// type: event name
// data: some data which will be serialized by JSON.stringify
if (message.type === "hello" && message.data === 123) {
emit({ type: "success", data: "succeeded!" });
}
});
return (
<WebView
// ref, source and onMessage must be passed to react-native-webview
ref={ref}
source={source}
onMessage={onMessage}
/>
);
};
I'm using react with redux and testing with cypress,
I was able to access the store using
cy.window().its('store').invoke('getState').then((state) => {}
But how do i access a component's local state rather than the application store?
I tried
cy.get('.simple-component').its('getState')
or
cy.get('.simple-component').invoke('getState')
but Cypress is returning "CypressError: Timed out retrying: cy.invoke() errored because the property: 'getState' does not exist on your subject"
And on the Cypress console (in chrome) it's yeilding:
Yielded:
<div class="simple-component" getstate="[object Object]"></div>
It seems that's caused by React removing the methods from the DOM so i need to access it in React rather than in the DOM?
import React, { Component } from 'react';
class simpleComponent extends Component {
constructor(props) {
super(props)
this.state = {
sample: "hello"
}
}
// getState() just for testing on cypress
getState() {
return this.state
}
render(){
return <div className="simple-component" getState={this.getState()}></div>
}
}
As an alternative can i export the local component state at the end of the simple-component using window.store?
>= version 7.0.0
As of Cypress 7.0, the new Component Test Runner is now bundled with Cypress
From https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react:
We still need to install the react adapter to mount components:
yarn add -D cypress #cypress/react #cypress/webpack-dev-server
add a glob pattern matching your component tests to cypress.json:
{
"component": {
"testFiles": "**/*.test.{js,ts,jsx,tsx}",
"componentFolder": "src"
}
}
Tell Cypress to use #cypress/webpack-dev-server for component tests. in cypress/plugins/index.js:
const injectDevServer = require("#cypress/react/plugins/react-scripts")
module.exports = (on, config) => {
injectDevServer(on, config)
return config
}
This will configure the Cypress Webpack Dev Server to use the same Webpack configuration as Create React App uses.
If you are using a different template, like Next.js, we have some other adapters available. It's also possible to create your own adapter.
< version 7.0.0
There's a Cypress Plugin for that, called react-unit-test. It gives you the ability to mount React components directly (adds a cy.mount() command) and provides access to the component's internal state.
Here's an example from the repo's readme:
// load Cypress TypeScript definitions for IntelliSense
/// <reference types="cypress" />
// import the component you want to test
import { HelloState } from '../../src/hello-x.jsx'
import React from 'react'
describe('HelloState component', () => {
it('works', () => {
// mount the component under test
cy.mount(<HelloState />)
// start testing!
cy.contains('Hello Spider-man!')
// mounted component can be selected via its name, function, or JSX
// e.g. '#HelloState', HelloState, or <HelloState />
cy.get(HelloState)
.invoke('setState', { name: 'React' })
cy.get(HelloState)
.its('state')
.should('deep.equal', { name: 'React' })
// check if GUI has rerendered
cy.contains('Hello React!')
})
})
You can identify the element without mounting the react component. If you are testing your react app in isolation with the source code or writing functional UI test cases, you can consider a Cypress plugin called cypress-react-selector. It helps you identify web elements by component, props, and state even after the minification. You need to use React Dev Tool to identify the component names in that case.
Here is an example:
Suppose your react app:
const MyComponent = ({ someBooleanProp }) => (
<div>My Component {someBooleanProp ? 'show this' : ''} </div>
)
const App = () => (
<div id='root'>
<MyComponent />
<MyComponent name={bob} />
</div>
)
ReactDOM.render(<App />, document.getElementById('root'))
Then you can simply identify the react element like:
cy.getReact('MyComponent', { name: 'bob' } ).getCurrentState();
Find more sample test here
Hope it will help!
I have created an empty application with create-react-app. it provides a test script at pckage.json and a sample App.test.js. at the first step, I want to set up the test environment according to the documentation of create-react-app. in my app I will use localStorage.
let's say an action like below to be tested
export const cancel = () => {
localStorage.removeItem("MyAppData");
return { type: types.USER_CANCEL};
};
besides the enzyme part, it shows how to initialize localStorage. so I ended up with a setupTests.js file like
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
// react-testing-library renders your components to document.body,
// this will ensure they're removed after each test.
import "react-testing-library/cleanup-after-each";
// this adds jest-dom's custom assertions
import "jest-dom/extend-expect";
configure({ adapter: new Adapter() });
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
here if I do not import jest from jest or jest-dom or jest-enzyme, ESLinter shows an error in jest.fn() that jest is not defined. when I import and run yarn test I get
$ react-scripts test --env=jsdom
FAIL src\App.test.js
● Test suite failed to run
TypeError: _jest2.default.fn is not a function
at Object.<anonymous> (src/setupTests.js:15:27)
at <anonymous>
I really dont get from QAs here and in other forums how should I setup localStorage for testing.
You should not need to import jest into your test files.
You should instead tell eslint what Environment it should expect in the file so it knows what globals are present.
Add this to the top of any files where you use jest globals
/* eslint-env jest */
I really do not know what exactly happened, some packages related to jest, jet-dom, jest-enzyme was mkaing a conflict. but I managed to make it work after deleting package-lock.json, yarn.lock, node_modules, removing jest from the dependencies in package.json, then doing npm install and yarn install!
Does axios-mock-adapter only work on requests made with axios?
I have written a component that POSTs to an API (using vanilla XHR, not axios). I'm testing it in Storybook and want to intercept those POST requests since the endpoint doesn't exist yet:
import React from "react"
import { storiesOf } from "#kadira/storybook"
import MyComponent from "./MyComponent"
import axios from "axios"
import MockAdapter from "axios-mock-adapter"
var mock = new MockAdapter(axios)
storiesOf("My Component", module).addWithInfo(
"Simulator",
() => {
mock.onPost().reply(500)
return <MyComponent />
},
{}
)
My component still is trying to hit the API endpoint and I am getting a 404 response - not the expected 500 response.
Does axios-mock-adapter only work on requests made with axios?
Does the mock call have to be inside MyComponent?
Thanks.
xhr-mock should work for local testing where you probably don't want to make requests across the internet.
Outside of testing, if you are waiting on the real endpoints to be built you could use Mock/it (https://mockit.io) in development. You can claim your own dedicated subdomain and swap it out later for the real one. Disclaimer: this is a side project I recently released and would love any feedback on it!
You can use xhr-mock instead of axios-mock-adapter.
Utility for mocking XMLHttpRequest.
Great for testing. Great for prototyping while your backend is still being built.
Works in NodeJS and in the browser. Is compatible with Axios, jQuery, Superagent >and probably every other library built on XMLHttpRequest
import mock from 'xhr-mock';
storiesOf("My Component", module).addWithInfo("Simulator",
() => {
mock.post('', {
status: 500,
body: '{}'
});
return <MyComponent />
},
{}
)
Additionaly you need to add jquery script in preview-head.html file in storybook
1) https://www.npmjs.com/package/xhr-mock
I've started using json-server to intercept API calls. You have to start it in one tab, and start storybook in another, but it is pretty cool.
You can use fetchMock npm module. All XHR call will be mocked with data you provide.
Storybook configuration:
import React from 'react';
import Messages from '../components/messagesList';
import fetchMock from "fetch-mock";
import MESSAGES from './data/messages';
fetchMock.get('/messages', MESSAGES);
export default {
title: 'Messages',
component: Messages
};
export const ToStorybook = () => <Messages />;
ToStorybook.story = {
name: 'Messages list',
};
The complete tutorial how to do it is on YouTube
You can use storybook-addon-mock to mock any fetch or XHR request using the addon panel.
This package supports
Modify response from the panel and test on the fly.
Modify the status code to verify the error response.
Add a delay time to experience the loading state.
I'm building unit tests with JestJS (npm jest-cli) and need to validate that a ReactJS element contains the CSS styles that I'm looking for.
I tried to check
it('should highlight the selected option in the drop-down list', function() {
var iconTriangleDown = TestUtils.findRenderedDOMComponentWithClass(dropList, 'icon-triangle-down');
var iconHeight = window.getComputedStyle(iconTriangleDown.getDOMNode(), null).height;
expect(iconHeight).notToEqual('');
});
That results in
iconHeight === ''
instead of a value of pixels.
I wonder if window is being mocked by Jest. Or if window isn't supported.
This is fairly easy using jest-dom and react-testing-library.
Tiny example:
component.js
const Component = () => <div style={{left: '4rem'}}>Left component</div>;
component.test.js
test("left shoud be 4rem", () => {
const { getByText } = render(<Component />);
expect(getByText(/left component/i).parentElement).toHaveStyle(`left: 4rem`);
})
To anyone finding this thread, Jest Enzyme now has a assert to test Styles: toHaveStyle: https://github.com/blainekasten/enzyme-matchers/blob/master/README.md#tohavestylestylekeystring-stylevalueany
The problem for me was that from seeing the code, is only testing objects and I have many styles which are arrays (I am using React Native BTW) so it was not working for me.
I am doing this method to test for a particular style:
const render = wrapper.dive();
expect(render.find("[testID='bannerStamp']").get(0).props.style[0].right).toBe(7);
You could test styles though snapshot tests, but Jest does not support evaluating component styles through assertions—that is to say through expect.
In order to do this, you need to combine Jest with enzyme, chai, and chai-enzyme.
This combination will allow you to write tests like this simple example:
it('should render style', () => {
chai.expect(shallow(
<div
style={{
left: '4rem'
}}
/>
)).to.have.style('left', '4rem');
});
First, create a setup file and add it to the the jest.setupFiles array in package.json. See the Jest documentation for an overview of this option.
This is my setup file:
// test/setup.js
import React from 'react';
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
import { shallow, render, mount } from 'enzyme';
// Add commonly used methods and objects as globals
global.chai = chai;
global.mount = mount;
global.React = React;
global.render = render;
global.shallow = shallow;
chai.use(chaiEnzyme());
This is my package.json:
{
"jest": {
"setupFiles": [
"./test/setup.js"
]
}
}
Now, when necessary, you are able to access the Chai assertions API through chai.expect and the Jest assertions API through expect.