Refs are null in Jest snapshot tests with react-test-renderer - javascript

Currently I am manually initializing Quill editor on componentDidMount and jest tests fail for me. Looks like ref value that I am getting is null in jsdom. There is and issue here: https://github.com/facebook/react/issues/7371 but looks like refs should work. Any ideas what I should check?
Component:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
componentDidMount() {
console.log(this._p)
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro" ref={(c) => { this._p = c }}>
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
Test:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import renderer from 'react-test-renderer'
it('snapshot testing', () => {
const tree = renderer.create(
<App />
).toJSON()
expect(tree).toMatchSnapshot()
})
As a result, console.log outputs null. But I would expect P tag

Since test renderer is not coupled to React DOM, it doesn't know anything about what refs are supposed to look like. React 15.4.0 adds the ability to mock refs for test renderer but you should provide those mocks yourself. React 15.4.0 release notes include an example of doing so.
import React from 'react';
import App from './App';
import renderer from 'react-test-renderer';
function createNodeMock(element) {
if (element.type === 'p') {
// This is your fake DOM node for <p>.
// Feel free to add any stub methods, e.g. focus() or any
// other methods necessary to prevent crashes in your components.
return {};
}
// You can return any object from this method for any type of DOM component.
// React will use it as a ref instead of a DOM node when snapshot testing.
return null;
}
it('renders correctly', () => {
const options = {createNodeMock};
// Don't forget to pass the options object!
const tree = renderer.create(<App />, options);
expect(tree).toMatchSnapshot();
});
Note that it only works with React 15.4.0 and higher.

I used Enzyme-based test from this repo to solve this issue like that:
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
describe('< SomeComponent />', () => {
it('renders', () => {
const wrapper = shallow(<SomeComponent />);
expect(toJson(wrapper)).toMatchSnapshot();
});
});

Related

Storybook StoryRouter story is marked as required

I am using React with Storybook. One of my components uses Link and react need that any Link component should be wrapped in Router, that is why I am using the npm module StoryRouter. Everything works fine but I get one alert on the console.
// simple ListItem.tsx
import React from 'react';
import { Link } from 'react-router-dom';
export const ListItem = () => {
return (
<Link to={{pathname:`/page/1`}}>
go to page
</Link>
);
}
// ListItem.stories.tsx
import * as React from 'react';
import { storiesOf } from '#storybook/react';
import StoryRouter from 'storybook-react-router';
import { ListItem } from "./ListItem";
let props = {
text:"Introduction to limits",
}
storiesOf("ListItem", module)
.addDecorator(StoryRouter()) // this causes the alert
.add("default", () => <ListItem {...props} />)
And when I view the component on getStorybook, there is a message on the console
Warning: Failed prop type: The prop `story` is marked as required in `StoryRouter`, but its value is `undefined`.
in StoryRouter (created by storyFn)
in storyFn
in ErrorBoundar
Upgrade storybook-react-router to 1.0.8. This bug has been fixed. See: https://github.com/gvaldambrini/storybook-router/issues/43

Jest expected mock not called (redux component)

In React, I am testing that a button click inside a child component causes a function to be called in the parent component (onDeleteClick), via event bubbling.
For this test, I am using mount, as shallow will not allow us to trigger a function in a child component.
onDeleteClick, the function I am trying to check whether it was called or not, is a class property which in this case, is an arrow function.
I am mocking the onDeleteClick function, and passing it into my component via a Redux Provider when starting the test.
The problem I am having is that at the end of the test, when I perform a check to see if the mocked function was called, it returns 0.
expect(onDeleteClick.mock.calls.length).toBe(1);
If I put a console.log within onDeleteClick(), it's outputted during the test, so I know that the function is in fact being called.
I have researched this quite a bit and so far haven't gotten anything to work.
Some suggestions were to spy on my mocked function, and then call forceUpdate on the wrapper, but this didn't yield any positive results.
For this, I am using Jest with Enzyme.
Reference Code:
Parent.js
import { deleteAccount } from '../../actions/profileActions';
import ChildComponent from '../common/ChildComponent';
class ParentComponent extends Component {
onDeleteClick = () => {
console.log('onDeleteClick was executed during this test!')
this.props.deleteAccount();
}
render() {
let dashboardContent;
dashboardContent = (
<div>
<ChildComponent onDelete={this.onDeleteClick} />
</div>
);
return (
<div>
{dashboardContent}
</div>
);
}
}
// propTypes and mapStateToProps removed from this post
export default connect(
mapStateToProps,
{ deleteAccount }
)(ParentComponent);
__tests__/ParentComponent.js
import React from 'react';
import { mount } from 'enzyme';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import ParentComponent from '../ParentComponent';
import thunk from "redux-thunk";
const mockStore = configureStore([thunk]);
const deleteAccount = jest.fn();
const props = {
deleteAccount
}
const randomTestState = {
// some initial state, not important
};
const randomTestStore = mockStore(randomTestState);
describe('<ParentComponent />', () => {
it(`mounts the ParentComponent component and, when ChildComponent sends onDelete, then deleteAccount function is called once`, () => {
const wrapper = mount(
<Provider store={randomTestStore} props={props}>
<Router >
<ParentComponent />
</Router>
</Provider>
);
// Here, I grab an element in ChildComponent and simulate a click using Enzyme, then the event bubbles up, and deleteAccount() is called in the parent component.
// the console.log we expect to see from onDeleteClick is logged to console.
// the call does not seem to have registered though and the expect returns falsy
expect(deleteAccount.mock.calls.length).toBe(1);
})
});
Could the problem be that I am wrapping the component in a Provider?
I have a hunch, but I couldn't find any concrete examples of tests which use a Provider to wrap their component when running integration testing
The solution was that I needed to change my main ParentComponent file from
class ParentComponent extends Component {
to this:
extend class ParentComponent extends Component {
and then in my test file, import the component like so:
import { ParentComponent } from '../ParentComponent'; // non-default export should be wrapped in braces
and then update my test so that I assign the wrapper variable like so:
const wrapper = mount(<ParentComponent {...props} />);
This then allowed the test to pass
expect(deleteAccount.mock.calls.length).toBe(1);
It was recommended here in the Redux docs

styled-components - test createGlobalStyle

How is it possible to make snapshot tests with styled-components createGlobalStyle?
Tests are running with jest v22.4.4, and styled-components v4.1.2, react v16.7 and jest-styled-components v5.0.1 and react-test-renderer v16.6.3
The output of the snapshot is without the css. but I need a way to test if the css had changes...
E.g.
const BaseCSS = createGlobalStyle`
a { color: red };
`;
And a test
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { BaseCSS } from '../src';
test('test if e', () => {
const tree = renderer.create(<div><BaseCSS /> Test</div>).toJSON();
expect(tree).toMatchSnapshot();
});
edit:
The output of the snapshot looks like: (there is no css in the snapshot)
exports[`test if e 1`] = `
<div>
Test
</div>
`;
I found an answer here and it works!
The global style component will "live" in the <head> tag, not inside the <body> so "you should have aimed for the head".
Here is my working example:
import "jest-styled-components";
import React from "react";
import renderer from "react-test-renderer";
import GlobalStyle from "../../src/styles/GlobalStyle.js";
it("should have global style", () => {
renderer.create(<GlobalStyle />);
expect(document.head).toMatchSnapshot();
});

Why doesn't enzymes find() with component props work the same way when using full rendering or shallow rendering?

According to the enzyme documentation for find() for shallow rendering and full rendering (mount), one should be able to look up components using the value of props. This does not seem to work the same way for full and shallow rendering thought I the documentation doesn't seem to explain that there would be any difference expected.
Example component under test
import React, { Component } from 'react';
class Foo extends Component {
render() {
return (
<div>
<h1>Foo</h1>
{this.props.children}
</div>
);
}
}
class Bar extends Component {
render() {
return (<h1>Bar</h1>);
}
}
class FindTest extends Component {
render() {
return (
<div>
<span spanProp="spanValue">Enzyme Find Test</span>
<Foo fooProp="fooValue">
<Bar barProp="barValue" />
</Foo>
</div>
);
}
}
export default FindTest;
export { Foo, Bar };
Example Test File
import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
import FindTest, { Foo, Bar } from 'components/FindTest/FindTest.js';
describe('<FindTest />', () => {
it('can find using props with shallow rendering', () => {
const wrapper = shallow(<FindTest />);
// Passes
expect(wrapper.find({ spanProp: 'spanValue' })).to.have.length(1);
// Passes
expect(wrapper.find({ fooProp: 'fooValue' })).to.have.length(1);
// Passes
expect(wrapper.find({ barProp: 'barValue' })).to.have.length(1);
// Passes
expect(wrapper.find(Foo).find({ barProp: 'barValue' })).to.have.length(1);
});
it('can find using props with mount rendering', () => {
const wrapper = mount(<FindTest />);
//Passes
expect(wrapper.find({ spanProp: 'spanValue' })).to.have.length(1);
// Fails
expect(wrapper.find({ fooProp: 'fooValue' })).to.have.length(1);
// Fails
expect(wrapper.find({ barProp: 'barValue' })).to.have.length(1);
// Fails
expect(wrapper.find(Foo).find({ barProp: 'barValue' })).to.have.length(1);
});
});
in mount mode the test failed,because enzyme using react-addons-test-utils to render component into an visual dom in constructor,and react can't using dynamic properties in an element and the dynamic property will be striped.if you need you must using dynamic proeprty starts with data-,e.g:data-property-value.see:https://facebook.github.io/react/warnings/unknown-prop.html & https://github.com/holi-java/getstarted-react/blob/master/test/13.react-unknown-prop.test.js
renderWithOptions = (node, options) => {
if (options.attachTo) {
return React.render(node, options.attachTo);
}
return TestUtils.renderIntoDocument(node);
};
As of Feb '17 it seems to be a bug.
As #holi-java mentioned in his comments above. The instMatchesObjectProps method for mounted traversal just doesn't return nodes if the node is a react component.
This existing bug was found on the Enzyme project https://github.com/airbnb/enzyme/issues/376 and this PR are out to fix the issues https://github.com/airbnb/enzyme/pull/377

React Native jest test: TypeError: Cannot read property 'unsubscribeFromTopic' of undefined

I'm using react-native-fcm and jest to test my React Native app. I have a pretty basic test, it looks like this:
import 'react-native';
import React from 'react';
import PushController from '../app/PushController';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('works correctly', () => {
const tree = renderer.create(
<PushController />
);
});
And PushController is somewhat large, so here's the interesting parts
import React, { Component } from 'react';
import { AsyncStorage } from 'react-native';
import FCM from 'react-native-fcm';
export default class PushController extends Component {
(...)
componentDidMount() {
if (this.notificationListener) this.notificationListener.remove();
this.notificationListener = FCM.on('notification', (notif) => {
if (!notif.local_notification) {
this.notifyUser(notif.coffee);
}
});
FCM.unsubscribeFromTopic('/topics/coffee');
FCM.subscribeToTopic('/topics/coffee');
}
(...)
However, when running the test I get
__tests__/PushControllerTest.js
● works correctly
TypeError: Cannot read property 'unsubscribeFromTopic' of undefined
at Object.FCM.unsubscribeFromTopic (node_modules/react-native-fcm/index.js:86:15)
at PushController.componentDidMount (app/PushController.js:44:26)
at node_modules/react-test-renderer/lib/ReactCompositeComponent.js:265:25
at measureLifeCyclePerf (node_modules/react-test-renderer/lib/ReactCompositeComponent.js:75:12)
at node_modules/react-test-renderer/lib/ReactCompositeComponent.js:264:11
at CallbackQueue.notifyAll (node_modules/react-test-renderer/lib/CallbackQueue.js:76:22)
at ReactTestReconcileTransaction.ON_DOM_READY_QUEUEING.close (node_modules/react-test-renderer/lib/ReactTestReconcileTransaction.js:36:26)
at ReactTestReconcileTransaction.TransactionImpl.closeAll (node_modules/react-test-renderer/lib/Transaction.js:206:25)
at ReactTestReconcileTransaction.TransactionImpl.perform (node_modules/react-test-renderer/lib/Transaction.js:153:16)
at batchedMountComponentIntoNode (node_modules/react-test-renderer/lib/ReactTestMount.js:69:27)
I've tried including lots of stuff in the test, like jest.mock('react-native-fcm') and others, but I can't get it to work at all. I get that jest automatically mocks the library, but I don't understand why FCM is undefined. Any ideas?
I managed to solve it, finally! Simply needed to change my test to
import 'react-native';
import React from 'react';
import PushController from '../app/PushController';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
import FCM from 'react-native-fcm'; // <-- This
it('works correctly', () => {
FCM.unsubscribeFromTopic = jest.fn(); // <-- These two
FCM.subscribeToTopic = jest.fn();
const tree = renderer.create(
<PushController />
);
});
To make sure the actual calls are mocked. I did a lot of googling before this, so I'm sure this will be useful for someone.

Categories

Resources