How to test componentWillReceiveProps method in react component - reactjs

I want to test the componentWillReceiveProps method and see the path for current web page. I tried to use the following code to test the method, but it always throw an error.
Invariant Violation: A <Router> may have only one child element
I'm wondering what I should do to solve that error? Here is what I have tried so far.
class WrappedComponent extends React.Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.user_id) {
this.props.history.replace('/login');
}
}
render() {
return <div>WrappedComponent</div>
}
}
describe('AuthenticationHOC', () => {
describe('authenticationRequired', () => {
let props;
const shallowWrapper = () => {
return shallow(
<MemoryRouter>
withRouter(authenticationRequired(<WrappedComponent {...props} />))
</MemoryRouter>
)
}
it('renders the wrapped component', () => {
let wrapper = shallowWrapper()
expect(wrapper.contains(<WrappedComponent {...props} />)).toBe(true)
})
describe("when user_id doesn't exist", () => {
beforeEach(() => {
props = {
user_id: ''
}
});
it('should go to the login page', () => {
//how to test the method componentWillReceiveProps
let wrapper = shallowWrapper().dive();
wrapper.setProps({
user_id: '12312412'
});
// expect(spy.calledOnce).toBe(true);
expect(window.href).toBe("/login");
})
})
describe("when user_id do exist", () => {
beforeEach(() => {
props = {
user_id: 'something'
}
});
it('should not go to other page', () => {
let wrapper = shallowWrapper();
expect(window.href).toBe("/");
})
})
})

You shouldn't mock componentWillReceiveProps as this is an implemntation detail that we don't care about.
Instead you will need to mock out history somehow (not sure, look at react-router docs maybe) or hopefully you can just check the current href as hopefully when you use history.replace it might just change the href immediately.
Use enzymes dive() to dive through your Higher order components and use setProps on the wrapper component.
You might need to chain dive() again depending on how many HOC's you have wrapped.
// TODO add tests that verify history.replace was called
describe("when user_id doesn't exist", () => {
beforeEach(() => {
props.user_id = ''
});
const wrapper = shallowWrapper().dive();
const user_id = 'testId';
wrapper.setProps({ user_id });
expect(window.href).toBe('/login');
})

Related

How to test a component that is conditionally rendered based on a hook value?

I am working on a React Native application and am very new to testing. I am trying to mock a hook that returns a true or false boolean based on the current user state. I need to mock the return value of the authState variable, and based on that, I should check if the component is rendered or not. But the jest mock is returning the same value only
useAuth.ts
export const useAuthState = () => {
const [authState, setAuthState] = useState<AuthState>();
useEffect(() => {
return authentication.subscribe(setAuthState);
}, []);
return authState;
};
MyComponent.tsx
export const MyComponent = () => {
const authState = useAuthState();
if (!authState) {
return null;
}
return <AnotherComponent />
}
MyComponent.test.tsx
import { MyComponent } from "./MyComponent"
jest.mock('../use-auth-state', () => {
return {
useAuthState: () => false,
};
});
const TestComponent = () => <MyComponent />
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
let testRenderer: ReactTestRenderer;
act(() => {
testRenderer = create(<TestComponent />);
});
const testInstance = testRenderer.getInstance();
expect(testInstance).toBeNull()
})
})
This is working fine. But, I am not able to mock useAuthState to be true as this false test case is failing. Am I doing it right? I feel like I am messing up something.
You want to change how useAuthState is mocked between tests, right? You can set your mock up as a spy instead and change the mock implementation between tests.
It's also a little more ergonomic to use the render method from react-testing-library. The easiest way would be to give your component a test ID and query for it. Something like the below
import { MyComponent } from "./MyComponent"
import * as useAuthState from '../use-auth-state';
const authStateSpy = jest.spyOn(useAuthState, 'default');
describe('MyComponent', () => {
it('Should return null if the authState is null', () => {
// you can use .mockImplementation at any time to change the mock behavior
authStateSpy.mockImplementation(() => false);
const { queryByTestId } = render(<MyComponent />;
expect(queryByTestId('testID')).toBeNull();
})

"Objects are not valid as a React child (found: object with keys {type, props})."

While running the test for the below component, I get the error message Objects are not valid as a React child (found: object with keys {type, props}). If you meant to render a collection of children, use an array instead
The above issue happens only when I use mount. With shallow it works.
import { createMock } from "ts-auto-mock";
interface Props {
incidentId: string;
errorMessage:JSX.Element;
problemType:IncidentSummaryProblemTypeEnum;
}
describe("incidentdetails tests", () => {
let mock : Props;
beforeAll(async () => {
await Messages.init("en");
});
beforeEach(() => {
mock = createMock<Props>();
});
afterEach(() => {
jest.clearAllMocks();
});
it("test component incidentdetails", async () => {
await act(async () => {
const wrapper = mount(<Provider store={mockConnectorStore}><IncidentDetails {...mock}/></Provider>);
expect(wrapper).toMatchSnapshot();
});
});
});
Below is the component
interface Props {
incidentId: string;
errorMessage:JSX.Element;
problemType:IncidentSummaryProblemTypeEnum;
}
export const IncidentDetails: React.FC<Props> = (props) => {
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
<ErrorPanel message={errorMessage || props.errorMessage} setDisableButton={setDisableButton}/>
<DetailTemplate
breadcrumbItems={breadCrumbs(problemType)}
>
<DetailContent
/>
</DetailTemplate>
</>
);
};
});
I don't see any issue on the console while the component is getting rendered on the browser. I see this issue only while running test

How do I test next/link (Nextjs) getting page redirect after onClick in React?

I want to test the page get redirected after click on div, but I dont know how, this is my code. Thank you so much
<div className="bellTest">
<NextLink href="/notifications">
<Bell />
</NextLink>
</div>
test.tsx
jest.mock('next/link', () => {
return ({ children }) => {
return children;
};
});
describe('Test of <HeaderContainer>', () => {
test('Test the page redirect after click', async done => {
const wrapper = mount( <HeaderComponent /> );
await wrapper
.find('.bellTest')
.at(0)
.simulate('click');
// expect the page getting redirect
});
});
Instead of mocking next/link you can register a spy on router events, and check if it was called.
The test will look like this:
import Router from 'next/router';
describe('Test of <HeaderContainer>', () => {
const spies: any = {};
beforeEach(() => {
spies.routerChangeStart = jest.fn();
Router.events.on('routeChangeStart', spies.routerChangeStart);
});
afterEach(() => {
Router.events.off('routeChangeStart', spies.routerChangeStart);
});
test('Test the page redirect after click', async done => {
const wrapper = mount(<HeaderComponent />);
await wrapper
.find('.bellTest')
.at(0)
.simulate('click');
expect(spies.routerChangeStart).toHaveBeenCalledWith('expect-url-here');
});
});

How to test functions used in a stateless component that's defined outside of the component's scope in Jest/Enzyme?

I'm trying to write a test for the following:
import React from 'react'
import Popup from 'some-library'
const popupConfig = {
home: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
},
settings: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
}
}
const closePopup = () => {
Popup.closePopup()
}
const toggleNewPopup = () => {
Popup.togglePopup('some-popup')
}
const GetStartedPopup = ({ moduleName }) => {
if (!Object.keys(popupConfig).includes(moduleName)) return null
const {
popupValue = 'Hi there!',
popupStyle = 'warning',
popupMessage = 'Get Started',
popupBtnFunction = toggleNewPopup
} = popupConfig[moduleName]
return (
<Popup
popupValue={popupValue}
popupStyle={popupStyle}
popupBtnValue={popupMessage}
popupBtnStyle="neutral"
popupBtnFunction={popupBtnFunction}
xPopup={closePopup}
/>
)
}
export default GetStartedPopup
The objective of the test is to make sure that the closePopup and toggleNewPopup functions are called. I'm doing the following to do that for the closePopup function:
import React from 'react'
import { mount } from 'enzyme'
import { Popup } from 'some-library'
import GetStartedPopup from 'widgets/getStartedPopup'
describe('<GetStartedPopup/>', () => {
let wrapper
let props
beforeEach(() => {
props = {
page: 'home'
}
wrapper = mount(<GetStartedPopup {...props}/>)
})
it('should render the component without crashing', () => {
expect(wrapper).toBeDefined();
})
it('should call closePopup', () => {
const spy = jest.spyOn(wrapper.instance(), 'closePopup');
wrapper.instance().closePopup();
expect(spy).toHaveBeenCalledTimes(1);
})
afterEach(() => {
wrapper.unmount()
})
})
I went through the docs for spyOn and other SO threads that tackle issues like this but couldn't resolve how to test the closePopup and toggleNewPopup functions for my case here. When I run the test case written above I get this: TypeError: Cannot read property 'closePopup' of null. What would be the correct way to write the test to make sure that the two functions are called?
Funny that I ran into this myself at work in regards to wrapper.instance() doc
To return the props for the entire React component, use wrapper.instance().props. This is valid for stateful or stateless components in React 15.. But, wrapper.instance() will return null for stateless React component in React 16., so wrapper.instance().props will cause an error in this case.
As for the 3rd party library. You should be mocking any collaborators that your component uses.
import { Popup } from 'some-library';
describe('<GetStartedPopup />', () => {
let wrapper;
jest.mock('some-library', () => {
Popup: jest.fn(),
});
const initialProps = {
page: 'home'
};
const getStartedPopup = () => {
return mount(<GetStartedPopup {...initialProps});
};
beforeEach(() => {
Popup.mockClear()
wrapper = getStartedPopup();
};
it('should call closePopup', () => {
expect(Popup.closePopup()).toHaveBeenCalledTimes(1);
});
...
});

setState value used in componentDidMount is not reflected in Enzyme test

Component.js
import React from 'react'
import request from 'superagent'
export default React.createClass({
getInitialState() {
return {cats: []}
},
componentDidMount() {
request('/api', (err, res) => {
if (err) return;
this.setState({
cats: res.body.results
})
})
},
render() {
let cats = this.state.cats
let catsList = (
<ul>
{cats.map((c) => <li key={c.id}>cat</li>)}
</ul>
)
return (
<div>
{cats.length ? catsList : null}
</div>
)
}
})
Component.test.js
jest.unmock('../app.js')
jest.unmock('superagent')
import React from 'react'
import {mount} from 'enzyme'
import nock from 'nock'
import App from '../app.js'
describe('App', () => {
let ajaxFn
beforeEach(() => {
ajaxFn = nock('http://localhost')
.get('/api')
.reply(200, {
results: [{id: 1}, {id: 2}, {id: 3}]
})
})
it('renders correct number of cats', () => {
let wrapper = mount(<App />)
expect(wrapper.state('cats').length).toBe(3)
})
})
The test does not pass. wrapper.state('cats').length is always 0.
I understand that setState doesn't guarantee to update state immediately,
however if I log 'cats' in the component, I can see it updating.
If you end up setting state in your component in some context that enzyme doesn't know about, you will have to manually call .update() on the wrapper in order for it to get the updated version of the render tree.
it('renders correct number of cats', () => {
let wrapper = mount(<App />)
expect(wrapper.update().state('cats').length).toBe(3)
})
I had a similar problem and it was necessary to return a promise from the it callback and check the expectation in the then method of the promise.
In your case (assuming ajaxFn was a promise, or you could turn it into one) I think this would be approximately:
it('renders correct number of cats', () => {
let wrapper = mount(<App />)
return ajaxFn.then(() => {
expect(wrapper.state('cats').length).toBe(3);
}
})
I am not familiar with all the libraries you are using, but since your code is being executed asynchronously the test is finishing before the state can be updated. I was able to solve this problem by adding async/await to the test:
it('renders correct number of cats', async () => {
let wrapper = await mount(<App />)
expect(wrapper.state('cats').length).toBe(3)
})

Resources