1

I am trying to test a specific component that is nested within two other components:

<Router>
  <Provider store={store}>
    <Howitworks />
  </Provider>
</Router>

However when I try to run my test:

 test("should call onTaskerClick", () => {

   const spy = jest.spyOn(Howitworks.prototype, "onTaskerClick");
   const wrapper = mount(
     <Router>
       <Provider store={store}>
         <Howitworks />
       </Provider>
     </Router>
   );
   wrapper.find("#pills-tasker-tab.nav-link.tasklink").at(0).simulate("click");

   expect(spy).toHaveBeenCalled();

 });

I get a "spyOn on a primitive value; undefined given" error. I have tried different variations of mocking this "onTaskerClick" function when simulating a click on the link that invokes it but always get a variation of the error that the function is undefined or function was not called.

The link that invokes onTaskerClick:

 <a className='nav-link tasklink' id='pills-tasker-tab' data-toggle='pill' onClick={this.onTaskerClick} role='tab' aria-controls='pills-tasker' aria-selected='false'>LINK<span></span></a>

Here is how the Howitworks component is currently being exported:

 export default connect(mapStateToProps, { onNotifyClick })(Howitworks);

Unfortunately there's very limited documentation on accessing functions within a nested component in a test environment so any help would be great.

EDIT:

Updated test suite:

 test("should call onTaskerClick", () => {

   const wrapper = mount(
     <Router>
       <Provider store={store}>
         <Howitworks />
       </Provider>
     </Router>
   );
   const spy = 
 jest.spyOn(wrapper.find(Howitworks.WrappedComponent).instance(), "onTaskerClick");
   wrapper.find("#pills-tasker-tab.nav-link.tasklink").at(0).simulate("click");

   expect(spy).toHaveBeenCalled();
 });

mapStateToProps function and export:

 let mapStateToProps = (state) => ({});

 export default connect(mapStateToProps, { onNotifyClick })(Howitworks);
Richard Desouza
  • 249
  • 4
  • 13

1 Answers1

1

connect returns a higher-order component that doesn't inherit from original component, which is common in React.

In order for onTaskerClick to be spyable on a prototype, it should be prototype and not instance (arrow) method. Original component class can be either separate named export:

export class Howitworks {
  onTaskerClick() {...}
  ...
}

export default connect(...)(Howitworks);

Or it can be accessed on connected component as WrappedComponent property.

Since onTaskerClick isn't called on component instantiation, there's no need to spy on a prototype, it's easier to do this on an instance, this doesn't limit a spy to prototype methods:

   const wrapper = mount(...);
   const origComp = wrapper.find(Howitworks.WrappedComponent).instance();
   const spy = jest.spyOn(origComp, "onTaskerClick");
   origComp.forceUpdate();
   // trigger click
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thanks for the reply, I still get a TypeError: Cannot read property 'onTaskerClick' of null – Richard Desouza Sep 04 '20 at 15:27
  • Yea I used last one. What solution should I use because the onTaskerClick is an arrow function in Howitworks. – Richard Desouza Sep 04 '20 at 15:51
  • I tried using WrapperComponent but still getting Received number of calls: 0 – Richard Desouza Sep 04 '20 at 15:54
  • Then that you tried to spy on an arrow with Howitworks.prototype is wrong, only the last has a chance to work. Since it doesn't cause errors that could indicate problems with a spy, I'd expect it to work. Please, update the question with full component and your current test attempt. It's unclear what's wrong on your side. – Estus Flask Sep 04 '20 at 16:13
  • I didn't use prototype. Please see my updated test suite. I'm also unable to post the full component but it is a class component where the arrow function onTaskerClick changes a 'step' value in the state to 'tasker'. I also added the mapStateToProps function in the op maybe that will give additional information. – Richard Desouza Sep 04 '20 at 16:17
  • I see. The problem is that original onTaskerClick has already been registered at the time of click. The component needs to be re-rendered in order to register a spy as click handler. See the update. Proto methods with `bind` are more preferable as callbacks than arrows because they are more testable and allow to additionally use option 1, see https://stackoverflow.com/a/45882417/3731501 , this is useful because option 2 is not always available. – Estus Flask Sep 04 '20 at 16:35
  • If none of these options were available (this would happen for functional comp), you'd need to spy on effects that onTaskerClick causes rather than itself. – Estus Flask Sep 04 '20 at 16:38
  • Thanks for all the info I learned a lot lol. Also the updated works and the test passes now – Richard Desouza Sep 04 '20 at 17:09