enzyme testing react with context api - reactjs

I'm trying to test a form. When the form is submitted it should set a state on error: true and then a div with an error information should appear. My test looks like this:
outer = shallow(<Search />);
it("should not submit a form and display an error if title or author is empty", () => {
const Children = outer.props().children({});
const wrapper = mount(Children);
const button = wrapper.find("form button");
button.simulate("submit", {
preventDefault() {
outer.setState({ error: true });
}
});
expect(wrapper.find("div.error")).toHaveLength(1);
});
Unfortunately it doesn't work. I am new to unit testing and I have no idea if I'm doing it correctly and how should I fix that.
I think I also should get somehow inputs values but don't know how.

This is the sample to set value to input elements:
it('Should capture firstname correctly onChange', function(){
const component = mount(<Form />);
const input = component.find('input').at(0);
input.instance().value = 'hello';
input.simulate('change');
expect(component.state().firstname).toEqual('hello');
})
But it may not work because of different other reasons, make sure you have initialized enzyme components in beforeAll(). Try to read enzyme's examples about this topic.

Related

Should you render components / select elements in each `test()/it()` block or globally?

In react-testing-library you have to render your react component before executing some tests on its elements.
For several tests on the same component, should you avoid
rendering the component multiple times? Or do you have to render it in each
test()/it() block?
Should you select elements of the component (e.g. button) in each test()/it() block, or should you lift the selection, and select only once?
Does it have any impact on the execution time of the tests?
Is one of the approaches a best practice/antipattern?
Why does the last example fail?
For the basic component I have the following testing approaches:
function MyComponent() {
return (
<>
<button disabled>test</button>
<button disabled>another button</button>
</>
);
}
e.g.
describe("MyComponent", () => {
it("renders", async () => {
const { getByRole } = render(<MyComponent />);
const button = getByRole("button", { name: /test/i });
expect(button).toBeInTheDocument();
});
it("is disabled", async () => {
// repetetive render and select, should be avoided or adopted?
const { getByRole } = render(<MyComponent />);
const button = getByRole("button", { name: /test/i });
expect(button).toBeDisabled();
});
});
vs.
describe("MyComponent", () => {
const { getByRole } = render(<MyComponent />);
const button = getByRole("button", { name: /test/i });
it("renders", async () => {
expect(button).toBeInTheDocument();
});
it("is disabled", async () => {
expect(button).toBeDisabled();
});
});
I would expect the second approach to have a faster execution time since the component has to be rendered only once, but I don't know how to measure it and if it is an anti-pattern?
While it seems to be more DRY, if I add another toBeInTheDocument check, it fails.
Why is this the case?
describe("MyComponent", () => {
const { getByRole } = render(<MyComponent />);
const button = screen.getByRole("button", { name: /test/i });
const button2 = screen.getByRole("button", { name: /another button/i });
it("renders", async () => {
expect(button).toBeInTheDocument(); //ok
});
it("is disabled", async () => {
expect(button).toBeDisabled(); // ok
});
it("renders second button", async () => {
expect(button2).toBeInTheDocument(); // fails: element could not be found in the document
});
});
So this approach seems to be more error-prone!?
Each test should be as atomic as possible, meaning that it should not be using anything that other tests are also using and should run with a fresh state. So relating that to your examples, the first one would be the correct pattern.
When you have a test suite that contains sharable state between unit tests e.g. objects or environment variables, the test suite is very prone to errors. The reason for that is; if one of the unit tests happens to mutate one of the shared objects; all of the other unit tests will also be affected by this, causing them to exhibit unwanted behaviour. This can result in test failures where the code is technically correct or even set up landmines for future developers where the addition of new tests which are correct would still result in failures, hence causing major headaches in figuring out why this is happening.
The only exception to this rule would be immutable primitive variables (e.g. string, number, boolean with the use of const keyword) as tests will not be able to mutate them and they are useful for storing reusable ids, text etc.
Ofcourse, repeating the setup of each unit test can make them really clunky, that's why jest offers the beforeEach, beforeAll, afterEach and afterAll functions to extract the repeating logic. However, this opens up the vulnerability of shared state, so do be careful and make sure that all state is refreshed before any tests are kicked off. Ref.
For the last question as to why your last unit test in the last example is failing - it appears that you are using getByRole to look for button text. You should be using getByText instead. getByRole is used with role attributes (e.g. <button role="test">test</button>) which you don't seem to be using.

How do I re-render a component in Jest?

I have a beforeEach() that renders my app:
describe('...', () => {
beforeEach(() => {
render(<App />);
});
...
});
I am currently going through a config change in my app. config was previously a boolean value in localStorage and now it has a more appropriate value like prod and dev. My app checks the old boolean values when it first loads and it changes them to their respective non-boolean value - here's the mapping:
true -> prod
false -> dev
I am trying to write tests for this transformation in Jest. The idea behind the first test is to set the localStorage item, re-render the app, and check the the localStorage item has been updated correctly:
it('should show dev config when using old boolean value', () => {
const toggle = screen.getByTestId('toggle');
expect(toggle).toBeChecked();
localStorage.setItem('config', false);
// re-render app and check localStorage
render(<App />);
const toggle2 = screen.getByTestId('toggle');
expect(toggle2).not.toBeChecked();
expect(localStorage.getItem('config')).toEqual('dev');
});
However, this test throws an error presumably because the app is now rendered twice instead of once:
TestingLibraryElementError: Found multiple elements by: [data-id="toggle"]
How do I re-render the app so that it isn't duplicated in the test environment?
If you are using react-testing-library you can use the reredner method that is returned from the initial render. You can use this method to call render again and provide the same container
that your first call created (you can even pass new props).
Docs: https://testing-library.com/docs/example-update-props/
Example:
it('should add the fixed width class if position is fixed', () => {
const {rerender} = render(<PageNavigation position="fixed-left" fixedWidth="test-123" />);
const list = screen.getByTestId('page-navigation'); // Or however you're selecting
expect(list).toHaveClass('test-123');
rerender(<PageNavigation position="fixed-right" fixedWidth="test-456" />);
// Sanity check to ensure the first class was removed
expect(list).not.toHaveClass('test-123');
expect(list).toHaveClass('test-456');
});
You could use the rerender function from react-testing-library : https://testing-library.com/docs/marko-testing-library/api/#rerender
In your test :
import { ..., rerender } from '#testing-library/react';
it('should show dev config when using old boolean value', () => {
const toggle = screen.getByTestId('toggle');
expect(toggle).toBeChecked();
localStorage.setItem('config', false);
// re-render app and check localStorage
rerender(<App />);
const toggle2 = screen.getByTestId('toggle');
expect(toggle2).not.toBeChecked();
expect(localStorage.getItem('config')).toEqual('dev');
});

How to test outside click event functionality for React using Jest and Enzyme

I have a component which detect outside Click which is implemented like this https://medium.com/#pitipatdop/little-neat-trick-to-capture-click-outside-react-component-5604830beb7f
and i am using that component for Dropdown component so that it must close on outside click.
I followed this comment to unit test it but still no luck.
https://github.com/airbnb/enzyme/issues/426#issuecomment-431195329
outside click is not been captured please help me with this.
My test case is like this
it("should check outside click", () => {
const outerNode = document.createElement('div');
outerNode.className = "outerDiv";
document.body.appendChild(outerNode);
wrapper = mount(<Dropdown {...props}>{props.children}</Dropdown>, { attachTo: outerNode })
const obj = wrapper.find("button")
obj.instance().dispatchEvent(new Event('click', { bubbles: true }));
expect(wrapper.state().activated).toBe(true);
outerNode.dispatchEvent(new Event('click', { bubbles: true })); // this is not working as expected
expect(wrapper.state().activated).toBe(false);
}
outside element click is not dispatched.

Testing window.name in Jest

I'm working on a simple React app that has opens a new tab with window.open() for a read-only 'presenter' view. I'm writing a test for the conditional rendering in Jest but can't seem to get window.name to change correctly. Specifically, I'm testing the line in which handleStart() is called.
Here's the code that is being tested:
componentDidUpdate() {
localStorage.setItem('timeRemaining', this.state.timeRemaining);
if (window.name === 'presenter' && this.state.timeRemaining > 0) {
this.handleStart();
}
}
And here is the test as I currently have it:
it('checks the window and state to call #handleStart if started in parent view', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleStart');
global.window.name === 'presenter';
wrapper.setState({ timeRemaining: 100 });
wrapper.update();
expect(spy).toHaveBeenCalled();
});
Turns out I'm a fool and was attempting to assign global.window.name wrong. The test should read
it('checks the window and state to call #handleStart if started in parent view', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleStart');
global.window.name = 'presenter';
wrapper.setState({ timeRemaining: 100 });
wrapper.update();
expect(spy).toHaveBeenCalled();
});
Leaving this question up as it appears to be unique on here and might be helpful to someone. For reference, if you're testing the Window object in Jest you use 'global' as shown here in lieu of 'window'

How can I change the value of input in react test

I'd like to test a form of my react-app.
I have removed 'disabled' property of button when doing following things.
const component = TestUtils.renderIntoDocument(<Mycomponent/>);
const myDOM =findDOMNode(component);
const input = myDOM.querySelector('input');
input.value = "2017-11-11";
let submitButton = myDOM.querySelector('button');
TestUtils.Simulate.click(submitButton);
...
const newlyAddedDate = record.querySelector('#date').innerHTML;
console.log("newlyAddedDate:"+newlyAddedDate);
but the output in console is
"newlyAddedDate:"
This react-app performs correctly in chrome.
I believe it's the problem of
"input.value="2017-11-11";
This sentence failed to change the value in the inputbox.
So how can i set the value in a inputbox when doing react-test?
Here's the repo of this app
https://github.com/zzbslayer/ChargeAccount-React
Well, it totally depends on what libraries you use for testing. Here's a sample using Mocha and Enzyme combination.
beforeEach(() => {
_spies = {}
_props = {
propOne: 'somepropvalue',
...
}
_wrapper = mount(<Your Component {..._props} />)
})
it('Should change the input box', () => {
let promptInput = _wrapper.find('textarea')
promptInput.simulate('change', { target: { value : 'sample2' } })
})
In before method you initialize your component, by passing all the props down. Then inside your test you find that text box inside the wrapper using a selector or HTML id of that element. Then you simulate a change event with the new value.
Notice that Jest has become a defacto framework for testing nowadays in most of the react starter boilerplate. In that case the concepts remains the same, but the syntax differs.

Resources