0
votes

Not being able to get the following test to pass... Sorry for putting up a lot of code.... I was able get some other click events working but I am stuck with this one at the moment

Getting the following message:

"expect(jest.fn()).toHaveBeenCalled()

expected mock function to have been called."

here is the click event under Render method

      className={!this.state.name || !this.state.label || this.state.valueStore === null ? `add-custom-field-button disabled` : `add-custom-field-button`}
          id="test-addclick"
          onClick={() => {this.onAddClick()}}
        >
          Create Field
        </button>

here is onAddClick method:

onAddClick = () => {
let obj = this.props.selectedFormJSON;

this.addValueAttribute().then(()=>{
  obj.FORM_COLUMN.push(
    {
      Control: this.state.control,
      CreateBy: this.props.user.userId,
      Datatype: this.state.datatype,
      Form_UID: this.props.selectedFormJSON.Form_UID,
      Help: this.state.help,
      ValueStore: this.state.valueStore
    }
  )
  this.props.updateSelectedFormJSON(obj);
  if(!this.props.isNewForm && this.state.valueStore) {
    this.props.patchForm().then((res)=>{
    if(res.Forms[0] && (res.Forms[0].Code === '200' || res.Forms[0].Code===200)) {
      toast(<div>Attribute Added Successfully!</div>, {type: toast.TYPE.SUCCESS,position: toast.POSITION.TOP_LEFT})
    } else {
      toast(<div>Failed to Add Attribute!</div>, {type: toast.TYPE.ERROR,position: toast.POSITION.TOP_LEFT})
    }
  });
  } else if(this.state.valueStore) {
    this.props.postForm().then((res)=>{
      if(res.Forms[0] && (res.Forms[0].Code === '201' || res.Forms[0].Code===201)) {
        toast(<div>Attribute Added Successfully!</div>, {type: toast.TYPE.SUCCESS,position: toast.POSITION.TOP_LEFT})
      } else {
        toast(<div>Failed to Add Attribute!</div>, {type: toast.TYPE.ERROR,position: toast.POSITION.TOP_LEFT})
      }
    })
  }
  this.props.closeModal();
})
}

     addValueAttribute = () => {
return new Promise((resolve, reject) => {
  if(this.state.valueStore) {
    let {valueTables, valueDatatypes, service} = this.state;
    let body = {
      Name: this.state.name,
      TableName: this.props.selectedFormJSON.Entity,
      Datatype: this.state.datatype,
      ChangeType: 'I'
    }
    fetch(service.URL+'/VALUE_ATTRIBUTE', { headers: {
      'Content-Type': 'application/json',
      'Ocp-Apim-Subscription-Key': service.subscription_key,
    },
      method: 'POST',
      credentials: 'include',
      body: JSON.stringify(body),
    })
    .then((res) => {
      res.status === 201 && resolve();
    })
    .catch(() => {
      reject();
    })
  } else {
    //Not a value attr
    resolve()
  }
})

}

Here is how I am trying to test it: using jest/enzyme. I have been using the same set up for some other click events and it has been working. Unable to figure out for the following:

it("should call onAddClick", async () => {  // use an async test method
baseProps. closeModal.mockClear();
baseProps. updateSelectedFormJSON.mockClear();

const instance = wrapper.instance();
const spy = jest.spyOn(instance, 'addValueAttribute');  // spy on addValueAttribute...
spy.mockImplementation(() => Promise.resolve())  // give any callbacks queued in PromiseJobs a chance to run
wrapper.find('#test-addclick').at(0).simulate('click');  // simulate click

expect(baseProps.updateSelectedFormJSON).toHaveBeenCalled();  // SUCCESS
expect(baseProps.closeModal).toHaveBeenCalled();  // SUCCESS
});
1
The problem is the call to addValueAttribute. As this is a promise jest don't wait for it. So the call for toHaveBeenCalled is called before the promise is resolved. Normally you solve such issues by mocking the promise call. Could you please show what happens inside of addValueAttribute?Andreas Köberle
its going to be a lot code but I updated the questionusertestREACT
@AndreasKöberle updated !!usertestREACT
Looks like there is no easy way to mock this. Maybe you can try this solution: jestjs.io/docs/en/troubleshooting#unresolved-promisesAndreas Köberle
I will give it a try - thanks andreasusertestREACT

1 Answers

1
votes

addValueAttribute is expensive so you will want to mock it to resolve immediately. addValueAttribute is a class field so you will need to mock it using the component instance.

When onAddClick is called it will call this.addValueAttribute which will be mocked to immediately return. This will cause the Promise callback in then to get added to the PromiseJobs queue. Jobs in this queue run after the current message completes and before the next message begins.

This means that the callback that calls this.props.updateSelectedFormJSON and this.props.closeModal is queued in the PromiseJobs queue when the click handler returns and the test continues.

At this point you need to pause your test to give the callback queued in PromiseJobs a chance to run. The easiest way to do that is to make your test function async and call await Promise.resolve(); which will essentially queue the rest of the test at the end of the PromiseJobs queue and allow any jobs already in the queue to run first.

Putting it all together, here is a simplified version of your code with a working test:

import * as React from 'react';
import { shallow } from 'enzyme';

class Comp extends React.Component {
  onAddClick = () => {
    this.addValueAttribute().then(() => {
      this.props.updateSelectedFormJSON();
      this.props.closeModal();
    })
  }
  addValueAttribute = () => {
    return new Promise((resolve) => {
      setTimeout(resolve, 100000);  // does something computationally expensive
    });  
  }
  render() {
    return (<button onClick={this.onAddClick}>Create Field</button>);
  }
}

it("should call onAddClick", async () => {  // use an async test method
  const props = {
    updateSelectedFormJSON: jest.fn(),
    closeModal: jest.fn()
  }
  const wrapper = shallow(<Comp {...props} />);
  const instance = wrapper.instance();
  const spy = jest.spyOn(instance, 'addValueAttribute');  // spy on addValueAttribute...
  spy.mockResolvedValue();  // ...and mock it to immediately resolve
  wrapper
    .find('button')
    .at(0)
    .simulate('click');  // simulate click
  await Promise.resolve();  // give any callbacks queued in PromiseJobs a chance to run
  expect(props.updateSelectedFormJSON).toHaveBeenCalled();  // SUCCESS
  expect(props.closeModal).toHaveBeenCalled();  // SUCCESS
});