import React from 'react'
import toJson from 'enzyme-to-json'
import {ScreensCreateAccount} from './CreateAccount'
describe('Testing CreateAccount Component', () => {
const props = {
auth: {
isAuth: false,
isLoadding: false
}
}
it('should render correctly', () => {
const wrapper = shallow(<ScreensCreateAccount {...props}/>)
expect(toJson(wrapper)).toMatchSnapshot()
})
})
I have this test for my component CreateAccount, and i want to know if is necessary to use expect(toJson(wrapper)).toMatchSnapshot() or using just
expect(wrapper).toMatchSnapshot() is correct too. Both implementation build a folder with snapshots with file CreateAccount.test.js.snap. Whats is the diference?
Enzyme-to-json serializes component, created by Enzyme. If your tests work without it, then you have it already configured in jest configuration file.
If no, you can add this to package.json to work:
"jest": {
"snapshotSerializers": ["enzyme-to-json/serializer"]
}
Related
I'm trying to do a snapshot of a component wrapped by Ionic React router and perform deep rendering. But IonicRouterOutlet doesn't seem to deep render in my case. Any suggestions how to fix this issue and perform a deep rendering successfully?
Here's my sample code:
import React from 'react'
import { render } from '#testing-library/react'
import { IonRouterOutlet } from '#ionic/react'
import { IonReactRouter } from '#ionic/react-router'
jest.mock('#capacitor/core', () => ({
Plugins: {
StatusBar: {
setStyle: jest.fn()
},
Storage: {
set: jest.fn(),
get: () => ""
}
},
StatusBarStyle: {
Light: "LIGHT"
},
}));
describe('component', () => {
let component
beforeEach(async () => {
component = render(
<IonReactRouter>
<IonRouterOutlet>
<div />
</IonRouterOutlet>
</IonReactRouter>
)
})
describe('snapshot', () => {
it('should match snapshot', () => {
const { asFragment } = component
expect(asFragment()).toMatchSnapshot()
})
})
})
Here's my snapshot output:
exports[`Login component snapshot should match snapshot 1`] = `
<DocumentFragment>
<ion-router-outlet />
</DocumentFragment>
`;
As you can see, the <div /> is not generated in snapshot output. So how to fix this problem?
I have found this documentation which i think would be helpful https://github.com/crossroads/app.goodchat.hk/wiki/Testing-Routing
Basically, as you have found using IonReactRouter & IonRouterOutlet causes the JSDOM to output <ion-router-outlet /> and not the full tree.
The workaround is to use React Router's Router and MemoryRouter directly, depending on what you want to access e.g history.location.pathname. IonReactRouter uses React Router under the hood so the behaviour and JSDOM output should match.
I'm trying to test a simple component, that looks like this
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styles from './styles.css'
export class App extends PureComponent {
handleClick = (event) => {
const { loadGreetings } = this.props
loadGreetings()
}
render () {
const { hi } = this.props
return (
<section>
<h1 className={styles.earlyDawn}>{hi}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
)
}
}
App.propTypes = {
hi: PropTypes.string,
loadGreetings: PropTypes.func
}
export default App
Here is my test file
import React from 'react'
import {App} from './index'
import {shallow} from 'Enzyme'
describe('Testing App container...', () => {
let props
beforeEach(() => {
props = {
loadGreetings: jest.fn().mockName('loadGreetings'),
hi: 'Hi from test'
}
})
test('should handle click on the button', () => {
const wrapper = shallow(<App {...props}/>)
const buttonHi = wrapper.find('button')
const instance = wrapper.instance()
expect(buttonHi.length).toBe(1)
jest.spyOn(instance, 'handleClick')
buttonHi.simulate('click')
expect(props.loadGreetings).toHaveBeenCalled()
expect(instance.handleClick).toHaveBeenCalled()
})
})
So the problem is in the second toHaveBeenCalled assertion that fails all the time. However, first toHaveBeenCalled seems to be working, which bothers me, because props.loadGreetings is called inside instance.handleClick. Could you please help me to find what may be the problem?
Dependencies: "react": "16.9.0", "react-dom": "16.9.0", "babel-jest": "^24.8.0", "enzyme": "^3.10.0", "jest": "^24.8.0",
A simpler approach would be to pass in some initial props and test your component based upon those initial props -- you'll also manipulate those props to add more assertions.
Here's a working example (click on the Tests tab to run the tests):
components/App/index.js
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
class App extends PureComponent {
handleClick = () => {
this.props.loadGreetings();
};
render() {
const { message } = this.props;
return (
<section className="app">
<h1>{message}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
);
}
}
App.propTypes = {
message: PropTypes.string,
loadGreetings: PropTypes.func
};
export default App;
components/App/App.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "./index";
// define the passed in function here for easier testing below
const loadGreetings = jest.fn();
// initial props to pass into 'App'
const initProps = {
message: "hi",
loadGreetings
};
describe("Testing App container...", () => {
let wrapper;
beforeEach(() => {
// this resets the wrapper with initial props defined above
wrapper = shallow(<App {...initProps} />);
});
afterEach(() => {
// this clears any calls to the mocked function
// and thereby resets it
loadGreetings.mockClear();
});
it("renders without errors", () => {
expect(wrapper.find(".app").exists()).toBeTruthy();
expect(loadGreetings).toHaveBeenCalledTimes(0);
});
it("initially renders a 'hi' message and then a 'goodbye' message", () => {
expect(wrapper.find("h1").text()).toEqual("hi");
// manipulating the initial 'message' prop
wrapper.setProps({ message: "goodbye" });
expect(wrapper.find("h1").text()).toEqual("goodbye");
});
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
// since we passed in 'loadGreetings' as a jest function
// we expect it to be called when the 'Handshake' button is
// clicked
wrapper.find("button").simulate("click");
expect(loadGreetings).toHaveBeenCalledTimes(1);
});
});
Not recommended (see below), but you can spy on the class method -- you'll have to work with instances, forceUpdate the instance, then invoke the handleClick method either manually, wrapper.instance().handleClick(), or indirectly via some element's event handler: wrapper.find("button").simulate("click") or wrapper.find("button").props().onClick().
The reason I don't recommend this testing strategy is that you're testing a React implementation (testing whether or not the element's event handler invokes your callback function). Instead, you can avoid that by asserting against whether or not a prop function is called and/or a prop/state change happens. This is a more standard and direct approach to testing the component -- as that is what we care about; we care that the props and/or state changes based upon some user action. In other words, by making assertions against the 'loadGreetings' prop being called we're already testing that the onClick event handler works.
Working example:
App.test.js (same testing as above, with the exception of this test):
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
const spy = jest.spyOn(wrapper.instance(), "handleClick"); // spying on the method class
wrapper.instance().forceUpdate(); // required to ensure the spy is placed on the method
wrapper.find("button").simulate("click");
expect(spy).toHaveBeenCalledTimes(1);
const mockedFn = jest.fn(); // setting the method as a mocked fn()
wrapper.instance().handleClick = mockedFn;
wrapper.instance().forceUpdate(); // required to update the method instance with a mocked fn
wrapper.find("button").simulate("click");
expect(mockedFn).toHaveBeenCalledTimes(1);
});
The path to my styled objects is correct, however not sure why I'm getting the following error:
Cannot find module '../../shared/models' from 'Astronaut.tsx'
import { moonHoldings } from '../../shared/models';
My simple Jest test:
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
// #ts-ignore (works with .tsx)
import Astronaut from '../Astronaut.tsx';
describe('<Astronaut /> component', () => {
describe('should render', () => {
const wrapper = shallow(<Astronaut showLogo={true} />);
it ('should render a component matching the snapshot', () => {
const tree = toJson(wrapper);
expect(tree).toMatchSnapshot();
expect(wrapper).toHaveLength(1);
});
});
});
The Astronaut component
import React from 'react';
import { moonHoldings } from '../../shared/models'; // <-- The problem
import { astronaut } from '../../styles'; // <-- This works
const { AstronautContainer, Heading } = astronaut;
interface LogoCheck {
showLogo: boolean;
}
export default (showLogo: LogoCheck) => (
<AstronautContainer>
{ showLogo.showLogo === true ? <Heading>{moonHoldings}</Heading> : null }
<img src="static/astronaut.png" alt="astronaut" />
</AstronautContainer>
);
The Jest config section of my Package.json
"jest": {
"setupTestFrameworkScriptFile": "<rootDir>/jest.setup.js",
"testPathIgnorePatterns": [
"<rootDir>/.next/",
"<rootDir>/node_modules/"
],
"transform": {
"\\.(gql|graphql)$": "jest-transform-graphql",
".*": "babel-jest",
"^.+\\.js?$": "babel-jest"
},
"moduleFileExtensions": [
"js",
"json",
"ts",
"tsx"
],
"modulePaths": [
"<rootDir>/components/",
"<rootDir>/pages/",
"<rootDir>/shared/"
]
}
And my folder structure:
You need to do little configuration for your jest test.
Adding this snippet to your package.json should allow you to take your custom name and map it to your actual folder:
"moduleNameMapper": {
"^#fooBar/(.*)": "<rootDir>/src/barFoo/$1"
},
Ok I just fixed this by created an index file inside of the /shared folder and then exporting out the models that way (Though it should have still worked without the index file):
import { moonHoldings } from '../../shared';
And the index.js:
export { moonHoldings, nomicsLink } from './copy';
In my case, I got this error in a lot of instances inside my test file and the solution was to use file paths based mapping in the moduleNameMapper option inside my jest.config file. This helps us to map our absolute paths with their corresponding relative ones.
For example, if you are getting this error -
● Test suite failed to run
Cannot find module 'jsroot/io' from 'src/some-file.ts'
Adding this should work -
moduleNameMapper: {
// 'absolute-path': 'relative-path'
'jsroot/io': '<rootDir>/node_modules/jsroot/',
},
where <rootDir> is the root of the directory containing your Jest config file or the package.json.
import React from 'react'
import sinon from 'sinon'
import { mount } from 'enzyme'
import configureStore from 'redux-mock-store'
import FileListEditable from './index.js'
import {shallow} from 'enzyme'
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
customer: {
clientId:'123'
}
}
const store = mockStore(initialState)
const minProps = {
files: []
}
const removeFile = sinon.spy()
const wrapper = shallow(
<FileListEditable
store={store}
{...minProps}
removeFile={removeFile} />,
{context: {store}})
test.skip('Component: <FileListEditable/>, renders', () => {
expect(wrapper.length).toBe(1)
expect(wrapper.find('Tag').length).toBe(0)
})
test.skip('Component <FileListEditable/>, Add and remove files', () => {
wrapper.setProps({
files: [
{
name: 'file1',
extension: 'txt'
},
{
name: 'file2',
extension: 'txt'
}
]
})
expect(wrapper.find('Tag').length).toBe(2)
wrapper.find('Tag').at(0).find('button').simulate('click')
expect(removeFile.called).toBe(true)
expect(removeFile.args[0][0]).toBe(0)
wrapper.find('Tag').at(1).find('button').simulate('click')
expect(removeFile.args[1][0]).toBe(1)
})
test.skip('Component <FileListEditable/>, File from documents will have link to that document', () => {
wrapper.setProps({
files: [
{
name: 'file1',
extension: 'txt',
id: 'file-document-id'
},
{
name: 'file2',
extension: 'txt'
}
]
})
expect(wrapper.find('Tag').at(0).find('a').length).toBe(1)
expect(wrapper.find('Tag').at(1).find('a').length).toBe(0)
})
These tests do not work because FileListEditable is wrapped with injectIntl and one of our own created higher order component. Which means when I use shallow rendering it will render the InjectIntl component and if I use mount I have to dive two layers. But I just can't seem to get it right. Is there a general solution for testing components that are wrapped with higher order components without having to care about the higher order component?
Thank you Daniel Lizik for sharing the link
https://github.com/airbnb/enzyme/issues/98#issuecomment-169761955
cited from the link:
Internally at Airbnb, we use a pattern like the following:
class MyComponent extends React.Component {
...
}
export default connect(MyComponent); // default export. used in your app.
export { MyComponent as PureMyComponent}; // pure component. used in tests
This will work fine with redux's connect function, but won't work out of the box with the decorator syntax. You could open a pull request to redux to have the #connect decorator expose the underlying wrapped component as a static prop like UnderlyingComponent or something like that
Does that help at all?
I'm trying to figure out how to test an "onPress" event with Jest in a React-Native app so I can make sure the right function is called.
I went through the documentation and Google but couldn't find a solution for it in React-Native.
This is what I found that is supposed to work for React-Native with enzyme:
const mockFunc = jest.fn();
const component = mount(<MyComponent onPress={mockFunc} />);
component.simulate('press');
expect(mockFunc).toHaveBeenCalled();
But this doesn't work. Seems like mount doesn't work and I get this output:
ReferenceError: document is not defined
I tried with shallow instead but the TouchableOpacity is not getting rendered when I look at the output of the function... and you've guessed it, it doesn't work either. Not sure what to do.
Does anyone found a way to test events on React-Native?
Thanks
Enzyme does not support React-Native, because it's rendered differently and doesn't use the DOM. That's why you're getting the error ReferenceError: document is not defined. You can see this issue for more information. The React team is currently working to expose a .find() method in react-test-renderer to simulate actions on components. Then it should work for both React/React-native without needing a DOM environment.
There's a hack you can do (and that's what we did in our company) that is rendering a custom component that extends TouchableOpacity and map onClick to call onPress. Something like this:
const mockPressable = (name) => {
const RealComponent = require.requireActual(name);
class Component extends RealComponent {
render() {
return React.createElement(
RealComponent.displayName || RealComponent.name,
{ ...this.props, onClick: this.props.onPress },
this.props.children
);
}
}
return Component;
};
jest.mock('TouchableOpacity', () => mockPressable('TouchableOpacity'));
And in your test code, you call component.simulate('click').
It's a hack and I'm not sure what are the consequences of doing this but it has worked for our use cases.
You should use shallow instead, then called .dive()
const mockFunc = jest.fn();
const component = shallow(<MyComponent onPress={mockFunc} />);
component.dive().simulate('press');
expect(mockFunc).toHaveBeenCalled();
I'm able to run tests like what you've described in your question in React Native. Here is my configuration:
package.json
"scripts": {
...
"test": "node_modules/jest/bin/jest.js",
}
"devDependencies": {
...
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.1",
"enzyme-to-json": "^3.1.2",
"jest": "^21.2.1",
"jest-enzyme": "^4.0.0",
"jest-expo": "~21.0.0",
}
"jest": {
"preset": "jest-expo",
"setupFiles": [
"./test/jestSetup.js"
],
"snapshotSerializers": [
"./node_modules/enzyme-to-json/serializer"
]
}
test/jestSetup.js
import { configure, shallow, render, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
configure( { adapter: new Adapter() } )
// enzyme
global.shallow = shallow
global.render = render
global.mount = mount
Example component:
import React from 'react'
import { Button } from 'react-native'
const CancelButton = ( props ) =>
<Button
{ ...props }
onPress={ () => { props.navigation.goBack() } }
title="Cancel"
/>
export { CancelButton }
Example test
import React from 'react'
import { CancelButton } from '../CancelButton'
test( 'onPress', () => {
const goBackFunc = jest.fn()
const navigation = {
goBack: goBackFunc,
}
const component = shallow(
<CancelButton
navigation={ navigation }
/>
)
component.simulate( 'press' )
expect( goBackFunc ).toHaveBeenCalled()
} )
.babelrc
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
}
}