Stubbing class field-functions in ES7

605 Views Asked by At

In my test suite, how can I stub a class' property, which is a function*? With normal methods it's easy using Object.getOwnPropertyNames(component.prototype) and monkey patching each found method, but after a long time of struggle I haven't found any way to extract the functions created by assigning to class' fields.

My testing stack consists of Jest with Jasmine2 and babel.

The problem with transpiling is that the arrow-function-properties are (as expected, of course) assigned to instance of the output transpiled "class" (function actually, of course). So I don't see any way of stubbing them other then instantiating this object, am I right? Here is the example of input es7 code and the babel's output. However I don't particularly like this solution, seems very hacky. The other drawback of this solution is that I don't get to directly instantiate the component's class.


(*) The background of this question is unit testing React components written in es7-like classes with arrow functions assigned to class' properties for the purpose of auto binding.

2

There are 2 best solutions below

1
On BEST ANSWER

I was having the same problem, when writing unit tests for a project I'm working, and I think I got a good pattern to solve it. Hopefully it helps:

Context

Here is an example of a React component that has a method handleClick defined using the fat arrow notation.

import React, { Component } from 'react';

class Foo extends Component {
  componentWillMount() {
    this.handleClick();
  }

  handleClick = (evt) => {
    // code to handle click event...
  }

  render() {
    return (
      <a href="#" onClick={this.handleClick}>some foo link</a>
    );
  }
}

Problem

As described in this link Babel will transpile the code so that the handleClick method is only available after instantiation (check lines 31 to 33 of the generated constructor function)

The problem here is that sometimes you need to have access to methods defined using the fat arrow notation before instantiating the class.

Lets say for example that you are writing unit tests for the componentWillMount class method and you want to stub the handleClick so that you only test the desired unit. But now you have a problem, since you can only have access to handleClick after instantiation and componentWillMount method will be called automatically by React as part of its instantiation lifecycle.

Solution

Here is how I can apply a simple pattern to solve problems like this:

import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

import Foo from './foo';

describe('Foo', () => {
  describe('componentWillMount method', () => {
    const handleClickStub = sinon.stub();
    class FooWrapper extends Foo {
      constructor(props) {
        super(props);
        this.handleClick = handleClickStub;
      }
    }

    it('should register a click event listener to the externalElement property', () => {
      handleClickStub.reset();
      mount(<FooWrapper />);
      expect(handleClickStub.calledOnce).to.be.true;
    });
  });
});

Explanation

I've wrapped the original Foo component into a FooWrapper where on its constructor after initializing the original component I replace the original handleClick method with a stubbed version allowing me to property test my componentWillMount class.

1
On

Due to the way babel transpiles the arrow function syntax on class methods via transform-class-properties, the class method is no longer bound on the prototype, but rather the instance.

Using Jest 19's built-in assertion and .spyOn methods, this was my solution:

import React from 'react';
import { shallow } from 'enzyme';

describe('MyComponent', () => {
  it('should spy properly', () => {
    const wrapper = shallow(<Component />);
    const wrapperInstance = wrapper.instance();
    const spy = jest.spyOn(wrapperInstance, 'functionName');
    wrapperInstance.functionName();
    expect(spy).toHaveBeenCalledTimes(1);
  })
});