How do I re-render a component in Jest? - reactjs

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');
});

Related

Jest testing that a React class method was called by componentWillMount

I am trying to write a test to assert that my class method is being called when the componentWillMount method fires when the component renders.
I have tried the Jest documentation in addition to researching this online. From the answers I've found (including on here) there seemed to be 2 possible methods of doing this.
The first was to:
shallow render the component
create a jest.fn of the class method I want to test,
call componentWillMount using wrapper.instance().componentWIllMount
assert that the method was called once
The second was to spy on the method I'm expecting to be called:
shallow render the component
set up the spy and assign to a constant e.g. functionSpy
call componentWillMount
assert the functionSpy was called how ever many times
The refresh method definitely fires whenever the component is rendered so I just need to work out how I can reflect this in a test.
The code base I am working on is for a civil service system so have to be really careful what I disclose, hopefully this will be enough for explaining the problem I'm having..
The class is structured:
export class Search extends AnErrorComponent {
static propTypes = {
.....
};
state = {
.....
}
componentWillMount(){
this.refresh();
}
refresh = () => {
.....
} // This is the method I'm trying to test
but can't seem to access/test.
search = () => {
.....
}
//etc
render(){
return(
...
);
}
}
To test this I've tried:
describe('Search component', () => {
it("should call the refresh method when the page loads", () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
wrapper.instance().refresh = jest.fn();
wrapper.update();
wrapper.instance().componentWillMount;
expect(wrapper.instance().refresh).toHaveBeenCalledTimes(1);
});
});
The result of running this test is:
● Search component › should call the refresh method when the page loads
expect(jest.fn()).toHaveBeenCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
I also tried:
describe('Search component', () => {
it("should call the refresh method when the page loads", () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
const refreshSpy = spyOn(Search.prototype, 'refresh');
wrapper.instance().componentWillMount;
expect(refreshSpy).toHaveBeenCalledTimes(1);
});
});
I get the error:
● Search component › should call the refresh method when the page loads
refresh() method does not exist
This refers to the spy I tried to create.
I've double checked and I have imported the Search component in addition to the component it inherits from. I have also tried using mount instead of shallow rendering; however to make this work I had to wrap the component in a provider otherwise an error would be thrown e.g.
<provider store={store}>
<Search />
</provider>
I still got the same results after when using mount and wrapping the component in a provider. Due to the spy error I tried console logging wrapper.instance() in both tests and noted that none of the class methods are listed anywhere if this helps? Any help on this would be greatly appreciated. (This is the first question I've posted on here so hopefully this makes sense).
** Just to add, when using jest.spyOn() I get TypeError: jest.spyOn is not a function. I am using Jest 21.2.1 which I read should allow me to use jest.spyOn() as it was added in V19. **
componentWillMount is a method on the class instance, not a property. You need to call it to trigger the effect:
describe('Search component', () => {
it("should call the refresh method when the page loads", () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
wrapper.instance().refresh = jest.fn();
wrapper.update();
wrapper.instance().componentWillMount(); // Calling the method
expect(wrapper.instance().refresh).toHaveBeenCalledTimes(1);
});
});
You need to call componentWillMount and spyOn the refresh function by Mock Implementation
describe('Search component', () => {
const store = makeStore();
const wrapper = shallow(<Search store={store}/>);
let refresh;
beforeAll(() => {
refresh = jest.spyOn(Search.prototype, 'refresh').mockImplementation(() => true);
});
it("should call the refresh method when the page loads", () => {
wrapper.instance().componentWillMount();
expect(refresh.mock.calls.length).toBe(1);
});
afterAll(() => {
refresh.mockRestore();
});
});

enzyme testing react with context api

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.

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.

Button disabled attribute is not updated correctly when two renders of the same component are performed

I have just started using react-testing-library, so am guessing this is ultimately down to user error, but I am seeing the following behaviour which does not make sense to me.
For the following test run in a freshly created CRA application and using jest-dom 3.0.0 and react-testing-library 5.4.4:
import React from "react";
import { render } from "react-testing-library";
import "jest-dom/extend-expect";
import Component from "./Component";
describe("Verify UI state based on jobs", () => {
it("mounts with no previous data", () => {
const { getByTestId } = render(<Component data={[]} />);
expect(getByTestId("refresh-button")).toBeDisabled();
});
it("mounts with previous data", () => {
const { getByTestId } = render(<Component data={["hi"]} />);
expect(getByTestId("refresh-button")).not.toBeDisabled();
});
});
/*
const Component = props => {
return (
<div>
<button data-testid="refresh-button" disabled={props.data.length === 0}>
Refresh
</button>
</div>
);
};
*/
As things are I get the following failure:
Verify UI state based on jobs › mounts with previous data
expect(element).not.toBeDisabled()
Received element is disabled:
<button data-testid="refresh-button" disabled="" />
13 | it("mounts with previous data", async () => {
14 | const { getByTestId } = render(<Component data={["hi"]} />);
> 15 | expect(getByTestId("refresh-button")).not.toBeDisabled();
| ^
16 | });
17 | });
18 |
at Object.toBeDisabled (src/Component.test.js:15:47)
However, if I disable the first test, the second now passes as it should. If I reorder things, the first test always passes and the second always fails, even if the first test is the 'mount with pervious data' test.
Not sure if this is an issue in the test library, jest-dom or my code, but any advice on how to correctly construct these tests would be appreciated.
The docs currently state that when render is called "the queries from dom-testing-library are automatically returned with their first argument bound to the rendered container".
As it turns out this is a bug in the documentation as the queries are actually bound to document.body if no container is passed to render (code here and here).
react-testing-library uses the DOM and unless cleanup is called between tests the DOM elements from earlier tests will still be there and will be included in later query results.
In this case both Component elements exist in document.body during the second test and since getByTestId queries document.body it ends up finding both of them and when it finds more than one it returns the first one it finds.
That means that the Component from the first test is being returned by getByTestId in the second test which causes the test to fail because the first component is disabled.
To resolve the issue make sure to call cleanup after each test to remove any DOM elements that were added during previous tests:
import React from "react";
import { render, cleanup } from "react-testing-library";
import "jest-dom/extend-expect";
afterEach(cleanup); // clean up the DOM after each test
describe("Verify UI state based on jobs", () => {
it("mounts with no previous data", () => {
const { getByTestId } = render(<Component data={[]} />);
expect(getByTestId("refresh-button")).toBeDisabled(); // SUCCESS
});
it("mounts with previous data", () => {
const { getByTestId } = render(<Component data={["hi"]} />);
expect(getByTestId("refresh-button")).not.toBeDisabled(); // SUCCESS
});
});

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