eguneys eguneys - 1 month ago 17
Javascript Question

How to test decorated React component with shallow rendering

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?

Answer

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.