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?
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.
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.
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.
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.
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
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')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With