Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest spyOn function called

I'm trying to write a simple test for a simple React component, and I want to use Jest to confirm that a function has been called when I simulate a click with enzyme. According to the Jest docs, I should be able to use spyOn to do this: spyOn.

However, when I try this, I keep getting TypeError: Cannot read property '_isMockFunction' of undefined which I take to mean that my spy is undefined. My code looks like this:

import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; class App extends Component {    myClickFunc = () => {       console.log('clickity clickcty')   }   render() {     return (       <div className="App">         <div className="App-header">           <img src={logo} className="App-logo" alt="logo" />           <h2>Welcome to React</h2>         </div>         <p className="App-intro" onClick={this.myClickFunc}>           To get started, edit <code>src/App.js</code> and save to reload.         </p>       </div>     );   } }  export default App; 

and in my test file:

import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { shallow, mount, render } from 'enzyme'  describe('my sweet test', () => {  it('clicks it', () => {     const spy = jest.spyOn(App, 'myClickFunc')     const app = shallow(<App />)     const p = app.find('.App-intro')     p.simulate('click')     expect(spy).toHaveBeenCalled()  }) }) 

Anyone have an insight into what I'm doing wrong?

like image 206
Max Millington Avatar asked Jun 26 '17 22:06

Max Millington


People also ask

What is Jest spyOn function?

The spyOn function is one of the most powerful utility functions in Jest. It allows you to spy on a function, observe interactions, and mock them accordingly. But to spy on a named import in Jest isn't straightforward due to the arguments that the function accepts.

How do you check if a function is called in Jest?

To check if a component's method is called, we can use the jest. spyOn method to check if it's called. We check if the onclick method is called if we get the p element and call it.

How do you get the spyOn function in Jasmine?

describe("Example Of jasmine Spy using Create Spy", function() { it("can have a spy function", function() { var person = new Person(); person. getName11 = jasmine. createSpy("Name spy"); person. getName11(); expect(person.

What is the difference between Jest FN and Jest spyOn?

jest. fn() is a method to create a stub, it allowing you to track calls, define return values etc... jest. spyOn() came from jasmine, it allow you to convert an existing method on an object into a spy, that also allows you to track calls and re-define the original method implementation.


2 Answers

You're almost there. Although I agree with @Alex Young answer about using props for that, you simply need a reference to the instance before trying to spy on the method.

describe('my sweet test', () => {  it('clicks it', () => {     const app = shallow(<App />)     const instance = app.instance()     const spy = jest.spyOn(instance, 'myClickFunc')      instance.forceUpdate();          const p = app.find('.App-intro')     p.simulate('click')     expect(spy).toHaveBeenCalled()  }) }) 

Docs: http://airbnb.io/enzyme/docs/api/ShallowWrapper/instance.html

like image 27
CharlieBrown Avatar answered Sep 18 '22 10:09

CharlieBrown


You were almost done without any changes besides how you spyOn. When you use the spy, you have two options: spyOn the App.prototype, or component component.instance().


const spy = jest.spyOn(Class.prototype, "method")

The order of attaching the spy on the class prototype and rendering (shallow rendering) your instance is important.

const spy = jest.spyOn(App.prototype, "myClickFn"); const instance = shallow(<App />); 

The App.prototype bit on the first line there are what you needed to make things work. A JavaScript class doesn't have any of its methods until you instantiate it with new MyClass(), or you dip into the MyClass.prototype. For your particular question, you just needed to spy on the App.prototype method myClickFn.


jest.spyOn(component.instance(), "method")

const component = shallow(<App />); const spy = jest.spyOn(component.instance(), "myClickFn"); 

This method requires a shallow/render/mount instance of a React.Component to be available. Essentially spyOn is just looking for something to hijack and shove into a jest.fn(). It could be:

A plain object:

const obj = {a: x => (true)}; const spy = jest.spyOn(obj, "a"); 

A class:

class Foo {     bar() {} }  const nope = jest.spyOn(Foo, "bar"); // THROWS ERROR. Foo has no "bar" method. // Only an instance of Foo has "bar". const fooSpy = jest.spyOn(Foo.prototype, "bar"); // Any call to "bar" will trigger this spy; prototype or instance  const fooInstance = new Foo(); const fooInstanceSpy = jest.spyOn(fooInstance, "bar"); // Any call fooInstance makes to "bar" will trigger this spy. 

Or a React.Component instance:

const component = shallow(<App />); /* component.instance() -> {myClickFn: f(), render: f(), ...etc} */ const spy = jest.spyOn(component.instance(), "myClickFn"); 

Or a React.Component.prototype:

/* App.prototype -> {myClickFn: f(), render: f(), ...etc} */ const spy = jest.spyOn(App.prototype, "myClickFn"); // Any call to "myClickFn" from any instance of App will trigger this spy. 

I've used and seen both methods. When I have a beforeEach() or beforeAll() block, I might go with the first approach. If I just need a quick spy, I'll use the second. Just mind the order of attaching the spy.


EDIT: If you want to check the side effects of your myClickFn you can just invoke it in a separate test.

const app = shallow(<App />); app.instance().myClickFn() /* Now assert your function does what it is supposed to do... eg. expect(app.state("foo")).toEqual("bar"); */ 

EDIT: Here is an example of using a functional component. Keep in mind that any methods scoped within your functional component are not available for spying. You would be spying on function props passed into your functional component and testing the invocation of those. This example explores the use of jest.fn() as opposed to jest.spyOn, both of which share the mock function API. While it does not answer the original question, it still provides insight on other techniques that could suit cases indirectly related to the question.

function Component({ myClickFn, items }) {    const handleClick = (id) => {        return () => myClickFn(id);    };    return (<>        {items.map(({id, name}) => (            <div key={id} onClick={handleClick(id)}>{name}</div>        ))}    </>); }  const props = { myClickFn: jest.fn(), items: [/*...{id, name}*/] }; const component = render(<Component {...props} />); // Do stuff to fire a click event expect(props.myClickFn).toHaveBeenCalledWith(/*whatever*/); 

If a functional component is niladic (no props or arguments) then you can use Jest to spy on any effects you expect from the click method:

import { myAction } from 'src/myActions' function MyComponent() {     const dispatch = useDispatch()     const handleClick = (e) => dispatch(myAction('foobar'))     return <button onClick={handleClick}>do it</button> }  // Testing: const { myAction } = require('src/myActions') // Grab effect actions or whatever file handles the effects. jest.mock('src/myActions') // Mock the import  // Do the click expect(myAction).toHaveBeenCalledWith('foobar') 
like image 50
taystack Avatar answered Sep 18 '22 10:09

taystack