How to test decorated React component with shallow rendering - javascript

I am following this tutorial: http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/
Trying to learn how "shallow rendering" works.
I have a higher order component:
import React from 'react';
function withMUI(ComposedComponent) {
return class withMUI {
render() {
return <ComposedComponent {...this.props}/>;
}
};
}
and a component:
#withMUI
class PlayerProfile extends React.Component {
render() {
const { name, avatar } = this.props;
return (
<div className="player-profile">
<div className='profile-name'>{name}</div>
<div>
<Avatar src={avatar}/>
</div>
</div>
);
}
}
and a test:
describe('PlayerProfile component - testing with shallow rendering', () => {
beforeEach(function() {
let {TestUtils} = React.addons;
this.TestUtils = TestUtils;
this.renderer = TestUtils.createRenderer();
this.renderer.render(<PlayerProfile name='user'
avatar='avatar'/>);
});
it('renders an Avatar', function() {
let result = this.renderer.getRenderOutput();
console.log(result);
expect(result.type).to.equal(PlayerProfile);
});
});
The result variable holds this.renderer.getRenderOutput()
In the tutorial the result.type is tested like:
expect(result.type).toEqual('div');
in my case, if I log the result it is:
LOG: Object{type: function PlayerProfile() {..}, .. }
so I changed my test like:
expect(result.type).toEqual(PlayerProfile)
now it gives me this error:
Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]
So PlayerProfile's type is the higher order function withMUI.
PlayerProfile decorated with withMUI, using shallow rendering, only the PlayerProfile component is rendered and not it's children. So shallow rendering wouldn't work with decorated components I assume.
My question is:
Why in the tutorial result.type is expected to be a div, but in my case isn't.
How can I test a React component decorated with higher order component using shallow rendering?

You can't. First let's slightly desugar the decorator:
let PlayerProfile = withMUI(
class PlayerProfile extends React.Component {
// ...
}
);
withMUI returns a different class, so the PlayerProfile class only exists in withMUI's closure.
This is here's a simplified version:
var withMUI = function(arg){ return null };
var PlayerProfile = withMUI({functionIWantToTest: ...});
You pass the value to the function, it doesn't give it back, you don't have the value.
The solution? Hold a reference to it.
// no decorator here
class PlayerProfile extends React.Component {
// ...
}
Then we can export both the wrapped and unwrapped versions of the component:
// this must be after the class is declared, unfortunately
export default withMUI(PlayerProfile);
export let undecorated = PlayerProfile;
The normal code using this component doesn't change, but your tests will use this:
import {undecorated as PlayerProfile} from '../src/PlayerProfile';
The alternative is to mock the withMUI function to be (x) => x (the identity function). This may cause weird side effects and needs to be done from the testing side, so your tests and source could fall out of sync as decorators are added.
Not using decorators seems like the safe option here.

Use Enzyme to test higher order / decorators with Shallow
with a method called dive()
Follow this link, to see how dive works
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md
So you can shallow the component with higher order and then dive inside.
In the above example :
const wrapper=shallow(<PlayerProfile name={name} avatar={}/>)
expect(wrapper.find("PlayerProfile").dive().find(".player-profile").length).toBe(1)
Similarly you can access the properties and test it.

You can use 'babel-plugin-remove-decorators' plugin. This solution will let you write your components normally without exporting decorated and un-decorated components.
Install the plugin first, then create a file with the following content, let us call it 'babelTestingHook.js'
require('babel/register')({
'stage': 2,
'optional': [
'es7.classProperties',
'es7.decorators',
// or Whatever configs you have
.....
],
'plugins': ['babel-plugin-remove-decorators:before']
});
and running your tests like below will ignore the decorators and you will be able to test the components normally
mocha ./tests/**/*.spec.js --require ./babelTestingHook.js --recursive

I think the above example is confusing because the decorator concept is used interchangeably with idea of a "higher order component". I generally use them in combination which will make testing/rewire/mocking easier.
I would use decorator to:
Provide props to a child component, generally to bind/listen to a flux store
Where as I would use a higher order component
to bind context in a more declarative way
The problem with rewiring is I don't think you can rewire anything that is applied outside of the exported function/class, which is the case for a decorator.
If you wanted to use a combo of decorators and higher order components you could do something like the following:
//withMui-decorator.jsx
function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///bind here based on some getter
};
}
render() {
return <ComposedComponent {...this.props} {...this.state} {...this.context} />;
}
};
}
//higher-order.jsx
export default function(ChildComp) {
#withMui //provide store bindings
return class HOC extends Component {
static childContextTypes = {
getAvatar: PropTypes.func
};
getChildContext() {
let {store1} = this.props;
return {
getAvatar: (id) => ({ avatar: store1[id] });
};
}
}
}
//child.js
export default Child extends Component {
static contextTypes = {
getAvatar: PropTypes.func.isRequired
};
handleClick(id, e) {
let {getAvatar} = this.context;
getAvatar(`user_${id}`);
}
render() {
let buttons = [1,2,3].map((id) => {
return <button type="text" onClick={this.handleClick.bind(this, id)}>Click Me</button>
});
return <div>{buttons}</div>;
}
}
//index.jsx
import HOC from './higher-order';
import Child from './child';
let MyComponent = HOC(Child);
React.render(<MyComponent {...anyProps} />, document.body);
Then when you want to test you can easily "rewire" your stores supplied from the decorator because the decorator is inside of the exported higher order component;
//spec.js
import HOC from 'higher-order-component';
import Child from 'child';
describe('rewire the state', () => {
let mockedMuiDecorator = function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///mock that state here to be passed as props
};
}
render() {
//....
}
}
}
HOC.__Rewire__('withMui', mockedMuiDecorator);
let MyComponent = HOC(Child);
let child = TestUtils.renderIntoDocument(
<MyComponent {...mockedProps} />
);
let childElem = React.findDOMNode(child);
let buttons = childElem.querySelectorAll('button');
it('Should render 3 buttons', () => {
expect(buttons.length).to.equal(3);
});
});
I'm pretty sure this doesn't really answer your original question but I think you are having problems reconciling when to use decorators vs.higher order components.
some good resources are here:
http://jaysoo.ca/2015/06/09/react-contexts-and-dependency-injection/
https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750
https://github.com/badsyntax/react-seed/blob/master/app/components/Menu/tests/Menu-test.jsx
https://github.com/Yomguithereal/baobab-react/blob/master/test/suites/higher-order.jsx

In my case decorators are very useful and I dont want to get rid of them (or return wrapped and unwrapped versions) im my application.
The best way to do this in my opinion is to use the babel-plugin-remove-decorators (which can be used to remove them in tests) has Qusai says, but I wrote the pre-processor differently like below:
'use strict';
var babel = require('babel-core');
module.exports = {
process: function(src, filename) {
// Ignore files other than .js, .es, .jsx or .es6
if (!babel.canCompile(filename)) {
return '';
}
if (filename.indexOf('node_modules') === -1) {
return babel.transform(src, {
filename: filename,
plugins: ['babel-plugin-remove-decorators:before']
}).code;
}
return src;
}
};
Take notice of the babel.transform call that im passing the babel-plugin-remove-decorators:before element as an array value, see: https://babeljs.io/docs/usage/options/
To hook this up with Jest (which is what I used), you can do it with settings like below in your package.json:
"jest": {
"rootDir": "./src",
"scriptPreprocessor": "../preprocessor.js",
"unmockedModulePathPatterns": [
"fbjs",
"react"
]
},
Where preprocessor.js is the name of the preprocessor.

Related

How do I access app.state from a Cypress test in a Remix project

Cypress has a way to expose the app's state to the test runner -- in React it usually looks like this:
class MyComponent extends React.Component {
constructor (props) {
super(props)
// only expose the app during E2E tests
if (window.Cypress) {
window.app = this
}
}
...
}
Then you could access your state in a test with
cy.window()
.its('app.state')
.should('deep.equal', myStateObject)
However, the setup for Remix projects relies on functional components. I've tried this in my root.tsx component with a useEffect call:
export default function App() {
useEffect(() => {
window.app = App;
}, []}
}
as well as in the root route (routes/index.tsx) by importing the <App /> component and using the logic in the useEffect function above. Neither of these options are working and I'm not sure where else to go here. Remix's GitHub issues are devoid of questions about this issue, so maybe I'm going about this the wrong way. Any help is appreciated! Thanks!
I haven't done much work with Remix, but there is a question here that might be useful:
React - getting a component from a DOM element for debugging.
Note the last paragraph
Function components
Function components don't have "instances" in the same way classes do, so you can't just modify the FindReact function to return an object with forceUpdate, setState, etc. on it for function components.
That said, you can at least obtain the React-fiber node for that path, containing its props, state, and such. To do so, modify the last line of the FindReact function to just: return compFiber;
There's a lib cypress-react-app-actions that implements this for Cypress
export const getReactFiber = (el) => {
const key = Object.keys(el).find((key) => {
return (
key.startsWith('__reactFiber$') || // react 17+
key.startsWith('__reactInternalInstance$') // react <17
)
})
if (!key) {
return
}
return el[key]
}
// react 16+
export const getComponent = (fiber) => {
let parentFiber = fiber.return
while (typeof parentFiber.type == 'string') {
parentFiber = parentFiber.return
}
return parentFiber
}
One of the example tests is
/// <reference types="cypress" />
import { getReactFiber, getComponent } from '../support/utils'
it('calls Example double()', () => {
cy.visit('/')
cy.get('.Example').within(() => { // select via className of component
cy.contains('[data-cy=count]', '0')
cy.get('[data-cy=add]').click().click()
cy.contains('[data-cy=count]', '2')
cy.root().then((el$) => {
const fiber = getReactFiber(el$[0])
console.log(fiber)
const component = getComponent(fiber)
console.log(component.stateNode)
cy.log('calling **double()**')
component.stateNode.double() // work with component for functional
})
cy.contains('[data-cy=count]', '4')
})
})
This example is for class components, but given the info in Function components section above, you would use the component object rather than component.stateNode.

React - Setting component state using a function outside of state, is it wrong?

Is it wrong to use setState in a function outside of the React component?
Example:
// myFunction.js
function myFunction() {
...
this.setState({ ... })
}
// App.js
import myFunction from './myFunction
class App extends Component {
constructor() {
super()
this.myFunction = myFunction.bind(this)
}
...
}
I'm not sure the way you're binding will actually work. You could do something like:
export const getName = (klass) => {
klass.setState({ name: 'Colin'})
}
then
class App extends Component {
state = {
name: 'React'
};
handleClick = () => {
getName(this);
}
render() {
return (
<div>
<p>{this.state.name}</p>
<button onClick={this.handleClick}>change name</button>
</div>
);
}
}
Working example here.
So the only reasons to do this is if you are reducing repeated code, e.g. two components use the same logic before calling this.setState, or if you want to make testing easier by having a separate pure function to test. For this reason I recommend not calling this.setState in your outside function, but rather returning the object you need so it can you can call this.setState on it.
function calculateSomeState(data) {
// ...
return { updated: data };
}
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = calculateSomeState(props.data);
}
handleChange = (e) => {
const value = e.target.value;
this.setState(calculateSomeState({ ...props.data, value }));
}
}
It looks like a bug waiting to happen... If you want to use an external function to set state, you can use the alternative syntax provided by React:
this.setState((prevState, props) => {
return updatedState; //can be a partial state, like in the regular setState
});
That callback can easily be extracted to an external function and it's guaranteed to work
It is not wrong, the function is never called outside the component. This is a mix-in technique. bind isn't needed, as long as the function isn't used as a callback. In this case myFunction is same among all instances, a more efficient way would be:
class App extends Component {}
App.prototype.myFunction = myFunction;

Blacklist React components

Is there a way to define a function to hook before each component in my app is mounted?
The idea is that if a component is blacklisted it doesn't mount at all.
The solution must leave the components unmodified for backward compatibility and should run in production (so rewire and other testing tools are probably off the table but open to suggestions :) )
Example
//something like this...
ReactDOM.beforeEachComponentMount( (component, action) => {
if(isBlacklisted(component)){
action.cancelMountComponent();
}
}
Could you write a simple Babel plugin that transforms blacklisted components to a noop functional component () => {} at compile time?
You could wrap the required components inside a higher order component that checks whether the component is blacklisted or not.
for example :
class YourComponent extends Component {
constructor(props){
super(props);
}
render(){
return(
// your component goes here ..
);
}
}
export default WithPermission(YourComponent);
check if the component needs to be rendered or not inside the HOC WithPermission.
function withPermission(YourComponent) {
class WithPermission extends React.Component {
constructor(props) {
super(props);
}
// you can check the props inside ComponentDidMount and set a flag if
// the component satisfies the criteria for rendering.
render() {
const {blacklistedComponents,...rest} = this.props;
if(!blackListedComponents){
return <YourComponent {...rest} />
}
else{
return null;
}
}
}
}
There is no such functionality out of box.
You may shim React rendering cycle, I mean shim React.createElement method and validate component before it is added to VDOM
All JSX is processed through React.createElement
e.g. at the start of app add
let React = require('react');
let originalCreateElement = React.createElement;
React.createElement = function() {
let componentConstructorOrStringTagName = arguments[0];
if (isBlacklisted(componentConstructorOrStringTagName)) {
return null;
}
return originalCreateElement.apply(this, arguments);
}
The best idea I can think of is to "shim" react and Component
if you are using webpack you can use this:
https://webpack.js.org/guides/shimming/
in the bottom line that means instead of importing react you will import your own class of react.
In your new class you could extend React Component and place a check on the render function or something similar.
You could implement a custom ESLint rule and catch this as soon as a dev tries to use a blacklisted components. The id-blacklist rule is similar to what you want, but at the identifier level. The source code looks simple. Maybe you can adapt it to disallow more then just identifiers.
Consider the following solution:
Let there be a file where you declare which components are blacklisted:
let blacklist = [{
name: 'secretComponent',
invoke: (props)=> {
return <SecretComponent ...props />
},
isBlacklisted: true
},{
name: 'home',
invoke: (props)=> {
return <HomeComponent ...props />
},
isBlacklisted: false
},{
name: 'login',
invoke: (props)=> {
return <LoginComponent ...props />
},
isBlacklisted: false
}];
Define a Higher Order Component like below:
function renderIfNotBlacklisted(name) {
let component = blacklist.map(x=> x.name == name); //blacklist from above
if (!component.isBlacklisted){
return component.invoke();
} //else can be handled as you will
//You can keep a default component to render or send empty values
}
Call this component in the render function wherever you want this feature to work. This way you have a centralized location to managed blacklisted components (blacklist.json can be in the root of react project or fetched from API on first run)

React unit tests with Enzyme don't re-bind context of helper functions

This is an interesting issue that I came across while trying to refactor some of my React components using AirBnB's React testing library, Enzyme.
I think the best way to explain my problem is through an example.
Here is a small React component that will display a message depending on the props it receives from its parent component:
test.js:
import React from 'react';
function renderInnerSpan() {
const {foo} = this.props;
if (foo) {
return <span>Foo is truthy!</span>;
}
return <span>Foo is falsy!</span>;
}
export default class extends React.Component {
render() {
return (
<div>
{renderInnerSpan.call(this)}
</div>
);
}
}
And here is a test suite for this component with two passing tests:
test.spec.js:
import Test from '../../src/test';
import React from 'react';
import {shallow} from 'enzyme';
import {expect} from 'chai';
describe('Test Suite', () => {
let renderedElement,
expectedProps;
function renderComponent() {
const componentElement = React.createElement(Test, expectedProps);
renderedElement = shallow(componentElement);
}
beforeEach(() => {
expectedProps = {
foo: true
};
renderComponent();
});
it('should display the correct message for truthy values', () => {
const span = renderedElement.props().children;
expect(span.props.children).to.equal('Foo is truthy!');
});
it('should display the correct message for falsy values', () => {
expectedProps.foo = false;
renderComponent();
const span = renderedElement.props().children;
expect(span.props.children).to.equal('Foo is falsy!');
});
});
This works fine, but the Test component's current implementation isn't as efficient as it could be. By using .call(this), it is creating a new function every time the render() function is called. I could avoid this by binding the correct context of this in the component's constructor, like so:
export default class extends React.Component {
constructor(props) {
super(props);
renderInnerSpan = renderInnerSpan.bind(this);
}
render() {
return (
<div>
{renderInnerSpan()}
</div>
);
}
}
After this change, the component still works as intended, but the tests start failing:
AssertionError: expected 'Foo is truthy!' to equal 'Foo is falsy!'
Expected :Foo is falsy!
Actual :Foo is truthy!
I added a console.log(props.foo) in the constructor, which confirmed that the constructor was still being called when I expected it to, and the props it's receiving are correct. However, I added a console.log(foo) inside of renderInnerSpan, and it looks like the value is true all the time, even after re-rendering the component with its foo prop explicitly set to false.
It looks like renderInnerSpan is only be bound once, and Enzyme is re-using this for every single test. So, what gives? I'm re-creating my component in the test, which is calling its constructor with the values I expect - why is my bound renderInnerSpan function continuing to use old values?
Thanks in advance for the help.
The issue here is that a function cannot be bound multiple times, as you are trying to in your test case.
The reason for that is that the context is not simply a property of the function itself. When a function is bound, it is rather wrapped in a bound function exotic object.
The context (this-assignment) is saved in the [[BoundThis]] property of the exotic object. The bound function will always be called with this context, even if it is bound again.
You can test this yourself:
function foo() {
console.log(this.bar);
}
foo(); // undefined
foo = foo.bind({bar: 1});
foo(); // 1
foo = foo.bind({bar: 2});
foo(); // 1
To solve this issue, I suggest you remove the dependency to the context from the rendering function and transfer all required input via function parameters instead:
function renderInnerSpan(foo) {
if (foo) {
return <span>Foo is truthy!</span>;
}
return <span>Foo is falsy!</span>;
}
export default class extends React.Component {
render() {
return (
<div>
{renderInnerSpan(this.props.foo)}
</div>
);
}
}
This removes a hidden dependency and makes the code more readable and maintainable. If you ever decide to move the rendering function to its own module, you can now easily do so.
Since you do not need to bind the function context in the constructor anymore, you can even transform your React component into a stateless function:
import renderInnerSpan from './renderInnerSpan'
export default (props) => (
<div>
{renderInnerSpan(props.foo)}
</div>
);
So much nicer and more readable! :-)
It looks to me that you defined the renderInnerSpan function outside your class and that might create some problems.
Try this:
import React from 'react';
export default class extends React.Component {
render() {
return (
<div>
{this.renderInnerSpan.bind(this)}
</div>
);
}
renderInnerSpan() {
const {foo} = this.props;
if (foo) {
return <span>Foo is truthy!</span>;
}
return <span>Foo is falsy!</span>;
}
}
Another thing is that your renderComponent function could be rewritten like this:
function renderComponent(expectedProps) {
const componentElement = React.createElement(Test, expectedProps);
return shallow(componentElement);
}
And if you are changing the props in each test, then there is no reason to set the props in a beforeEach block. Just use the new renderComponent in each test instead.
it('should display the correct message for truthy values', () => {
renderedElement = renderComponent({foo: true});
const span = renderedElement.props().children;
expect(span.props.children).to.equal('Foo is truthy!');
});

Decorate react component to add lifecycle methods

I am trying to create a decorator method which will add some default lifecycle methods into the react component. My objective is to add some default functionality into the component, for example all component should be able to do a specific thing on componentWillMount.
I read a couple of articles and found this. It can be used to add new props to the react components.
export default function context(contextTypes, context) {
return function (DecoratedComponent) {
return class {
static childContextTypes = contextTypes;
getChildContext() {
return context;
}
render() {
return (
<DecoratedComponent {...this.props} />
);
}
}
}
}
But I am not sure how would I add class methods like componentWillMount. Can I do something like
Object.assign(DecoratedComponent.prototype, {
componentWillMount: () => {
// do something
}
})
Any idea towards right direction?
Refs:
http://asaf.github.io/blog/2015/06/23/extending-behavior-of-react-components-by-es6-decorators/
https://gist.github.com/motiz88/3db323f018975efce575
If you're using Babel with the stage 1 or stage 0 preset, you can use the following method:
First, define your decorator function, e.g.:
function lifecycleDefaults(target) {
target.prototype.componentWillMount = function() {
console.log('componentWillMount ran from decorator!');
console.log('this.props is still accessible', this.props);
}
target.prototype.componentWillUnmount = function() {
console.log('componentWillUnmount ran from decorator!');
console.log('this.props is still accessible', this.props);
}
target.prototype.componentDidMount = function() {
console.log('componentDidMount ran from decorator!');
console.log('this.props is still accessible', this.props);
}
}
Following that, decorate a component using the function you just defined e.g.:
#lifecycleDefaults
export class Page extends React.Component {
render() {
return (
<div>Hello decorators!</div>
);
}
};
Component 'Page' now has methods componentWillMount, componentDidMount, and componentWillUnmount. They run at the expected times in the component's lifecycle.
2 caveats: 1) I'm using the babel transform-decorators-legacy plugin; 2) I'm building my project using Webpack, with babel's transform-runtime included. YMMV.

Categories

Resources