Test the function inside click handler - Jest, React testing Library - javascript

I have a button click handler in which I call another function. I need to test the function call inside of the handler:
SomeComponent
...
const handler = () => {
someFunction();
}
...
<button data-testId="button" onClick={handler}>Click Me</button>
test
describe('Button click', () => {
it('button click', async () => {
render(<SomeComponent />);
const button = await screen.findByTestId('button');
fireEvent.click(button);
// some silly test case just for example
expect(button).toBeInTheDocument();
});
});
While doing this, it covers the handler but not the inner function itself:
const handler = () => { <<<<<<< covered
someFunction(); <<<<<<< UNCOVERED
}. <<<<<<< covered
The main question here is how can I test the inner function call? If I need to mock it, how should I do it, because the mocked function will not test the actual one?
UPDATE
Also, my someFunction doesn't change anything in the scope of this component, so I can't catch it by comparing the inner state or document change.
SomeFunction is coming from another file and I tested it separately.

It depends on where someFunction is defined. If it's a property given to <SomeComponent /> then you could do something like this:
describe('Button click', () => {
it('button click', async () => {
const someFunction = jest.fn();
render(<SomeComponent someFunction={someFunction} />);
const button = await screen.findByTestId('button');
fireEvent.click(button);
// if there are some precise arguments given to `someFunction` maybe
// use `toHaveBeenCalledWith` instead
expect(someFunction).toHaveBeenCalled();
});
});
But if it's defined in a separate hook then you should mock this hook. For instance here let's assume there a useSomeFunction that directly returns this someFunction:
import { useSomeFunction } from '../path/to/useSomeFunction';
jest.mock('../path/to/useSomeFunction', () => ({
useSomeFunction: jest.fn(),
}));
describe('Button click', () => {
it('button click', async () => {
const mockSomeFunction = jest.fn();
useSomeFunction.mockImplementation(() => mockSomeFunction);
render(<SomeComponent />);
const button = await screen.findByTestId('button');
fireEvent.click(button);
// if there are some precise arguments given to `someFunction` maybe
// use `toHaveBeenCalledWith` instead
expect(mockSomeFunction).toHaveBeenCalled();
});
});
And if it's simply a function defined elsewhere you could adapt the example I gave with hook mocking:
import { someFunction } from '../path/to/util';
jest.mock('../path/to/util', () => ({
someFunction: jest.fn(),
}));
describe('Button click', () => {
it('button click', async () => {
render(<SomeComponent />);
const button = await screen.findByTestId('button');
fireEvent.click(button);
// if there are some precise arguments given to `someFunction` maybe
// use `toHaveBeenCalledWith` instead
expect(someFunction).toHaveBeenCalled();
});
});

someFunction() needs to generate some side effects to your app. You
can test those side effects. For instance if someFunction() was
incrementing a count state value you could test for that in your
component to check if count was incremented when button was clicked.

Related

testing with jest to update a react state inside a rejected promise

This is a continuation of this question. I have made a few changes that simplifies the question(I believe) and changes it drastically.
I have seperated creating the hook and initialization of midi events.
describe("midiConnection", () => {
it("Should fail", () => {
const midiPorts = renderHook(() => { return MidiConnection()})
act(() => {
midiPorts.result.current.enable()
})
console.log(midiPorts.result.current.error)
})
})
export function MidiConnection() {
const {array: midiInputs, push: midiPush, filter: midiFilter} = useArray(["none"])
const [error, setError] = useState<Error | undefined>();
function enable() {
WebMidi.addListener("connected", (e) => { if (isInput(e)) {midiPush(e.port.name)}});
WebMidi.addListener("disconnected", (e) => {
e.port.removeListener()
if (isInput(e)) {midiFilter((str) => {return str != e.port.name})}
});
// setError("this is a test")
WebMidi.
enable().
catch((err) => {
// console.log("test")
// setError(err)
})
}
return ({ports: midiInputs, error, enable})
}
the warning is still;
Warning: An update to TestComponent inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
In addition to seperating out some of the logic I have also experimented with placing setError() on other lines to see if I can trigger the warning (the commented out comments.)
It appears that the warning is only triggered when I try to update the state when the promise from enable() is rejected.
What can I do to stop this error from happening?
EDIT: I have created a working replica of this in CodeSandbox, which you will see if you go to tests and look at the console.
Your hook is async so u need to wait for the next update. Here is the docs that talks more about it.
import { renderHook, act } from "#testing-library/react-hooks/dom";
import CHook from "./../hook/CHook";
test("This is a test", async () => {
const { result, waitForNextUpdate } = renderHook(() => CHook());
act(() => {
result.current.update();
});
await waitForNextUpdate();
console.log(result.current.error);
});
Here is the link to a fixed sandbox.

How to use "toHaveBeenCalledAfter" with a function that has been called several times?

I have a function:
funtion foo() {
Spinner.setVisible(true);
await ApiService.doApiCall();
Spinner.setVisible(false);
}
I'd like to unit test the order of function calls, to make sure the spinner is being shown before the API call and hidden afterwards.
I use Jest. I tried this:
import 'jest-extended';
describe('foo', () => {
it('should make spinner visible before the api call', async () => {
const setVisibleSpy = jest.spyOn(Spinner, 'setVisible').mockImplementation(() => true);
const doApiCallSpy = jest.spyOn(ApiService, 'doApiCall').mockImplementation(() => Promise.resolve(true));
expect(setVisibleSpy).toHaveBeenCalledBefore(doApiCallSpy ); // <-- THIS WORKS!!!
})
it('should make spinner hidden afterthe api call', async () => {
const setVisibleSpy = jest.spyOn(Spinner, 'setVisible').mockImplementation(() => true);
const doApiCallSpy = jest.spyOn(ApiService, 'doApiCall').mockImplementation(() => Promise.resolve(true));
expect(setVisibleSpy).toHaveBeenCalledAfter(doApiCallSpy ); // <-- THIS DOESN'T WORK!!!
})
})
As I understand, the check after doesn't work, because the very first call of the setVisible() function actually happen before the doApiCall().
How can I check that the second call of the setVisible() function happened after the doApiCall() call?

React Testing Library - not wrapped in act() error

I'm having trouble using react-testing-library to test a toggle component.
On click of an icon (wrapped in a button component), I am expecting text to go from 'verified' to 'unverified'. In addition, a function is called where there are state updates.
However, the click event doesn't seem to work, and I am getting the below error:
> jest "MyFile.spec.tsx"
FAIL src/my/path/__tests__/MyFile.spec.tsx
component MyFile
✓ renders when opened (94 ms)
✓ renders with items (33 ms)
✕ toggles verification status on click of icon button (100 ms)
console.error
Warning: An update to MyFile inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at MyFile (/path/to/myfile.tsx:44:3)
at ThemeProvider (/users/node_modules/#material-ui/styles/ThemeProvider/ThemeProvider.js:48:24)
123 | );
124 | } finally {
> 125 | setIsLoading(false);
| ^
126 | }
127 | };
128 |
at printWarning (node_modules/react-dom/cjs/react-dom.development.js:67:30)
at error (node_modules/react-dom/cjs/react-dom.development.js:43:5)
at warnIfNotCurrentlyActingUpdatesInDEV (node_modules/react-dom/cjs/react-dom.development.js:24064:9)
at dispatchAction (node_modules/react-dom/cjs/react-dom.development.js:16135:9)
at handleConfirm (src/modules/myfile.tsx:125:7)
In my code, I have a function like this:
const handleSubmit = async() => {
if(isLoading) {
return;
}
try {
setIsLoading(true);
await myFunctionCalls();
} catch (error){
console.log(error)
} finally {
setIsLoading(false)
}
};
My test looks similar to this:
test('toggles verification status on click of icon button', async () => {
renderWithTheme(
<MyComponent/>,
);
const updateVerificationMock = jest.fn();
const callFunctionWithSerializedPayloadMock =
callFunctionWithSerializedPayload as jest.Mock;
callFunctionWithSerializedPayloadMock.mockImplementation(
() => updateVerificationMock,
);
const button = screen.getByRole('button', {name: 'Remove approval'});
fireEvent.click(button);
await act(async () => {
expect(myFunctionCalls).toHaveBeenCalledTimes(1);
});
expect(await screen.findByText('unverified')).toBeInTheDocument();
});
The first expect passes as the function calls are called once, however I have the act() error from above, and also there is a failure as it seems that the text does not toggle from verified to unverified.
I am aware that usually the act error is an issue of async/waiting for calls to happen, but I thought that findByText should wait, and it seems like there is another issue I'm not catching here. Any help on what to do to debug/improve this test?
There are 3 async functions that are called here when you click on the Remove Approval button.
First, you are setting the loading state to true, so it will load then the async function (myFunctionCalls) is called, and finally, the loader will disappear after the loading state is set to false.
In order to solve it, we have to wait for the loading to appear first, then myFunctionCalls is called, and then later we have to wait for loading to disappear.
test("toggles verification status on click of icon button", async () => {
renderWithTheme(<MyComponent />);
const updateVerificationMock = jest.fn();
const callFunctionWithSerializedPayloadMock =
callFunctionWithSerializedPayload as jest.Mock;
callFunctionWithSerializedPayloadMock.mockImplementation(
() => updateVerificationMock
);
const button = screen.getByRole("button", { name: "Remove approval" });
fireEvent.click(button);
expect(await screen.findByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(myFunctionCalls).toHaveBeenCalledTimes(1);
});
await waitForTheElementToBeRemoved(() => {
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
});
expect(await screen.findByText("unverified")).toBeInTheDocument();
});
If you do not have loading text then you can use act(() => jest.advanceTimersByTime(500)); for extending the time till 500ms. When the time reaches 500ms, the async function would have been resolved.
beforeEach(() => {
jest.useFakeTimers();
})
afterEach(() => {
jest.runAllPendingTimers();
jest.useRealTimers()
})
test("toggles verification status on click of icon button", async () => {
renderWithTheme(<MyComponent />);
const updateVerificationMock = jest.fn();
const callFunctionWithSerializedPayloadMock =
callFunctionWithSerializedPayload as jest.Mock;
callFunctionWithSerializedPayloadMock.mockImplementation(
() => updateVerificationMock
);
const button = screen.getByRole("button", { name: "Remove approval" });
fireEvent.click(button);
act(() => jest.advanceTimersByTime(500));
await waitFor(() => {
expect(myFunctionCalls).toHaveBeenCalledTimes(1);
});
act(() => jest.advanceTimersByTime(500));
expect(await screen.findByText("unverified")).toBeInTheDocument();
});
Try this:
// [...]
fireEvent.click(button);
await waitFor(() => {
expect(myFunctionCalls).toHaveBeenCalledTimes(1),
expect(screen.findByText('unverified')).toBeInTheDocument()
});
// End of test

How to check if one function triggers another function with `Mocha`, `Chai`, `Sinon`

How to create test with Mocha, Chai, Sinon to check if one function triggers another function.
I would like to check if funcToTrigger triggers funcToSpy
import { expect } from 'chai';
import sinon from 'sinon';
it('one function should trigger other function', () => {
const funcToSpy = () => {
console.log('I should be called');
};
const funcToTrigger = () => {
funcToSpy();
};
const spyFunc = sinon.spy(funcToSpy);
funcToTrigger();
expect(spyFunc.called).to.be.true;
});
When I test only one function it works fine:
it('function should be called', () => {
const funcToSpy = () => {
console.log('I should be called');
};
const spyFunc = sinon.spy(funcToSpy);
spyFunc();
expect(spyFunc.called).to.be.true;
});
Based on documentation:
var spy = sinon.spy(myFunc);
Wraps the function in a spy. You can pass this spy where the original
function would otherwise be passed when you need to verify how the
function is being used.
Usage examples:
import { expect } from 'chai';
import sinon from 'sinon';
it('use Object', () => {
const Test = {
funcToSpy: () => {
console.log('I should be called');
},
};
const funcToTrigger = () => {
Test.funcToSpy();
};
const spyFunc = sinon.spy(Test, 'funcToSpy');
funcToTrigger();
expect(spyFunc.called).to.be.true;
});
it('use Function', () => {
const funcToSpy = () => {
console.log('I should be called');
};
const spyFunc = sinon.spy(funcToSpy);
const funcToTrigger = () => {
spyFunc();
};
funcToTrigger();
expect(spyFunc.called).to.be.true;
});
it('use Function Argument', () => {
const funcToSpy = () => {
console.log('I should be called');
};
const funcToTrigger = (funcToSpy) => {
funcToSpy();
};
const spyFunc = sinon.spy(funcToSpy);
funcToTrigger(spyFunc);
expect(spyFunc.called).to.be.true;
});
Result:
$ npx mocha index.spec.js
I should be called
✓ use Object
I should be called
✓ use Function
I should be called
✓ use Function Argument
3 passing (3ms)
$
Your test fail because: funcToTrigger has defined and always calls the original funcToSpy.
In the 'use Object' case, funcToTrigger calls method inside object Test, which has been replaced by spy, which is wrapping funcToSpy.
In the 'use Function' case, funcToTrigger calls spy directly, and the spy is wrapping funcToSpy.
In the 'use Function Argument' case, funcToTrigger calls first argument which is a spy, which is wrapping funcToSpy.

JEST - How to check the prop- actions getting called from the function

I am new to the React and Jest and I am trying to test my function where : I have a function :
handleSubmit = (e) => {
this.changeData().then(() => {
const payload = {
somePayload: 'sample'
};
this.props.actions.authenticate(payload);
});
};
and the jest function as :
it('test mock test', () => {
const actionsMock = { authenticate: jest.fn() };
const localWrapper = shallow(<SomeComponent actions={actionsMock} />);
const instance = localWrapper.instance();
jest.spyOn(instance, 'handleSubmit');
instance.forceUpdate();
localWrapper.find('.button').simulate('click');
expect(actionsMock.authenticate).toHaveBeenCalledWith({somePayload: 'sample'});
});
So, In this case, when I click on the button it calls the handlesubmit function and eventually the this.props.actions.authenticate(payload); but when I assert the same in Jest it gives me error that function was never called.
EDIT
As in the comment CJ Pointed: I see that my assertion is getting called even before the promise for changeData resolved. So, How I can I make my assertion wait till the promise gets resolved?

Categories