Insufficient Jest coverage - javascript

I have the below code:
<ReturnToLastSearch
href={'/listings'}
onClick={(e): void => {
e.preventDefault();
router.back();
}}
/>
Inside ReturnToLastSearch({ href, onClick }: Props) has the following:
<a className="button" onClick={!onClick ? urlOnClick : onClick}>
Everything is working fine, except jest complains that my diff, which adds the onClick prop and the ternary has insufficient tests coverage!
I tried:
it('when onClick is defined uses onClick', () => {
const call = ()=> ({back: jest.fn()});
jest.mock('next/router', call)
const { getByText } = render(<ReturnToLastSearch href="foo" onClick={call}/>);
getByText(i18n.t('favorites.returnToLastSearch') as string).click();
expect(router.back).toHaveBeenCalledWith('/listings')
});
I tried the above, which is the same as the version without onClick except for the expect and the mock of router.back(). However, I got an error telling me that the second argument in the jest.mock() should be an inline function.
What sort of test would make sense and also convince jest to leave me alone?
Thank you

I ended-up with:
it('when onClick is defined uses onClick', () => {
const call = jest.fn();
const { getByText } = render(<ReturnToLastSearch href="foo" onClick={call} />);
getByText(i18n.t('favorites.returnToLastSearch') as string).click();
expect(call.mock.calls.length).toBe(1);
});
It is not the most useful test, in my opinion. Yet, it did get jest off my back.

Related

Vitest -Unable to access the DOM inside a test

I am new to front-end testing, and I am trying to get a good grasp on it.
In the process, inside one project, I am creating a test for a Vue component. I want to test that the behaviour of the code is correct (The component has code inside the mounted() hook that has to perform basically some checks and an API call).
I want to check that the code reaches one method. Previously to that, the code creates a click event listener to one element in the DOM.
My test emulates a click event (triggers it), but it cannot assert that the proper method has been called after the click event.
This is due to it not finding the element in the DOM to which it has to add the event listener. It seems that the code cannot find anything inside the document (using .getElementById()).
I wonder why, and how I would resolve this, since I have been stuck here for hours and I haven't found any solution that could work here, even when I have learned some interesting things in the process. I will leave a code example with the code structure I have built:
Inside the component:
<template>
// ...
<button id = "myButton">Add</button>
</template>
<script>
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile ";
let classIESF = new classInExternalScriptsFile();
export default {
methods: {
setup: function () {
classIESF.setupMethod();
},
},
mounted() {
this.setupMethod();
},
};
</script>
Inside the scriptsFile
export class classInExternalScriptsFile {
setupMethod() {
let myButton = document.getElementById("myButton") // <-- getElementById() returns a null here
if (typeof myButton !== "undefined" && myButton !== null) {
myButton.onclick = () => { // <-- The test code complains because it cannot enter here
// Some lines...
this.mySuperMethod()
}
}
}
mySuperMethod() {
// API call etc.
}
}
Inside the .spec.js test file:
// imports...
import { classInExternalScriptsFile } from "#/scripts/externalScriptsFile.js";
describe("description...", () => {
const mySuperMethodMock = vi
.spyOn(classInExternalScriptsFile.prototype, "mySuperMethod")
.mockImplementation(() => {});
test("That the button performs x when clicked", () => {
let wrapper = mount(myComponent, {
props: ...,
});
let myButton = wrapper.find('[test-id="my-button"]');
myButton.trigger("click");
expect(mySuperMethodMock).toHaveBeenCalled(); // <-- The test fails here
}
}

Get child props in Jest? (NO ENZYME)

I'm trying to test whether a React button component's text changes on-click. This is roughly what gets returned inside the component I'm testing (let's call it RandComponent):
return (<>
...
<ButtonComponent
data-testid='testButton'
>
{getButtonText()}
</ButtonComponent>
</>)
I want to be able to access whatever getButtonText() returns in Jest. I've looked online and in enzyme you can do something like this in the test file:
it('test button text toggle', async ()=>{
let container;
await act(async ()=> container = render(<RandComponent/>));
const button = getByTestId('testButton');
expect(button.text()).toEqual(whatever getButtonText() is supposed to return);
}
My problem is that I can't use enzyme... is there a way to get the child prop text like in the last line (button.text()) in Jest?
This requires to stub ButtonComponent by means of Jest and assert props that were provided to it:
jest.mock('...ButtonComponent module...', () => jest.fn());
...
let buttonProps;
ButtonComponent.mockImplementation(props => {
buttonProps = props;
return <div>ButtonComponent</div>
});
container = render(<RandComponent/>));
...
expect(buttonProps.children).toBe(...);
This is boilerplate code that Enzyme is supposed to save from.

Jest testing the event target that was collected onClick

I am testing that myFunction is called when a button is clicked. myFunction actually just calls another function and passes some stuff to it, including the element that was clicked, which I expect to actually be the img inside the button.
E.g:
<button class="button" onClick=myFunction(someArgs)>
<img class="imageInsideButton" />
/>
myFunction = (someArgs) => (event) =>
this.props.someOtherFunction(
someArgs
event.target
);
In my test I am trying to check that someOtherFunction was called with the expected args, which includes event.target
function render(args, renderer = shallow) {
const component = renderer(<MyComponent {...args} />);
return {
component,
buttons: () => component.find(".button"),
imagesInsideButtons: () => component.find(".imageInsideButton"),
};
}
beforeEach(() => {
myProps = {
myFunction: jest.fn(),
};
});
it("Should call someOtherFunction with the correct args", () => {
const { buttons, imagesInsideButtons } = render(defaultArgs, mount);
const indexClicked = 1;
buttons().at(indexClicked).simulate("click");
expect(myProps.someOtherFunction).toHaveBeenCalledWith(
someArgs, imagesInsideButtons().at(indexClicked)
);
});
So in this test I am simulating a click of one of the buttons() (in this case the second one listed). I'm expecting the event target that was clicked to be the img that was wrapped inside this button.
This does sort of work, but the problem appears to be that my test expects a ReactComponent but instead gets an actual node:
Expected
the other args,
ReactWrapper {}
Received
the other args,
<button class="button"><img class"imageInsideButton" /></button>
It seems like the result is sort of OK, in terms of getting this event target, but the way in which I have written the test means these are not matching. How do I make this test work?
When simulating the click, you could pass a fake target, and expect THAT target.
buttons().at(indexClicked).simulate("click", { target: 999 });
// test
expect(myProps.someOtherFunction).toHaveBeenCalledWith(
someArgs, 999
);

clear mocks between tests

I fail to understand how jest works.
consider the following setup.
I have a "dependency" module like so :
dependency.js
function doSomething(y) {
console.log(y);
}
export {doSomething}
I have a module that uses this dependency :
MyModule.js
import { doSomething } from './dependency';
let alreadyDid;
export default (x) => {
if (alreadyDid)
throw "error";
doSomething(x * 2);
alreadyDid = true;
}
my test file look like the following (I'm using require & jest.resetModules in order to reset the module "state")
MyModule.test.js
jest.mock('./dependency', () => ({doSomething: jest.fn()}))
import { doSomething } from './dependency';
let myModuleDo;
describe('myModule', () => {
beforeEach(() => {
myModuleDo = require('./MyModule').default;
jest.resetModules();
});
it('calls the dependency with double the input', () => {
myModuleDo(2);
expect(doSomething).toBeCalledWith(4);
});
it('calls the dependency with double the input', () => {
myModuleDo(1);
expect(doSomething).toBeCalledWith(2);
});
});
The problem is that the second test always failed, with the actual count as 4, as if it remembers the first call.
when I switch between the 2, I get the opposite (2 instead of 4).
I tried using mockReset() in afterEach, but then the second test fail on "not called".
any idea what's happen here ?
UPDATE
I realized I need to replace the "import" with require in order to get a new mock in each test.
so now I'm confused - most of the jest examples are with "import" rather than "require". I almost don't see examples, or patterns explaining we need to use require in order to properly use the jest.fn().
What am I missing
One of the quick fix is to reset the doSomething.mock.calls = []; in beforeEach.
Try using doSomething.mockClear() in the before each. Also you shouldn't have to do this: myModuleDo = require('./MyModule').default; in the before each nor use jest.resetModules();. Also, if you want the mock doSomething to return a specific value for each test, you can set up the jest.fn() to return a value e.g. jest.fn(() => 'some value here')

React-jest-enzyme: testing callback of child component that calls another function first before calling the callback

I am testing wether a callback that is passed to a child component is called after my button in the child component is clicked. I simulate the react-bootstrap button, <Button></Button>, by using the .simulate('click') function.
The problem is that the onClick() function of my button calls another function called update() and that function calls the handleSave callback passed to my child component. The onKeyPress function of the <FormControl/> element also calls the update function of my component. Here is how I have my child component setup:
update(event) {
//Have to check to see if the key stroke is the enter key or if it comes from the button click.
if(event.charCode === 13 || event.type === 'react-click'){
// Have to use this get the ref value because this.refs.input.value doesn't work.
var input = ReactDOM.findDOMNode(this.refs.input);
input.value = '';
this.props.handleSave();
}
}
render(){
return(
<Form>
<FormControl type="text" ref="input" onKeyPress={this.update.bind(this)} placeholder="Enter Note" />
<Button onClick={this.update.bind(this)}>Submit </Button>
</Form>
)
}
That is why my update() function has a check to see if came from charCode==13, that is the charCode for the enter key, or the button click because both save the info that is in the <FormControl />
I have my test setup this way:
describe('Input', () => {
const mockHandleText = jest.fn();
const mockHandleSave = jest.fn();
const props = {handleSave: mockHandleSave}
let input = shallow(<Input {...props} />);
describe('when entering a note', () => {
beforeEach(() => {
input.find('Button').simulate('click', {
charCode: 13
});
});
it('adds the note to state', () => {
expect(props.handleSave).toHaveBeenCalled();
});
});
});
A weird thing is that I have to pass an object as a second parameter to the .simulate() function because if I don't it will give me an error saying cannot read charCode of undefined but when a pass an object, the object doesn't even have to have an event property, then it just says
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.
Also If I don't pass in the object with some property then it also breaks my other test that I have for a callback on the onChange function of my element. I left it out of the code sample for the sake of simplicity and just uploaded the code that is giving me problems. I am also using a bootstrap form with and . The full code is on my github at github.com/Alebron23.
Enzyme's shallow method doesn't render the whole DOM tree, just the most shallow level. You'll not be able to find nested children using it. In the docs for shallow (https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md), they discuss that if you need to assert any behavior on child components, you'll have to use something other than shallow().
Your other options are to either use render(), or more likely- since render() is static and you want to test side effects- to fully mount()
the component (https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md) instead.

Categories