Stubbing class field-functions in ES7 - javascript

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.

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.

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);
})
});

Related

Test method called in mounted hook Vue class component typescript

I am trying to test that a method is called on mount of Vue component. Fairly new to Vue and Typescript.
export default class App extends Vue {
mounted () {
this.deviceId()
this.ipAddress()
this.channel()
this.show()
this.campaign()
this.adUnit()
}
this approach works but I get a warning:
it('mounted methods are called', async () => {
const deviceId = jest.fn()
wrapper = shallowMount(App, {
methods: {
deviceId
}
})
expect(deviceId).toHaveBeenCalled()
})
The error:
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:1735
[vue-test-utils]: overwriting methods via the `methods` property is deprecated and will be removed in the next major version. There is no clear migration path for the `methods` property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex method extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I have tried using jest spyOn, but I cannot find a way to access the method;
const spy = jest.spyOn(App.prototype, 'methodName')
wrapper = shallowMount(App)
expect(spy).toHaveBeenCalled()
Gives the following error:
Cannot spy the deviceId property because it is not a function; undefined given instead
The following also doesn't work:
const spy = jest.spyOn(App.methods, 'methodName')
Error:
Property 'methods' does not exist on type 'VueConstructor<Vue>'.ts(2339)
And the following:
const spy = jest.spyOn(App.prototype.methods, 'deviceId')
Error:
Cannot spyOn on a primitive value; undefined given
I have read in places I may need to define an interface for the component but I am not sure how this works with defining functions inside or if it is necessary?
I've been facing the same issue for a few days, but I've found the way of pointing to the correct method when calling jest.spyOn().
It's a bit tricky but you'll find the methods of your class like this:
const spy = jest.spyOn(App.prototype.constructor.options.methods, 'deviceId');
Note that (even if it might seem obvious, just in case) you'll need to do this before wrapping your component, i.e. like this:
const spy = jest.spyOn(App.prototype.constructor.options.methods, 'deviceId');
wrapper = mount(App, { /* Your options go here */ });
By the way, you don't need to define methods property inside options.
Define your method under the property methods. Only then you can access them from the class.
export default class App extends Vue {
methods: {
deviceId(){
console.log("do your stuff")
}
}
}
See here for more examples for the usage of methods

Testing an isolated function of a component in React Jest

I am fairly new to Jest and Enzyme and I stumbled a cross a problem:
I have a Component which renders Children and also calls a method of those children. I achieve that by using refs. I call these functions something like:
somefunction = () => {
this.myReference.current.childFunction();
this.doSomethingOther();
}
I now want to test the function somefunction. I want to check if the doSomethingOther function was called. Using a shallow render I cannot achieve that. The test would succeed if this.myReference.current.childFunction(); wasn't called. Jest cannot know it because it only renders shallow and therefore throws an error.
I may be missing the full understanding. I wonder if someone has a Idea to test this function without using mount.
Take a look at the below code where I shallow render a component and then get the class instance and mock the required functions. Now when we call somefunction, we check if doSomethingOther has been called. (Assumption you are using jest+enzyme)
const wrapper = shallow(<Comp />);
const inst = wrapper.instance();
inst.myReference = {
current: {
childFunction: jest.fn()
}
}
inst.doSomethingOther = jest.fn();
inst.somefunction();
expect(inst.doSomethingOther).toHaveBeenCalled();

JS Class that return a function

So i love this React thingy, and we have this so called 'stateless' functions to help us build a 'dumb' component. Now i want to create a class that produce a stateless function. Why you may ask, well i love the idea of inheritance and wanting to 'extend' my stateless function basic capability, said i want to add a helper function as a statics that binds to the function itself.
I ended up with this code
class Stateless {
constructor() {
return this.render.bind(this)
}
nanny() {
// do something
}
render(props) {
// yeay! a stateless function!
// plus i can access nanny by using this.nanny()
}
}
And when i extend it, i can see that the inheritance is working well.
BUT, if then i initialize the class:
const stateless = new Stateless()
Why can't i access stateless.nanny even tho inside the render function i can see that this.nanny is accessible? Where does the nanny lives? Does it binded to the render function?
EG:
class Stateless {
constructor() {
return this.render.bind(this)
}
nanny() {
console.log('foo')
return true
}
render(props) {
console.log(this.nanny()) // -> returns 'foo'
return 'JSX'
// this should return a JSX
}
}
const stateless = new Stateless() // -> stateless IS a function
stateless()
// 'foo'
// true
// JSX
stateless.nanny
// undefined
While clearly inside render when i called this, there is nanny there. But when i
refer it outside, the nanny is gone. I thought nanny should be a static property of the stateless, right?
If you are returning object from constructor - new will return that object instead of the instance of the class being constructed (more info).
Therefore line
const stateless = new Stateless()
will assign to stateless variable result of this.render.bind(this) - that is reference to method (function) of Stateless class, that is not an instance of Stateless. Therefore stateless.nanny makes no sense - as function render does not have such property defined. On the other hand calling bound render function directly - produce the expected result.
All in all - i strongly do not recommend you to return anything from constructor (unless you are dealing with some really weird requirements like controlling number of instances and such). It makes code hard to understand and maintain.
Your example should work if you remove this.render.bind(this) from your constructor.
It should also work, if you just remove return from the constructor:
constructor() {
this.render.bind(this)
}
However, you might actually be looking to create a higher order component that can enhance the component that it wraps.
Your higher order component is a function that returns a class that renders the component that it passed to it:
import React from 'react'
function HigherOrderComponent(WrappedComponent) {
return class Enhancer extends React.Component {
constructor(props) {
super(props)
}
exampleFunc() {
// Code here
}
render() {
return <WrappedComponent exampleprop={ this.exampleFunc } />
}
}
}
export default HigherOrderComponent
Then you can import HigherOrderComponent into your stateless dumb component file and wrap the export:
import React from 'react'
import HigherOrderComponent from './HigherOrderComponent'
const DumbComponent = (props) => {
// Return JSX
// You can use props.exampleprop
}
export default HigherOrderComponent(DumbComponent)
Here are some articles that you can read on higher order components:
https://facebook.github.io/react/docs/higher-order-components.html
https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e

React - binding `this` to an imported function

In my React app, I have a handful of functions that I'd like to be able to access across a few similar components... however, I want to bind this to the shared functions so that they can do things like update the component state, etc... however, it seems that importing the functions and then trying to bind this in the 'typical' React manner does not work.
Here's an illustration of what I'd like to accomplish - in this case, clicking the rendered button would call the function from the imported shared function file and update the component state:
//shared_functions.js
const sharedFunctions = {
testFunction = () => {
this.setState({functionWasRun: true})
}
}
//MyComponent.jsx
import React, { Component } from 'react';
import sharedFunctions from '../static/scripts/shared_functions.js';
let { testFunction } = sharedFunctions;
class MyComponent extends Component {
constructor(props){
super(props);
this.testFunction = this.testFunction.bind(this)
this.state = {
functionWasRun: false
}
}
render(){
return(
<div>
<button onClick={this.testFunction}>Click</button>
</div>
)
}
}
Trying to run this code as is will return an error like:
Uncaught (in promise) TypeError: Cannot read property 'bind' of undefined
and I get what that's all about... but what I'd like to know is: is it possible to bind this to an imported function?
I'm starting to get a lot of similar-looking functions popping up throughout my app and I'd love to simplify things by abstracting them into a shared script, but I'm not sure how to achieve the typical this binding that's needed to achieve state-setting.
The following line is not trying to bind the imported testFunction but rather a method testFunction of <MyComponent>
To bind the imported function, refer to it directly, as follows:
this.testFunction = testFunction.bind(this);
// Notice how: ^--- there is no longer a this here
NB: You're example tries to use bind on an arrow function You cannot bind a new context to an arrow function. The this context of an arrow function will always be set to the location
were it is defined. You can get around this by declaring
testFunction using a regular function declaration:
const sharedFunctions = {
function testFunction(){
this.setState({functionWasRun: true})
}
}
I used it in the following way:
In constructor:
this.handleChange = handleChange.bind(this);
In the imported file (be careful, no arrow):
export const handleChange = function handleChange(event)
{
const { name, value } = event.target;
this.setState({
[name]: object
});
};
import ToastTimeout from 'toastTimout'
ToastTimeout.bind(this)('message to popup')
I was able to get the context of this in a simple service with a setTimeout function that changed the variable on this context
sorry for the sudo code

Closures in React

Is it ok use closures in react, for event handlers?
For example, i have some function and a lot of menu in navigation
and in navigation component i use something like this:
handleMenuClick(path) {
return () => router.goTo(path)
}
...
<MenuItem
handleTouchTap={this.handleMenuClick('/home')}
>
or i should prefer just arrow function?
<MenuItem
handleTouchTap={() => router.goTo('/home')}
>
first variant really make code cleaner, but i'm worried about performance with a large number of such elements
Both should be avoided.
While they'll both work, they both have the same weakness that they'll cause unnecessary renders because the function is being created dynamically, and will thus present as a different object.
Instead of either of those, you want to create your functions in a static way and then pass them in. For something like your MenuItem, it should just get the string for the path and then have the code to do the routing inside. If it needs the router, you should pass that in instead.
The function should then be a pre-bind-ed function (usually in the constructor) and just passed in.
export class MenuItem extends React.Component {
constructor() {
this.handleClick = () => this.props.router.go(this.props.path);
}
render() {
return (
<Button onClick={ this.handleClick }>Go to link</Button>
);
}
}
You can use an arrow function in the constructor. That way it isn't recreated every render function, and thus you avoid unnecessary renders. That pattern works well for single-line simple functions. For more complex functions, you can also create them as a separate function, then bind it in the constructor.
export class MenuItem extends React.Component {
handleClick() {
this.props.router.go(this.props.path);
}
constructor() {
this.handleClick = this.handleClick.bind(this);
}
render() { /* same as above */ }
}
The point of this is that the handler is the same function every time. If it was different (which both methods you describe above would be), then React would do unnecessary re-renders of the object because it would be a different function every time.
Here are two articles which go into more details:
https://ryanfunduk.com/articles/never-bind-in-render/
https://daveceddia.com/avoid-bind-when-passing-props/
when you define a new method inside a react component (Object) as we know functions are object in javascript.
let reactComponent={
addition: function(){ //some task ...},
render: function(){},
componentWillMount : function(){},
}
so, every new method should be bind with in the object using bind, but render() is already defined so we don't do
this.render = this.render.bind(this)
for each new function, except react lifecycle methods are needed to be added and hence, we call the object (constructor function) methods using this.method().

Categories

Resources