Describe method can only pass with 1 test unless re-rendering each component again and again - reactjs

I'm trying to figure out why my test - which passes when ran alone - is failing whenever the describe block contains more than 1 test. Take this example, which I've taken from my real code and simplified:
describe('Create Account Form', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
const password1 = container.querySelector('input[name="password1"]');
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument(); // fails
});
});
The 2nd test fails, but passes only when commenting out the first test, or re-rendering the container again in the test like this:
it('Another test', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
expect(email).toBeInTheDocument(); // passes
});
Why does this have to happen? I would much rather not have to re-render the container and declare new variables inside each test block.
Thank you

RTL will unmount React trees that were mounted with render in afterEach hook. See cleanup.
Please note that this is done automatically if the testing framework you're using supports the afterEach global and it is injected to your testing environment (like mocha, Jest, and Jasmine).
Move the render code into beforeEach or individual test case. So that we can create react trees before each test case. Isolate test cases from each other, using their own test data without affecting the rest.
E.g.
index.tsx:
import React from 'react';
export function Example() {
return (
<div>
<input name="email" />
<input name="password1" />
</div>
);
}
index.test.tsx:
import { render } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import React from 'react';
import { Example } from './';
describe('70753645', () => {
let email, password1, allInputs;
beforeEach(() => {
const { container } = render(<Example />);
email = container.querySelector('input[name="email"]');
password1 = container.querySelector('input[name="password1"]');
allInputs = container.querySelectorAll('input');
});
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument();
});
});
Test result:
PASS stackoverflow/70753645/index.test.tsx (9.222 s)
70753645
✓ Should render all fields (24 ms)
✓ Another test (3 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.717 s
package versions:
"#testing-library/react": "^11.2.2",
"jest": "^26.6.3",

Related

How can I test an input with Jest

I've been trying to figure out how to test different input methods but since I am new to this test methodology, I cannot get even close to the answer. Here is what I have:
const App = (props) => {
const newGame = props.newGame;
const [typeracertext, setTyperacertext] = useState(props.typeracertext);
const [wholeText, setWholeText] = useState("");
const onChange = (e) => {
//here I have code that read the input and is comparing it with variable - typeracertext and if so, it sets the property wholeText to that value
};
return (
<input ref={(node) => this.textInput = node} placeholder="Message..." onChange={onChange}></input>
);
}
so what I am trying to figure out is a test that should set the typeracertext to a certain value (for example "This is a test), and set the input value to "This" so if it passes the onChange() check it should set wholeText to "This". I hope that makes sense.
This is the best I could get and I don't have an idea what should I write on "expect".
test('Test the input value', () => {
const node = this.textInput;
node.value = 'This';
ReactTestUtils.Simulate.change(node);
expect()
});
Since this is a react app, I'll advice you take advantage of react testing library to make this easy
import React from 'react';
import { fireEvent, render, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
// In describe block
test('Test input component', () => {
const onChange = jest.fn();
render(<InputComponent onChange={onChange} data-test-id="input" />);
const input = screen.getByTestId('input');
fireEvent.change(input, { target: { value: 'a value' } });
// You can also do this with userEvent
userEvent.type(input, 'test')
// Check if change event was fired
expect((input as HTMLInputElement).onchange).toHaveBeenCalled();
});
See documentation here

Unit testing a custom hook to ensure that it calls another hook

How can we ensure that a custom hook actually calls a method exposed by another hook?
Let's say, I have a custom hook useName that internally leverages useState.
import { useState } from 'react'
export const useName = () => {
const [name, setState] = useState()
const setName = (firstName: string, lastName: string) => setState([firstName, lastName].join(' '))
return {name, setName}
}
I need to assert that calling setName actually calls `setState'. My test case is written as following:
/**
* #jest-environment jsdom
*/
import * as React from 'react'
import { renderHook, act } from '#testing-library/react-hooks'
import { useName } from './useName'
jest.mock('react')
const setState = jest.fn()
React.useState.mockReturnValue(['ignore', setState]) //overwriting useState
test('ensures that setState is called', () => {
const {result} = renderHook(() => useName())
act(() => {
result.current.setName("Kashif", "Nazar") //I am expecting this to hit jest.fn() declared above.
})
expect(setState).toBeCalled()
})
and I get the following result.
FAIL src/useName.test.ts
✕ ensures that setState is called (3 ms)
● ensures that setState is called
TypeError: Cannot read property 'setName' of undefined
18 |
19 | act(() => {
> 20 | result.current.setName("Kashif", "Nazar")
| ^
21 | })
22 |
23 | expect(setState).toBeCalled()
at src/useName.test.ts:20:24
at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:22380:12)
at act (node_modules/react-dom/cjs/react-dom-test-utils.development.js:1042:14)
at Object.<anonymous> (src/useName.test.ts:19:5)
at TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/#jest/core/build/runJest.js:404:19)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 0.32 s, estimated 1 s
Ran all test suites.
Is this possible, and am I doing it the right way?
You should test the returned state instead of the implementation detail(setState). Mock may destroy the functionality of setState. This causes the test case to pass, but the code under test will fail at the actual run time. And mock also make the test vulnerable, when your implementation details change, your test cases have to change accordingly such as mock the new object.
I only test if the interface is satisfied, no matter how the implementation details change, right
useName.ts:
import { useState } from 'react';
export const useName = () => {
const [name, setState] = useState('');
const setName = (firstName: string, lastName: string) => setState([firstName, lastName].join(' '));
return { name, setName };
};
useName.test.ts:
import { renderHook, act } from '#testing-library/react-hooks';
import { useName } from './useName';
describe('70381825', () => {
test('should pass', () => {
const { result } = renderHook(() => {
console.count('render');
return useName();
});
expect(result.current.name).toBe('');
act(() => {
result.current.setName('Kashif', 'Nazar');
});
expect(result.current.name).toBe('Kashif Nazar');
act(() => {
result.current.setName('a', 'b');
});
});
});
Test result:
PASS examples/70381825/useName.test.ts
70381825 - mock way
○ skipped should pass
70381825
✓ should pass (29 ms)
console.count
render: 1
at examples/70381825/useName.test.ts:31:15
console.count
render: 2
at examples/70381825/useName.test.ts:31:15
console.count
render: 3
at examples/70381825/useName.test.ts:31:15
Test Suites: 1 passed, 1 total
Tests: 1 skipped, 1 passed, 2 total
Snapshots: 0 total
Time: 1.251 s, estimated 8 s
Now, if you insist to use a mock way. You should only mock useState hook of React. jest.mock('react') will create mocks for all methods, properties, and functions exported by React, and this will break their functions.
E.g.
useName.test.ts:
import { renderHook, act } from '#testing-library/react-hooks';
import { useName } from './useName';
import React from 'react';
jest.mock('react', () => {
return { ...(jest.requireActual('react') as any), useState: jest.fn() };
});
describe('70381825 - mock way', () => {
test('should pass', () => {
const setState = jest.fn();
(React.useState as jest.MockedFunction<typeof React.useState>).mockReturnValue(['ignore', setState]);
const { result } = renderHook(() => {
console.count('render');
return useName();
});
act(() => {
result.current.setName('a', 'b');
});
expect(result.current.name).toBe('ignore');
expect(setState).toBeCalled();
act(() => {
result.current.setName('c', 'd');
});
});
});
Test result:
PASS examples/70381825/useName.test.ts (7.885 s)
70381825 - mock way
✓ should pass (29 ms)
70381825
○ skipped should pass
console.count
render: 1
at examples/70381825/useName.test.ts:14:15
Test Suites: 1 passed, 1 total
Tests: 1 skipped, 1 passed, 2 total
Snapshots: 0 total
Time: 8.487 s
Ok. Do you know why the mock way only renders one time and the other way renders three times when we call the setName? As I said earlier.

React testing library test each test case as separate

I am implementing React testing library as follows:
const onCardClick = jest.fn();
afterEach(cleanup);
describe('Card', () => {
test('renders quote card and allows click', () => {
const { getByTestId } = render(<Card onClick={onCardClick} />);
fireEvent.click(getByTestId('card-root'));
expect(onCardClick.mock.calls.length).toBe(1);
});
test('renders card passes index on click', () => {
const { getByTestId } = render(
<Card onClick={onCardClick} index={2} />
);
fireEvent.click(getByTestId('card-root'));
expect(onCardClick.mock.calls.length).toBe(1);
expect(onCardClick.mock.calls[0][0]).toBe(2);
});
});
Could there be a way that both test cases are not dependent on each other, as currently when first is run the test is passed as it is clicked one time, when second test is run now click is expected as 2(It should be expected as 1 only as a separate click) . I want these test cases not to be dependent on each other.

react-testing-library | Cannot Split Test into smaller chunks inside describe method

I'm learning about unit testing React components using react-testing-library
I have the component rendering correctly, however, when I aim to break the test into smaller chunks inside a describe() function. The test breaks and here's why.
Current only one or the other test() passes but not both
import React from 'react'
import 'react-testing-library/cleanup-after-each'
import { render, fireEvent } from 'react-testing-library'
import Quantity from '../components/Quantity'
describe('Quantity Component', () => {
const { container, getByTestId } = render(<Quantity />)
// first test
test('checks that quantity is never 0', () => {
expect(getByTestId('quantity')).not.toBe('0')
})
// second test
test('checks for the initial product quantity count', () => {
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
When trying to run both tests it errors:
Unable to find an element by: [data-testid="quantity"]
[data-testid="quantity"] is just an attribute that I passed inside my desired JSX tag.
The test passes when running only the first or second test but not both concurrently.
What am I missing here?
Cross-contamination is strictly discouraged in unit testing.
The problem is that a setup occurs only once per Quantity Component suite, while it should be done for each test. This is what beforeEach is for:
describe('Quantity Component', () => {
let container, getByTestId;
beforeEach(() => {
({ container, getByTestId } = render(<Quantity />));
});
...
You need to also use an afterEach cleanup.
describe('your tests', () => {
afterEach(cleanup);
beforeEach(() => ({container, getById} = render(<Quantity />))
it('does something', () => {
expect(getByTestId('quantity')).toHaveTextContent(0);
}
}
I suggest you call the render inside your it clauses, it keeps the tests easier to manage:
describe('Quantity Component', () => {
test('checks that quantity is never 0', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).not.toBe('0')
})
test('checks for the initial product quantity count', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
The added advantage is that if for some reason one of your tests needs to run with different props you can do that more easily with this setup.

React, Enzyme and Istanbul - code coverage missing functions being executed by tests

I'm testing a React component with 3 functions, I've written tests that use all 3 of these functions and the test pass however in the code coverage report I'm only getting 33%. The component is below.
const AddWidget = ({ }: Props) => {
var id = generateGuid().slice(0, 8);
var showWidget = () => {
document.getElementById(id).style.zIndex = "10";
}
var hideWidget = () => {
document.getElementById(id).style.zIndex = "200";
}
return (
<div onMouseLeave={hideWidget} onMouseEnter={showWidget} className="addWidget" >
<div className="divide" />
<div id={id} className="cover" />
<div>
<CircularButton type={CircularButtonType.DarkAdd} small={true} />
<p>Add a widget here</p>
</div>
</div>
);
}
export default AddWidget;
And my tests...
import * as React from 'react';
import * as Enzyme from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import AddWidget from './AddWidget';
Enzyme.configure({ adapter: new Adapter() });
const addWidget = Enzyme.mount(<AddWidget />, { attachTo: document.body });
describe('AddWidget', () => {
test('renders without crashing', () => {
expect(addWidget.find(AddWidget).length).toBe(1);
});
test('should render parent element with class "addWidget"', () => {
expect(addWidget.find('div').at(0).hasClass('addWidget')).toBe(true);
});
test('should cover component from view when mouse is not hovering', () => {
addWidget.simulate('mouseEnter');
addWidget.simulate('mouseLeave');
var covers = document.getElementsByClassName('cover');
for (var n = 0; n < covers.length; n++) {
expect((covers[n] as HTMLElement).style.zIndex).toBe("200");
}
});
test('should show component from view onMouseEnter', () => {
addWidget.simulate('mouseEnter');
var covers = document.getElementsByClassName('cover');
for (var n = 0; n < covers.length; n++) {
expect((covers[n] as HTMLElement).style.zIndex).toBe("10");
}
});
});
The tests specify it's the showWidget and hideWidget functions that aren't being tested but the last 2 tests definitely run these functions otherwise the tests wouldn't pass.
Is this a code coverage bug? Is it that it doesn't like that I'm using pure Javascript functions or am I fundamentally misunderstanding function code coverage?
EDIT: coverage report images below
I found what the issue was, I was running the tests with the command react-scripts test --coverage --coverageDirectory=output/coverage --coverageReporters text --env=jsdom which was updating the cobertura.xml file but not any of the html. I thought the html read the coberatura file and displayed it but that's not the case. Adding the html flag to coverageReporters fixed the issue.

Resources