1

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.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
jalooc
  • 1,169
  • 13
  • 23
  • can you show an example of what you're trying to extract? the words "function" and "method" are confusing in this context. – dandavis Jan 26 '16 at 17:28
  • As a method I mean a function assigned to a class. The code is pretty obfuscated right now, as I'm testing a lot, but I'll try to extract some essential parts. – jalooc Jan 26 '16 at 17:32
  • well to me, and maybe most readers, a function assigned to a class is a method. fat arrows don't make good methods though, so maybe you describe something else. that's why i'm confused... – dandavis Jan 26 '16 at 17:35
  • I agree entirely and in my question follow this rule. I call _methods_ methods and fields which are arrow functions are described as _functions created by assigning to class' fields_. – jalooc Jan 26 '16 at 17:38
  • does `Object.getOwnPropertyNames(component)` iterate your "own functions"? – dandavis Jan 26 '16 at 17:40
  • Since the getOwnPropertyNames is executed on a code transpiled by babel, the only _properties_ of that class are it's _methods_ (it sounds terrible when mixing JS syntax naming conventions with OOP doesn't it). So the class's fields are not seen by that function. I showed it in the linked repl I just have posted in the question. – jalooc Jan 26 '16 at 17:50
  • i see `["normalProperty","functionProperty","constructor","normalMethod"]`; what's missing? – dandavis Jan 26 '16 at 17:58
  • This is the desired outcome, but the method is ugly - I need to search on the prototype of the object and then on the instantiated object. Besides, I'm not yet sure if I am able to instantiate a react components object directly, to be able to extract it's properties I think it's deeply encapsulated. – jalooc Jan 26 '16 at 18:01
  • well, since the property is defined inside the constructor, then the constructor needs to run before you can find the property. if you want to avoid the 2step, then a simpler for-in loop will iterate own and prototype properties at once. you could also use extend()/Object.assign instead of direct assignment in-constructor, duping from an outside object collection of methods, which can be iterated at will. – dandavis Jan 26 '16 at 18:03
  • It's still very hacky and demands spoiling code clarity for sake of unit tests. I hope that in such a big community like React.js users, there exists some _standard_ method to do something as common as end-to-end component stubbing, that's why I posted this question. But if nothing else shows up, I'll have no other option then to resort to your solution. – jalooc Jan 26 '16 at 18:19
  • do note that you can use `Object.keys(C.apply({}))` to find such own properties without technically instantiating a `C` instance. methods found in that fashion probably won't be usable without a true instance, but you can at least iterate them. – dandavis Jan 26 '16 at 18:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/101710/discussion-between-jalooc-and-dandavis). – jalooc Jan 26 '16 at 18:41
  • You might want to have a look at [s it possible to redefine a JavaScript class's method?](http://stackoverflow.com/a/21243884/1048572), however with ES6 `class`es (in a real ES6 environment) you might need to employ some of the techniques [mentioned here](http://stackoverflow.com/a/31789308/1048572) – Bergi Jan 26 '16 at 19:09

2 Answers2

2

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.

Marcos Abreu
  • 2,832
  • 22
  • 18
  • 1
    With the recent Jest 19 features such as [`jest.spyOn`](https://facebook.github.io/jest/docs/jest-object.html#jestspyonobject-methodname), is there a better solution? I really would like to avoid refactoring if possible. – Con Antonakos Apr 20 '17 at 16:20
1

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);
  })
});
Con Antonakos
  • 1,735
  • 20
  • 23